diff --git a/votingapi.admin.inc b/votingapi.admin.inc
index 7cae7d7..c32f1c5 100644
--- a/votingapi.admin.inc
+++ b/votingapi.admin.inc
@@ -43,46 +43,308 @@ function votingapi_settings_form($form_state) {
 }
 
 /**
- * Developer tool to generate dummy votes.
+ * Developer tool to generate fake votes.
  */
 function votingapi_generate_votes_form() {
-  $form['node_types'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Which node types should receive votes?'),
-    '#options' => node_type_get_names(),
-    '#default_value' => array_keys(node_type_get_names()),
+  $entities = entity_get_info();
+  foreach ($entities as $entity => $data) {
+    $entity_types[$entity] = t($data['label']);
+  }
+
+  $node_type_names = node_type_get_names();
+  foreach ($node_type_names as $node_type_machine_name => $node_type_name) {
+    $node_types[$node_type_machine_name] = array('node_type' => '<strong>' . $node_type_machine_name . '</strong> (' . $node_type_name . ')');
+  }
+
+  $metadata = votingapi_metadata();
+  foreach ($metadata['tags'] as $tag => $data) {
+    $vote_tags[$tag] = array('tag' => '<strong>' . $tag . '</strong> (' . $data['description'] . ')');
+  }
+  foreach ($metadata['value_types'] as $value_type => $data) {
+    switch ($value_type) {
+      case 'percent': $text = t('Percent (averaged)'); break;
+      case 'points': $text = t('Points (summed)'); break;
+      case 'option': $text = t('Option (counted per option)'); break;
+      default: $text = $data['name'];
+    }
+    $vote_value_types[$value_type] = $text;
+  }
+
+  $form['delete_votes_option'] = array(
+    '#type' => 'radios',
+    '#default_value' => 'none',
+    '#options' => 
+      array(
+        'none' => t('Do not delete any votes'), 
+        'kill_all' => t('Delete ALL votes for ALL entities'),
+        'select_criteria' => t('Delete votes by criteria...')
+      ),
+  );
+  
+  $form['delete_votes'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Delete existing votes by criteria...'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+    '#states' => array(
+      'visible' => array(
+        ':input[name="delete_votes_option"]' => array('value' => 'select_criteria'),
+      ),
+    ),
   );
 
-  $form['vote_type'] = array(
+  $form['delete_votes']['delete_entity_type'] = array(
     '#type' => 'select',
-    '#title' => t('What type of votes should be generated?'),
-    '#options' => array(
-      'percent' => t('Percentage (Fivestar style)'),
-      'points' => t('Point-based (Digg style)'),
+    '#title' => t('Select entity type for which to delete votes:'),
+    '#options' => $entity_types,
+    '#default_value' => 'node',
+  );
+
+ 
+  $form['delete_votes']['delete_node_types_container'] = array(
+    '#type' => 'container',
+    '#states' => array(
+      'visible' => array(
+        ':input[name="delete_entity_type"]' => array('value' => 'node'),
+      ),
+    ),
+  );
+  $header = array(
+    'node_type' => t('Select node types for which to delete votes:'),
+  );
+  $form['delete_votes']['delete_node_types_container']['delete_node_types'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $node_types,
+  );
+  $form['delete_votes']['delete_node_types_container']['delete_node_types_addendum'] = array(
+    '#markup' => t('<strong>If no node types are selected, then no votes will be deleted.</strong>'),
+  );
+
+  $header = array(
+    'tag' => t('Select vote tags for which to delete votes:'),
+  );
+  $form['delete_votes']['delete_tags'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $vote_tags,
+  );
+  $form['delete_votes']['delete_tags_addendum'] = array(
+    '#markup' => t("In most cases, the vote tag is simply 'vote', unless you have multiple categories of ratings.<br/><strong>If no vote tags are selected, then no votes will be deleted.</strong>"),
+  );
+
+ 
+  $form['generate_votes'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Generate new votes...'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  
+  $form['generate_votes']['generate_entity_type'] = array(
+    '#type' => 'select',
+    '#title' => t('Select entity type for which to generate votes:'),
+    '#options' => $entity_types,
+    '#default_value' => 'node',
+  );
+
+  $form['generate_votes']['generate_node_types_container'] = array(
+    '#type' => 'container',
+    '#states' => array(
+      'visible' => array(
+        ':input[name="generate_entity_type"]' => array('value' => 'node'),
+      ),
+    ),
+  );
+  $header = array(
+    'node_type' => t('Select node types for which to generate votes:'),
+  );
+  $form['generate_votes']['generate_node_types_container']['generate_node_types'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $node_types,
+  );
+  $form['generate_votes']['generate_node_types_container']['generate_node_types_addendum'] = array(
+    '#markup' => t('<strong>If no node types are selected, then no votes will be generated.</strong>'),
+  );
+  
+  $header = array(
+    'tag' => t('Select vote tags for which to generate votes:'),
+  );
+  $form['generate_votes']['generate_tags'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $vote_tags,
+  );
+  $form['generate_votes']['generate_tags_addendum'] = array(
+    '#markup' => t("In most cases, the vote tag is simply 'vote', unless you have multiple categories of ratings.<br/><strong>If no vote tags are selected, then no votes will be generated.</strong>"),
+  );
+
+  
+  $vote_styles = array(
+    array(
+      'description' => t('Fivestar module: 4 stars'),
+      'value_type' => 'percent',
+      'values' => '25,50,75,100'
+    ),
+    array(
+      'description' => t('Fivestar module: 5 stars'),
+      'value_type' => 'percent',
+      'values' => '20,40,60,80,100'
+    ),
+    array(
+      'description' => t('Fivestar module: 10 stars'),
+      'value_type' => 'percent',
+      'values' => '10,20,30,40,50,60,70,80,90,100'
+    ),
+    array(
+      'description' => t('Rate module: Fivestar widget'),
+      'value_type' => 'percent',
+      'values' => '0,25,50,75,100'
+    ),
+    array(
+      'description' => t('Up/Down, Like/Dislike, +/- , etc.'),
+      'value_type' => 'points',
+      'values' => '-1,1'
+    ),
+    array(
+      'description' => t('+1, Like, Thumbs up, etc.'),
+      'value_type' => 'points',
+      'values' => '1'
+    ),
+    array(
+      'description' => t('Rate module: Emotion widget, 3 options'),
+      'value_type' => 'option',
+      'values' => '1,2,3'
+    ),
+    
+    // please add any other preset voting styles in frequent use!
+    
+  );
+  $header = array(
+    'description' => t('Choose which kind of votes to generate:'),
+    'value_type' => '',
+    'values' => '',
+  );
+  $options = array();
+  foreach($vote_styles as $vote_style) {
+    $option_value = $vote_style['value_type'] . ";" . $vote_style['values'];
+    while (!empty($options[$option_value])) {
+      // ensure unique index for voting styles which may have the same type & values
+      $option_value_suffix = isset($option_value_suffix) ? $option_value_suffix + 1 : 1;
+      $option_value .= ";" . $option_value_suffix;  // this suffix is ignored by the handler
+    }
+    $options[$option_value] = $vote_style;
+  }
+  $options['custom'] = array(
+    'description' => t('CUSTOM...'),
+    'value_type' => '',
+    'values' => '',
+  );
+  $form['generate_votes']['vote_style'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $options,
+    '#multiple' => FALSE,
+    '#default_value' => 'percent;20,40,60,80,100',
+  );
+
+  
+  $form['generate_votes']['custom_style'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Custom vote settings:'),
+    '#collapsible' => FALSE,
+    '#states' => array(
+      'visible' => array(
+        ':input[name="vote_style"]' => array('value' => 'custom'),
+      ),
     ),
+  );
+  
+  $form['generate_votes']['custom_style']['vote_type'] = array(
+    '#type' => 'select',
+    '#title' => t('Vote value type:'),
+    '#options' => $vote_value_types,
     '#default_value' => 'percent',
+    '#description' => t('What type of vote values should be generated?'),
   );
 
-  $form['kill_votes'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Delete existing votes before generating new ones.'),
-    '#default_value' => FALSE,
+  $form['generate_votes']['custom_style']['vote_values'] = array(
+    '#type' => 'textfield', 
+    '#title' => t('Vote values:'),
+    '#description' => t('Enter a comma-separated list of numeric vote values that may be generated for each vote.<br/>(See examples above, and refer to the documentation for your voting widget.)'),
+  );
+
+  
+
+  $options = array(1 => t('Now'));
+  foreach (array(3600, 86400, 604800, 2592000, 31536000) as $interval) {
+    $options[$interval] = format_interval($interval, 1) . ' ' . t('ago');
+  }
+  $form['generate_votes']['time_range'] = array(
+    '#type' => 'select',
+    '#title' => t('Maximum age of new votes:'),
+    '#description' => t('Vote dates will be distributed randomly between the selected time and the current time.'),
+    '#options' => $options,
+    '#default_value' => 604800,
   );
+
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Do it!'),
   );
+  
+  drupal_add_css("
+    table.sticky-table {
+      margin-top: 1em;
+      margin-bottom: 0em;
+    }
+    table.sticky-table td {
+      padding-top: .2em;
+      padding-bottom: .4em;
+    }
+    fieldset#edit-delete-votes,
+    fieldset#edit-custom-style {
+      margin-left: 2em;
+    }
+    ",
+    array('type' => 'inline')
+  );
+
   return $form;
 }
 
+
 /**
  * Submit handler for votingapi_generate_votes_form.
  */
 function votingapi_generate_votes_form_submit($form, &$form_state) {
+  if ($form_state['values']['vote_style'] != 'custom') {
+    $vote_settings = explode(';', $form_state['values']['vote_style']);
+    $vote_type = $vote_settings[0];
+    $vote_values = $vote_settings[1];
+  }
+  else { // custom vote style
+    $vote_type = $form_state['values']['vote_type'];
+    $vote_values = $form_state['values']['vote_values'];
+    if (empty($vote_values)) {
+      drupal_set_message(t('<strong>You must specify the vote values for a custom vote.</strong>'), 'error');
+      return;
+    }
+  }
+  $vote_values = explode(',', $vote_values);
+
   $options = array(
-    'types' => $form_state['values']['node_types'],
-    'kill' => $form_state['values']['kill_votes'],
+    'kill' => ($form_state['values']['delete_votes_option'] == 'kill_all'),  // boolean expression; overrides other delete options
+    'delete_entity_type' => $form_state['values']['delete_entity_type'],
+    'delete_node_types' => $form_state['values']['delete_node_types'],
+    'delete_tags' => $form_state['values']['delete_tags'],
+    'generate_node_types' => $form_state['values']['generate_node_types'],
+    'generate_tags' => $form_state['values']['generate_tags'],
+    'vote_values' => $vote_values,
+    'time_range' => $form_state['values']['time_range'],
   );
+
   require_once(drupal_get_path('module', 'votingapi') . '/votingapi.drush.inc');
-  votingapi_generate_votes('node', $form_state['values']['vote_type'], $options);
+  votingapi_generate_votes($form_state['values']['generate_entity_type'], $vote_type, $options);
 }
diff --git a/votingapi.drush.inc b/votingapi.drush.inc
index 800fa98..6cad675 100644
--- a/votingapi.drush.inc
+++ b/votingapi.drush.inc
@@ -2,12 +2,12 @@
 
 /**
  * @file
- *  Generate votingapi votes, recalculate results for existing votes, or flush
- *  VotingAPI data entirely.
+ *  Generate fake VotingAPI votes, delete VotingAPI votes, 
+ *  or recalculate results for existing VotingAPI votes
  */
 
 /**
- * Implements of hook_drush_help().
+ * Implements hook_drush_help().
  */
 function votingapi_drush_help($section) {
   switch ($section) {
@@ -21,20 +21,25 @@ function votingapi_drush_help($section) {
 }
 
 /**
- * Implements of hook_drush_command().
+ * Implements hook_drush_command().
  */
 function votingapi_drush_command() {
   $items['generate-votes'] = array(
-    'description' => 'Creates dummy voting data.',
+    'description' => 'Creates fake voting data.',
     'arguments' => array(
-      'entity_type' => 'The type of entity to generate votes for.',
-      'vote_type' => 'The type of votes to generate, defaults to \'percent\'.',
+      'entity_type' => 'The type of entity to generate votes for. Default: node',
+      'vote_type' => 'The type of votes to generate: percent, points, or option. Default: percent',
     ),
     'drupal dependencies' => array('devel_generate'),
     'options' => array(
-      'kill' => 'Specify \'kill\' to delete all existing votes before generating new ones.',
-      'age' => 'The maximum age, in seconds, of each vote.',
-      'node_types' => 'A comma delimited list of node types to generate votes for, if the entity type is \'node\'.',
+      'kill' => '(true/false) Delete ALL votes for ALL entities.  Overrides other delete options.',
+      'delete_entity_type' => 'Entity type to delete votes for.  Default: node',
+      'delete_node_types' => 'A comma delimited list of node types to delete votes for, if delete_entity_type is \'node\'.',
+      'delete_tags' => 'A comma delimited list of vote tags.  If specified, only votes with these tags will be deleted.',
+      'generate_node_types' => 'A comma delimited list of node types to generate votes for, if entity_type is \'node\'.',
+      'generate_tags' => 'A comma delimited list of vote tags.  If specified, votes will be generated for only these tags.',
+      'vote_values' => 'A comma delimited list of possible values for each vote.\n  Defaults: 20,40,60,80,100 (percent) or -1,1 (points) or 1,2,3 (option)',
+      'age' => 'The maximum age of each vote, in seconds.',
     ),
     'aliases' => array('genv'),
   );
@@ -47,11 +52,15 @@ function votingapi_drush_command() {
     'aliases' => array('vcalc'),
   );
   $items['votingapi-flush'] = array(
-    'description' => 'Deletes all existing voting data.',
+    'description' => 'Deletes existing voting data.',
     'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, // Various D7 code assumes we have a uid.
     'arguments' => array(
       'entity_type' => 'The type of entity whose voting data should be flushed.',
     ),
+    'options' => array(
+      'delete_tags' => 'A comma delimited list of vote tags.  If specified, only votes with these tags will be deleted.',
+      'delete_node_types' => 'A comma delimited list of node types to delete votes for, if the entity type is \'node\'.',
+    ),
     'aliases' => array('vflush'),
   );
   return $items;
@@ -59,141 +68,504 @@ function votingapi_drush_command() {
 
 
 /**
- * Command callback. Generate a number of votes.
+ * Command callback. Generate votes.
  */
 function drush_votingapi_generate_votes($entity_type = 'node', $vote_type = 'percent') {
+  $metadata = votingapi_metadata();
+  $all_vote_tags = array_keys($metadata['tags']);
+  $all_node_types = array_keys(node_type_get_names());
   $options = array(
-    'kill' => drush_get_option('kill'),
-    'age' => drush_get_option('age'),
-    'types' => drush_get_option('types'),
+    'kill' => drush_get_option('kill', FALSE),
+    'delete_entity_type' => drush_get_option('delete_entity_type', 'node'),
+    'delete_node_types' => drush_get_option_list('delete_node_types', $all_node_types),
+    'delete_tags' => drush_get_option_list('delete_tags', $all_vote_tags),
+    'generate_node_types' => drush_get_option_list('generate_node_types', $all_node_types),
+    'generate_tags' => drush_get_option_list('generate_tags', $all_vote_tags),
+    'vote_values' => drush_get_option_list('vote_values', array()),
+    'time_range' => drush_get_option('age', 36000),
   );
-
+  
+  drush_log(t('Generating @vtype votes for @etype entities.', array('@vtype' => $vote_type, '@etype' => $entity_type)));
   votingapi_generate_votes($entity_type, $vote_type, $options);
 
-  drush_log(t('Generating @vtype votes for @etype entities.', array('@vtype' => $vote_type, '@etype' => $entity_type)), 'success');
 }
 
+
+/**
+ * Command callback. Recalculate voting results.
+ */
+function drush_votingapi_recalculate($entity_type = 'node', $entity_id = NULL) {
+  // Prep some starter query objects.
+  $data = array();
+  if (empty($entity_id)) {
+    $votes = db_select('votingapi_vote', 'vv')
+      ->fields('vv', array('entity_type', 'entity_id'))
+      ->condition('entity_type', $entity_type, '=')->distinct(TRUE)
+      ->execute()->fetchAll(PDO::FETCH_ASSOC);
+      $message = t('Rebuilt voting results for @type votes.', array('@type' => $entity_type));
+  }
+  else {
+    $votes[] = array('entity_type' => $entity_type, 'entity_id' => $entity_id);
+    $message = t('Rebuilt all voting results.');
+  }
+
+  foreach ($votes as $vote) {
+    votingapi_recalculate_results($vote['entity_type'], $vote['entity_id'], TRUE);
+  }
+
+  drush_log($message, 'success');
+}
+
+
+/**
+ * Command callback. Flush votes and results.
+ */
+function drush_votingapi_flush($entity_type = NULL, $entity_id = NULL) {
+  if (drush_confirm(dt("Delete @type voting data?", array('@type' => empty($entity_type) ? dt('all') : $entity_type)))) {
+    $node_types = drush_get_option_list('delete_node_types', array());
+    $vote_tags = drush_get_option_list('delete_tags', array());
+    _votingapi_flush($entity_type, $node_types, $vote_tags, $entity_id);
+    drush_log(t('Flushed vote data for @type entities.', array('@type' => empty($entity_type) ? t('all') : $entity_type)), 'success');
+  }
+}
+
+
+/**
+ * Utility function to Flush votes and results.
+ */
+function _votingapi_flush($entity_type = NULL, $node_types = array(), $vote_tags = array(), $entity_id = NULL) {
+  $cache = db_delete('votingapi_cache');
+  $votes = db_delete('votingapi_vote');
+
+  if (!empty($entity_type)) {
+    $cache->condition('entity_type', $entity_type);
+    $votes->condition('entity_type', $entity_type);
+  
+    if ($entity_type == 'node' && !empty($node_types)) {
+      $subquery = db_select('node', 'n');
+      $subquery->addField('n', 'nid', 'entity_id');
+      $subquery->condition('n.type', $node_types, 'IN');
+
+      $cache->condition('entity_id', $subquery, 'IN');
+      $votes->condition('entity_id', $subquery, 'IN');
+    }
+  }
+
+  if (!empty($vote_tags)) {
+    $cache->condition('votingapi_cache.tag', $vote_tags, 'IN');
+    $votes->condition('votingapi_vote.tag', $vote_tags, 'IN');
+  }
+
+  if (!empty($entity_id)) {
+    $cache->condition('entity_id', $entity_id);
+    $votes->condition('entity_id', $entity_id);
+  }
+
+  $cache->execute();
+  $votes->execute();
+}
+
+
 /**
- * Utility function to generate votes.
+ * Utility function to generate fake votes.
  */
 function votingapi_generate_votes($entity_type = 'node', $vote_type = 'percent', $options = array()) {
   module_load_include('inc', 'devel_generate');
-  $options += array(
-    'age' => 36000,
-    'types' => array(),
-    'kill' => FALSE,
-  );
 
-  if (!empty($options['kill_votes'])) {
-    db_truncate('votingapi_vote');
-    db_truncate('votingapi_cache');
+  $delete_entity_type = $options['delete_entity_type'];
+  $delete_node_types = array_filter($options['delete_node_types']);
+  $delete_tags = array_filter($options['delete_tags']);
+  
+  $generate_node_types = array_filter($options['generate_node_types']);
+  $generate_tags = array_filter($options['generate_tags']);
+  
+  $all_entity_types = array_keys(entity_get_info());
+  $all_node_types = array_keys(node_type_get_names());
+  $metadata = votingapi_metadata();
+  $all_vote_tags = array_keys($metadata['tags']);
+  $all_vote_value_types = array_keys($metadata['value_types']);
+
+  
+  /* validation */
+  $error_message = "";
+  if (!empty($delete_entity_type) && !in_array($delete_entity_type, $all_entity_types)) $error_message .= t('Invalid entity type:') . " <strong>$delete_entity_type</strong><br/>";
+  if (!in_array($entity_type, $all_entity_types)) $error_message .= t('Invalid entity type:') . " <strong>$entity_type</strong><br/>";
+  $invalid_node_types = implode(', ', array_diff(array_merge($delete_node_types, $generate_node_types), $all_node_types));
+  if (!empty($invalid_node_types)) $error_message .= t('Invalid node type:') . " <strong>$invalid_node_types</strong><br/>";
+  $invalid_vote_tags = implode(', ', array_diff(array_merge($delete_tags, $generate_tags), $all_vote_tags));
+  if (!empty($invalid_vote_tags)) $error_message .= t('Invalid vote tag:') . " <strong>$invalid_vote_tags</strong><br/>";
+  if (!in_array($vote_type, $all_vote_value_types)) $error_message .= t('Invalid vote value type:') . " <strong>$vote_type</strong><br/>";
+  if (!empty($error_message)) {
+    drupal_set_message($error_message, 'error');
+    return;
+  }
+  //TODO: the t() string processing needs some cleanup on validation feedback (above)
+  
+  
+  /* delete existing votes */
+  
+  if ($options['kill']) {
+    // delete ALL votes for ALL entities.  overrides all other delete options.
+    _votingapi_flush();
+    drupal_set_message(t('Deleted all votes for all entities.'));
+  }
+  elseif (!empty($delete_tags) && ($delete_entity_type != 'node' || !empty($delete_node_types))) {
+    if ($delete_entity_type == 'node') {
+      $deleted_content = '[<strong>'. implode(', ', $delete_node_types) . '</strong>] nodes';
+      if (!array_diff($all_node_types, $delete_node_types)) $delete_node_types = array(); // all node types, remove condition
+    }
+    else {
+      $delete_node_types = array();
+      $deleted_content = "[<strong>$delete_entity_type</strong>] entities";
+    }
+
+    _votingapi_flush($delete_entity_type, $delete_node_types, $delete_tags);
+    
+    $deleted_tags = '[<strong>' . implode(', ', $delete_tags) . '</strong>]';
+    $message = t('Deleted all !deleted_vote_tags votes for all !deleted_vote_content.', 
+      array('!deleted_vote_tags' => $deleted_tags, '!deleted_vote_content' => $deleted_content));
+    drupal_set_message($message);
+  }
+  else {
+    drupal_set_message(t('No votes deleted.'));
   }
 
+
+  /* generate new votes */
+  
+  if ($entity_type != 'node') {
+    $generate_node_types = array();
+  }
+  
+  if (empty($generate_tags)) {
+    drupal_set_message(t('No votes generated.'));
+    return;
+  }
+  
   if (($schema = drupal_get_schema($entity_type)) && ($entity_id_column = array_shift($schema['primary key']))) {
-    // oh look we found a schema yay
+    // oh look we found a schema yay     <-- haha, I love that comment  ;)
   }
   else {
     $entity_type = 'node';
     $entity_id_column = 'nid';
   }
-  $uids = devel_get_users();
 
   $query = db_select($entity_type, 'e')->fields('e', array($entity_id_column));
-  if ($entity_type == 'node' && !empty($options['types'])) {
-    $query->condition('e.type', $options['types'], 'IN');
+  if ($entity_type == 'node') {
+    if (empty($generate_node_types)) {
+      drupal_set_message(t('No node types selected for vote generation.'));
+      return;
+    }
+    $query->condition('e.type', $generate_node_types, 'IN');
   }
   $results = $query->execute()->fetchAll(PDO::FETCH_ASSOC);
-
+  
   foreach ($results as $entity) {
-    _votingapi_cast_votes($entity_type, $entity[$entity_id_column], $options['age'], $uids, $vote_type);
+    $eids[] = $entity[$entity_id_column];
+  }
+  
+  if (empty($eids)) {
+    drupal_set_message(t('No entities found - No votes generated.'));
+    return;
+  }
+  
+  $ignore_zero_vote = false;
+  
+  foreach ($options['vote_values'] as $dirty_vote_value) {
+    $vote_value = trim($dirty_vote_value);
+    if (preg_match('/[^\-\d\.]/', $vote_value)) {  // only allow numeric digits, negative sign, and decimal point
+      $invalid_values[] = $vote_value;
+    }
+    else {
+      $vote_values[$vote_value] = $vote_value;
+    }
+  }
+  if (!empty($invalid_values)) {
+    $error_message = t('Invalid vote value: [<strong>@value_list</strong>]  Only numeric values accepted.', array('@value_list' => implode(', ', $invalid_values)));
+    drupal_set_message($error_message, 'error');
+    return;
+  }
+
+  if (empty($vote_values)) {
+    // provide some reasonable default values for drush
+    // (admin UI already requires values field to validate)
+    $default_vote_values = array(
+      'percent' => array(20,40,60,80,100),  // Fivestar module
+      'points' => array(-1,1),  // Up/Down
+      'option'=> array(1,2,3),  // Rate module Emotion widget
+    );
+    $vote_values = $default_vote_values[$vote_type];
+  } 
+  else {
+    if (count($vote_values) == 1) {
+      if ($vote_type == 'points' || $vote_type == 'option') {
+        if (!isset($vote_values[0])) {  
+          $vote_values[0] = 0;      // zero value is a place-holder representing a vote withheld, 
+                                    // in a vote/no-vote use case, such as +1 or Like
+          $ignore_zero_vote = true; // but don't actually record the zero vote
+        }
+        else {
+          drupal_set_message(t('You must specify at least one non-zero vote value.<br/>No votes generated.'), 'error');
+          return;
+        }
+      }
+      elseif ($vote_type == 'percent') {
+        // percent votes aren't meaningful without at least two options
+        drupal_set_message(t('You must specify at least two vote values for percent votes.<br/>No votes generated.'), 'error');
+        return;
+      }
+    }
+    $vote_values = array_keys($vote_values);
+  }
+  
+  $vote_count = _votingapi_cast_votes($entity_type, $eids, $vote_type, $vote_values, $generate_tags, $options['time_range'], $ignore_zero_vote);
+  
+  $generated_tags = '[<strong>' . implode(', ', $generate_tags) . '</strong>]';
+  if ($entity_type == 'node') {
+    $generated_content = '[<strong>'. implode(', ', $generate_node_types) . '</strong>] nodes';
+  }
+  else {
+    $generated_content = "[<strong>$entity_type</strong>] entities";
   }
+  $message = t('Generated a total of !vote_count !generated_vote_tags votes for !generated_vote_content.', 
+    array('!vote_count' => $vote_count, '!generated_vote_tags' => $generated_tags, '!generated_vote_content' => $generated_content));
+  drupal_set_message($message);
 }
 
+
 /**
- * Utility function to generate votes on a node by a set of users.
+ * Utility function to generate a set of fake votes by all users for a set of entity/tag combinations.
  */
-function _votingapi_cast_votes($etype, $eid, $timestamp = 0, $uids = array(), $style = 'percent') {
-  $votes = array();
-  static $tags;
-  if (!isset($tags)) {
-    $tags = explode(' ', devel_create_greeking(30));
-  }
-  foreach ($uids as $uid) {
-    switch ($style) {
-      case 'percent':
-        $votes[] = array(
-          'uid' => $uid,
-          'entity_type' => $etype,
-          'entity_id' => $eid,
-          'value_type' => 'percent',
-          'timestamp' => REQUEST_TIME - mt_rand(0, $timestamp),
-          'value' => mt_rand(1, 5) * 20,
-          'tag' => $tags[mt_rand(0, 30)],
-        );
-        break;
-      case 'points':
-        if (rand(0, 3)) {
+function _votingapi_cast_votes($entity_type, $eids, $vote_type, $vote_values, $tags, $time_range, $ignore_zero_vote = false) {
+  $uids = devel_get_users();
+  $vote_generator_class = ($vote_type == 'option') ? '_votingapi_vote_option_generator' : '_votingapi_vote_value_generator';
+  $vote_generator = new $vote_generator_class($vote_values, $uids, $eids, $tags);
+  
+  foreach ($eids as $eid) {     // for every specified vote tag on every specified entity,
+    foreach ($tags as $tag) {
+      foreach ($uids as $uid) { // every user has a chance to vote
+        $vote_value = $vote_generator->generate_vote_value($eid, $tag, $uid);
+        if (!is_null($vote_value) && $ignore_zero_vote == false || $vote_value != 0) {
           $votes[] = array(
             'uid' => $uid,
-            'entity_type' => $etype,
+            'entity_type' => $entity_type,
             'entity_id' => $eid,
-            'value_type' => 'points',
-            'timestamp' => REQUEST_TIME - mt_rand(0, $timestamp),
-            'value' => rand(0, 1) ? 1 : -1,
+            'value_type' => $vote_type,
+            'value' => $vote_value,
+            'tag' => $tag,
+            'timestamp' => mt_rand(REQUEST_TIME - $time_range, REQUEST_TIME),
           );
         }
-        break;
+      }
     }
   }
   votingapi_set_votes($votes, array());
+  return count($votes);
 }
 
+
 /**
- * Command callback. Recalculate voting results.
+ * Utility class for generating 'percent' and 'points' vote values
+ * 
+ * The statistical model sums a Gaussian-distributed user bias with an evenly distributed 
+ * target-average unique to each entity/tag combination, plus a bit of jitter on each vote.
+ * The result is quantized into a valid value for the widget.
  */
-function drush_votingapi_recalculate($entity_type = 'node', $entity_id = NULL) {
-  // Prep some starter query objects.
-  $data = array();
-  if (empty($entity_id)) {
-    $votes = db_select('votingapi_vote', 'vv')
-      ->fields('vv', array('entity_type', 'entity_id'))
-      ->condition('entity_type', $entity_type, '=')->distinct(TRUE)
-      ->execute()->fetchAll(PDO::FETCH_ASSOC);
-      $message = t('Rebuilt voting results for @type votes.', array('@type' => $entity_type));
-  }
-  else {
-    $votes[] = array('entity_type' => $entity_type, 'entity_id' => $entity_id);
-    $message = t('Rebuilt all voting results.');
-  }
+class _votingapi_vote_value_generator {
+  public $values, $target_values, $user_value_biases, $quantization_thresholds;
+  public $skip_vote_cutoff, $jitter_divisor, $jitter_offset;
 
-  foreach ($votes as $vote) {
-    votingapi_recalculate_results($vote['entity_type'], $vote['entity_id'], TRUE);
+  /**
+   * Constructor
+   * (lots of precalculating)
+   */
+  public function __construct($values, $uids, $eids, $tags) {
+    $this->values = $values;
+    
+    $minval = min($values);
+    $maxval = max($values);
+    $value_range = $maxval - $minval;
+    
+    // calculate midpoints between each possible vote value, to quantize generated values into valid vote values for the widget
+    // (some modules allow user-specified values, so we can't make any assumptions, just pick the closest value)
+    sort($values); // make sure they're in order
+    foreach ($values as $i => $value) {
+      $this->quantization_thresholds[$i] = ($value != $maxval) ? ($value + $values[$i + 1]) / 2 : 30000; // maxval catches everything else
+    }
+    
+    // calculate standard_deviation for user bias
+    $sd_factor = sqrt(min((count($uids) - 1), 30)) / 20; // totally arbitrary, keeps SD reasonable for sample size
+    $standard_deviation = $sd_factor * $value_range;
+    
+    // create lookup-table for persistent vote bias of each user
+    // this allows us to generate votes with normal distribution very quickly
+    // and also simulates a diversity of user attitudes, which may be useful 
+    // for testing code that identifies users who consistently vote at extremes
+    foreach ($uids as $uid) {
+      $this->user_value_biases[$uid] = _votingapi_standard_normal_rand() * $standard_deviation;
+    }
+    
+    // allows users to not vote sometimes
+    $this->skip_vote_cutoff = mt_getrandmax() * 0.8;
+
+    // jitter smooths out the normal-distributed user bias pattern somewhat
+    // jitter is effectively +/- 5% of the value range
+    // the presence of skip_vote_cutoff here will make sense if you study generate_vote_value()
+    $jitter_range = $value_range / 10;
+    $this->jitter_divisor = $this->skip_vote_cutoff / $jitter_range;
+    $this->jitter_offset = $jitter_range / 2;
+
+    // extend value range to allow target values beyond the allowed values of the widget
+    // (otherwise, it would be very rare for vote averages to equal MINVAL or MAXVAL)
+    $extension_factor = pow($value_range, 0.1) * 2.4 - 1.8;  // arbitrary, derived from testing
+    $range_extension = $standard_deviation * $extension_factor;
+    $minval -= $range_extension;
+    $value_range += 2 * $range_extension;
+
+    // create lookup-table of target values for each eid and tag combination
+    // this allows the rating to converge toward a specific value, instead of 50% average (or 0 points sum)
+    $rand_divisor = mt_getrandmax() / $value_range;
+    foreach ($eids as $eid) {
+      foreach ($tags as $tag) {
+        $this->target_values[$eid][$tag] = $minval + mt_rand() / $rand_divisor;
+      }
+    }
   }
+  
 
-  drush_log($message, 'success');
+  /**
+   * Generates a single vote value
+   */
+  public function generate_vote_value($eid, $tag, $uid) {
+    // a single call to mt_rand() for two different purposes
+    $rand_val = mt_rand();
+    // sometimes users don't vote
+    if ($rand_val > $this->skip_vote_cutoff) return null;
+    // below that, $rand_val will be scaled appropriately for jitter
+    $jitter = $rand_val / $this->jitter_divisor - $this->jitter_offset;
+    
+    $target_value = $this->target_values[$eid][$tag];
+    $user_bias = $this->user_value_biases[$uid];
+
+    // user bias is average zero, jitter is average zero, 
+    // so the AVERAGE of votes generated for this entity/tag combination
+    // will converge to the target value
+    $vote_value = $target_value + $user_bias + $jitter;
+    
+    // quantize to valid vote values
+    // this is necessary because many widgets don't recognize arbitrary values
+    foreach ($this->quantization_thresholds as $i => $threshold) {
+      if ($vote_value <= $threshold) {
+        return $this->values[$i];
+      }
+    }
+    return null;
+  }
 }
 
 
 /**
- * Command callback. Flush votes and results.
+ * Utility class for generating 'option' vote values
+ * 
+ * Option-type votes are counted per option, with no meaningful average,
+ * so we need a different statistical model to generate them:
+ * 
+ * Each user is given a random probability distribution of option preferences.
+ * Likewise, each entity/tag combination is given its own unique probability distribution.
+ * Each vote is produced by testing a random number against 
+ * a cumulative normalized sum of these probability distributions.
  */
-function drush_votingapi_flush($entity_type = NULL, $entity_id = NULL) {
-  if (drush_confirm(dt("Delete @type voting data?", array('@type' => empty($entity_type) ? dt('all') : $entity_type)))) {
-    $cache = db_delete('votingapi_cache');
-    $votes = db_delete('votingapi_vote');
+class _votingapi_vote_option_generator {
+  public $options, $user_option_distribution, $entity_option_distribution, $skip_vote_cutoff;
+
+  /**
+   * Constructor
+   */
+  public function __construct($options, $uids, $eids, $tags) {
+    $this->options = $options;
+    
+    foreach ($tags as $tag) { // each vote tag may have a completely independent set of option distributions
+      // generate a unique option distribution for each entity
+      foreach ($eids as $eid) {
+        $this->entity_option_distribution[$tag][$eid] = $this->generate_option_distribution($weight = 1);
+      }
+
+      // each user has their own distribution of bias toward each option 
+      foreach ($uids as $uid) {
+        $this->user_option_distribution[$tag][$uid] = $this->generate_option_distribution($weight = 0.2);
+      }
+    }
+    
+    // allows users to not vote occasionally
+    $this->skip_vote_cutoff = mt_getrandmax() * 0.8;
+  }
 
-    if (!empty($entity_type)) {
-      $cache->condition('entity_type', $entity_type);
-      $votes->condition('entity_type', $entity_type);
+  /**
+   * Builds an array of values representing the relative likelihood of each option (denormalized)
+   */
+  public function generate_option_distribution($weight) {
+    $distribution = array();
+    $total = 0;
+    foreach ($this->options as $option) {
+      $option_likelihood = mt_rand() / mt_getrandmax();
+      $option_likelihood = pow($option_likelihood, 10); // encourage more decisive results
+      $option_likelihood *= $weight;
+      $distribution[$option] = $option_likelihood;
+      $total += $option_likelihood;
     }
-    if (!empty($entity_id)) {
-      $cache->condition('entity_id', $entity_id);
-      $votes->condition('entity_id', $entity_id);
+    $distribution['total'] = $total;
+    return $distribution;
+  }
+  
+  /**
+   * Generates a single vote value
+   */
+  public function generate_vote_value($eid, $tag, $uid) {
+    // a single call to mt_rand() for two different purposes
+    $rand_val = mt_rand();
+    // sometimes users don't vote
+    if ($rand_val > $this->skip_vote_cutoff) return null;
+    // below that, $rand_val will be scaled appropriately to test against a normalized sum of cumulative probability distributions
+
+    $entity_distribution = $this->entity_option_distribution[$tag][$eid];
+    $user_distribution = $this->user_option_distribution[$tag][$uid];
+    $total = $entity_distribution['total'] + $user_distribution['total'];
+    $test_val = $rand_val * $total / $this->skip_vote_cutoff;
+    
+    $option_val = 0;
+    foreach ($this->options as $option) {
+      $option_val += $entity_distribution[$option] + $user_distribution[$option];
+      if ($test_val <= $option_val) return $option;
     }
+    return null;
+  }
+}
 
-    $cache->execute();
-    $votes->execute();
 
-    drush_log(t('Flushed vote data for @type entities.', array('@type' => empty($entity_type) ? t('all') : $entity_type)), 'success');
+/**
+ * Utility function to generate random numbers with standard normal distribution (Gaussian)
+ * (Implements Box-Muller polar transform.)
+ */
+function _votingapi_standard_normal_rand() {
+  static $rand_factor, $do_init = true, $y2;
+  if (!isset($rand_factor)) $rand_factor = 2 / mt_getrandmax();
+  
+  if ($do_init) {
+    $do_init = false;
+    do {
+      $x1 = mt_rand() * $rand_factor - 1;
+      $x2 = mt_rand() * $rand_factor - 1;
+      $w = $x1 * $x1 + $x2 * $x2;
+    } while ( $w >= 1 );
+    $w = sqrt((-2 * log($w)) / $w);
+    $y1 = $x1 * $w;
+    $y2 = $x2 * $w;
+    return $y1;
+  }
+  else {
+    $do_init = true;
+    return $y2;
   }
 }
+
+
