diff --git a/textcaptcha.drush.inc b/textcaptcha.drush.inc
new file mode 100644
index 0000000..2f53718
--- /dev/null
+++ b/textcaptcha.drush.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ *   Text CAPTCHA drush commands
+ */
+
+/**
+ * Implements hook_drush_command().
+ *
+ * See `drush topic docs-commands` for a list of recognized keys.
+ *
+ * @return
+ *   An associative array describing your command(s).
+ */
+function textcaptcha_drush_command() {
+  $items = array();
+
+  $items['textcaptcha-fetch'] = array(
+    'description' => "Fetch questions from the Text CAPTCHA service",
+    'examples' => array(
+      'drush textcaptcha-fetch' => 'Fetch the default number of questions.',
+    ),
+  );
+
+  return $items;
+}
+
+/**
+ * drush command callback
+ *
+ * @see drush_invoke()
+ * @see drush.api.php
+ */
+function drush_textcaptcha_fetch(){
+  textcaptcha_get_new_challenges();
+}
diff --git a/textcaptcha.install b/textcaptcha.install
new file mode 100644
index 0000000..97d2a1f
--- /dev/null
+++ b/textcaptcha.install
@@ -0,0 +1,112 @@
+<?php
+
+ /**
+ * Implements hook_install().
+ */
+function textcaptcha_install() {
+  $text = t('To set up textCAPTCHA get an API key !here. Then go to the admin
+  configuration page !admin to save your key and retrieve some challenge
+  questions.',
+    array(
+      '!here' => l('here', 'http://textcaptcha.com/'),
+      '!admin' => l('here', 'admin/config/people/captcha/textcaptcha'),
+    )
+  );
+  drupal_set_message($text, 'warning');
+}
+
+/**
+ * Implements hook_schema().
+ */
+function textcaptcha_schema() {
+  $schema['textcaptcha_answers'] = array(
+    'description' => 'TODO: please describe this table!',
+    'fields' => array(
+      'aid' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'serial',
+        'not null' => TRUE,
+      ),
+      'qid' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+      'answer' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'text',
+        'not null' => TRUE,
+      ),
+      'created' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('aid'),
+  );
+  
+  $schema['textcaptcha_questions'] = array(
+    'description' => 'TODO: please describe this table!',
+    'fields' => array(
+      'qid' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'serial',
+        'not null' => TRUE,
+      ),
+      'question' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'text',
+        'not null' => TRUE,
+      ),
+      'created' => array(
+        'description' => 'TODO: please describe this field!',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('qid'),
+  );
+  
+  return $schema;
+}
+
+/**
+ * Implements hook_update_N().
+ */
+function textcaptcha_update_7001() {
+  // Update captcha limit. There was a bug, restricting cache limit to 0, 1, 2,
+  // 3, or 4 (when users were selecting 0, 10, 20, 30, 40).
+  $limit = variable_get('textcaptcha_cache_limit', 0);
+  switch ($limit) {
+    case 0:
+    case 1:
+      variable_set('textcaptcha_cache_limit', 10);
+      break;
+    case 2:
+      variable_set('textcaptcha_cache_limit', 20);
+      break;
+    case 3:
+      variable_set('textcaptcha_cache_limit', 30);
+      break;
+    case 4:
+      variable_set('textcaptcha_cache_limit', 40);
+      break;
+  }
+
+  // Populate cache with some challenges.
+  for ($i = 0; $i < 10; $i++) {
+    if ($challenge = textcaptcha_get_new_challenge()) {
+      textcaptcha_cache_new_challenge($challenge);
+    }
+  }
+}
+
+/**
+ * Implements hook_update_N().
+ * 
+ * Install new schema for textcaptcha database tables. 
+ */
+function textcaptcha_update_7002() {
+  drupal_install_schema('textcaptcha');
+}
diff --git a/textcaptcha.module b/textcaptcha.module
index d9273a7..f089ed7 100644
--- a/textcaptcha.module
+++ b/textcaptcha.module
@@ -1,16 +1,16 @@
-<?php // $Id: textcaptcha.module,v 1.1.2.2.2.1 2010/12/08 21:34:33 kevee Exp $
-
+<?php
 /**
-*  @file
-*  Provides integration between the CAPTCHA module [http://drupal.org/project/captcha]
-*  and the text-only CAPTCHA service textcaptcha.com
-*/
+ * @file
+ * Provides integration between the CAPTCHA module
+ * [http://drupal.org/project/captcha] and the text-only
+ * CAPTCHA service textcaptcha.com
+ */
 
 define('TEXTCAPTCHA_URL_PREFIX', 'http://textcaptcha.com/api/');
 
 /**
-*  Implementation of hook_menu()
-*/
+ *  Implements hook_menu().
+ */
 function textcaptcha_menu() {
   $items = array();
   $items['admin/config/people/captcha/textcaptcha'] = array(
@@ -19,20 +19,22 @@ function textcaptcha_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('textcaptcha_settings_form'),
     'access arguments' => array('administer CAPTCHA settings'),
-    'type' => MENU_LOCAL_TASK
+    'type' => MENU_LOCAL_TASK,
   );
   return $items;
 }
 
 /**
-*  Admin settings form callback
-*/
+ *  Admin settings form callback.
+ */
 function textcaptcha_settings_form() {
   $form = array();
 
   $form['instructions']['#value'] = t('To use TextCAPTCHA, you need to get an API key by !link',
-                      array('!link' => l('registering at textcaptcha.com',
-                                 'http://textcaptcha.com/register')));
+                      array(
+                        '!link' => l(t('registering at textcaptcha.com'),
+                        'http://textcaptcha.com/register'))
+                      );
 
   $form['textcaptcha_api_key'] = array(
     '#type' => 'textfield',
@@ -42,26 +44,25 @@ function textcaptcha_settings_form() {
     '#required' => TRUE,
   );
 
-  $form['textcaptcha_cache_limit'] = array(
-    '#type' => 'select',
-    '#title' => t('Number of challenges to keep as backup'),
-    '#description' => t('If you cannot contact textcaptcha.com or the service is unavailable, some challenges are kept in the site cache as backup. Select zero to turn this feature off.'),
-    '#options' => array(
-              0,
-              10,
-              20,
-              30,
-              40
-            ),
-    '#default_value' => variable_get('textcaptcha_cache_limit', 10),
+  $form['textcaptcha_cron'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Fetch questions on cron'),
+    '#description' => t('Enable or disable fetching and processing questions on cron.'),
+    '#default_value' => variable_get('textcaptcha_cron', 1),
+  );
+
+  $form['actions']['text_captcha'] = array(
+    '#type' => 'submit',
+    '#value' => t('Retrieve new captcha challenges'),
+    '#submit' => array('textcaptcha_get_new_challenges'),
   );
 
   return system_settings_form($form);
 }
 
 /**
-*  Form validation for admin settings. Here we check that the API key works.
-*/
+ *  Form validation for admin settings. Here we check that the API key works.
+ */
 function textcaptcha_settings_form_validate($form, $form_state) {
   $result = drupal_http_request(TEXTCAPTCHA_URL_PREFIX . trim($form_state['values']['textcaptcha_api_key']));
   if ($result->code !== '200') {
@@ -70,20 +71,226 @@ function textcaptcha_settings_form_validate($form, $form_state) {
 }
 
 /**
- * Implementation of hook_captcha().
+ * Function to add new question to database.
+ *
+ * @param string $question
+ *   Question to enter into the database.
+ *
+ * @return int
+ *   Returns the id of the newly created question, or FALSE if error.
+ */
+function textcaptcha_question_create($question) {
+
+  $record = new stdClass();
+  $record->question = $question;
+  $record->created = time();
+
+  drupal_write_record('textcaptcha_questions', $record);
+
+  if (!isset($record->qid)) {
+    watchdog('textcaptcha', "There was an error writing a question to the database. Try clearing drupal's cache to update the schema registry.");
+    return FALSE;
+  }
+
+  return $record->qid;
+}
+
+/**
+ * Function to add new answer to database.
+ *
+ * @param int $qid
+ *   The database id of the question to which this answer is associated.
+ * @param string $answer
+ *   Hashed answer for the question.
+ *
+ * @return int
+ *   Returns the id of the newly created question, or FALSE if error.
+ */
+function textcaptcha_answer_create($qid, $answer) {
+
+  $record = new stdClass();
+  $record->qid = $qid;
+  $record->answer = $answer;
+  $record->created = time();
+
+  drupal_write_record('textcaptcha_answers', $record);
+
+  if (!isset($record->aid)) {
+    watchdog('textcaptcha', "There was an error writing the answer to the database. Try clearing drupal's cache to update the schema registry.");
+    return FALSE;
+  }
+
+  return $record->aid;
+}
+
+/**
+ * Save new question / answer combo to database.
+ *
+ * @param array $challenge
+ *   Challange retrieved from textcaptcha.org and parsed into array.
+ *
+ * @return bool
+ *   Returns TRUE on success or FALSE on failure
+ */
+function textcaptcha_challange_add($challenge) {
+  if (!textcaptcha_verify_distinct_challenge($challenge['question'])) {
+    return FALSE;
+  }
+
+  $qid = textcaptcha_question_create($challenge['question']);
+  foreach ($challenge['answers'] as $answer) {
+    if ($qid) {
+      textcaptcha_answer_create($qid, $answer);
+    }
+  }
+
+  return TRUE;
+}
+
+/**
+ * Function to verify that new challenges are distinct.
+ *
+ * @param array $question
+ *   Question to verify if entry exists in database.
+ *
+ * @return bool
+ *   If an entry exists validation returns FAILURE, otherwise pass
+ *   validation with TRUE
+ */
+function textcaptcha_verify_distinct_challenge($question) {
+  // Check to see if question is already in the system.
+  $result = db_select('textcaptcha_questions', 'q')
+    ->fields('q')
+    ->condition('question', $question)
+    ->execute()
+    ->fetchAssoc();
+
+  return $result ? FALSE : TRUE;
+}
+
+/**
+ * Get new challenge questions.
+ */
+function textcaptcha_get_new_challenges() {
+  $success = 0;
+  $total_successes = 0;
+  for ($i = 0; $i < 10; $i++) {
+    if ($challenge = textcaptcha_get_new_challenge()) {
+      $new_challenge = textcaptcha_challange_add($challenge);
+      if ($new_challenge) {
+        $success++;
+      }
+      $total_successes++;
+    }
+  }
+
+  if ($success < 1) {
+    $text = t('Sorry. After trying !i times, I was unable to retrieve any new
+    challenge questions. Either your API key is bad, you are not online, or the
+    web service you are trying to reach is not responding.', array('!i' => $i));
+    drupal_set_message($text, 'warning');
+  }
+  else {
+    $text = t('Success! You have !success new challenge questions. (!total/!i
+    attempts to retrieve new challenge questions were successful.)',
+      array(
+        '!success' => $success,
+        '!total' => $total_successes,
+        '!i' => $i,
+      )
+    );
+    drupal_set_message($text, 'success');
+  }
+}
+
+/**
+ * Retrieve a new challenge from the Text CAPTCHA web service.
+ *
+ * @return int
+ *   Returns a challenge array or FALSE if failed attempt to
+ *   reach textcaptcha.com
+ */
+function textcaptcha_get_new_challenge() {
+  // Attempt to retrieve a new challenge from the web service.
+  $success = FALSE;
+  for ($i = 1; $i <= 10; $i++) {
+    $response = textcaptcha_http_request();
+    if (!$response->code == 200) {
+      // We didn't get a successful response. Log failed attempt.
+      $text = t('Failed attempt !n to get textcaptcha question from !api.', array('!n' => $i, '!api' => TEXTCAPTCHA_URL_PREFIX));
+      watchdog('textcaptcha', $text);
+    }
+    else {
+      // Log success.
+      $success = TRUE;
+      $text = t('Successfully retrieved new textcaptcha challenge question on attempt !n', array('!n' => $i));
+      watchdog('textcaptcha', $text);
+      break;
+    }
+  }
+
+  return ($success) ? textcaptcha_parse_data($response->data) : FALSE;
+}
+
+/**
+ * Wrapper around textcaptcha API call.
+ *
+ * @return array
+ *   Response from drupal_http_request() to textcaptcha web service.
+ */
+function textcaptcha_http_request() {
+  $response = drupal_http_request(TEXTCAPTCHA_URL_PREFIX . trim(variable_get('textcaptcha_api_key', '')));
+  return $response;
+}
+
+/**
+ * Parse return from textcaptcha.com api calls.
+ *
+ * @param array $data
+ *   Data from a successful drupal_http_request() call to textcaptcha.com API
+ *
+ * @return array
+ *   Return array including challenge and md5 hash of valid answers.
+ *    e.g. $challenge(
+ *            'question' => 'What color is the sky?',
+ *            'answers' => array('c9f0f895f', '24d27c169'),
+ *    );
+ */
+function textcaptcha_parse_data($data) {
+  $parser = drupal_xml_parser_create($data);
+  $captcha_xml = array();
+  xml_parse_into_struct($parser, $data, $captcha_xml);
+
+  $challenge = array();
+  foreach ($captcha_xml as $element) {
+    if ($element['tag'] == 'QUESTION' && $element['type'] == 'complete') {
+      $challenge['question'] = $element['value'];
+    }
+    if ($element['tag'] == 'ANSWER' && $element['type'] == 'complete') {
+      $challenge['answers'][] = $element['value'];
+    }
+  }
+
+  return $challenge;
+}
+
+/**
+ * Implements hook_captcha().
  */
 function textcaptcha_captcha($op, $captcha_type='') {
   switch ($op) {
-    case 'list' :
+    case 'list':
       return array('Text Captcha');
-      break;
-    case 'generate' :
+    break;
+    case 'generate':
+      $challenge_description = variable_get('textcaptcha_challenge_description', FALSE);
+
       $result = array();
       if ($captcha_type == 'Text Captcha') {
-        $textcaptcha = textcaptcha_get_captcha();
+        $challenge = textcaptcha_get_captcha();
         $result['form']['captcha_response'] = array(
           '#type' => 'textfield',
-          '#title' => $textcaptcha['question'],
+          '#title' => $challenge['question'],
           '#description' => t('Question text provided by !link',
                               array('!link' => l('textcaptcha.com', 'http://textcaptcha.com'))
                              ),
@@ -91,7 +298,7 @@ function textcaptcha_captcha($op, $captcha_type='') {
           '#maxlength' => 50,
           '#required' => TRUE,
         );
-        $result['solution'] = serialize($textcaptcha['answers']);
+        $result['solution'] = serialize($challenge['answers']);
         $result['captcha_validate'] = 'textcaptcha_captcha_validate';
 
         return $result;
@@ -101,79 +308,66 @@ function textcaptcha_captcha($op, $captcha_type='') {
 }
 
 /**
-*  Retrieves CAPTCHA and possible answer list from the textcaptcha.com service
-*/
-function textcaptcha_get_captcha($use_cache = TRUE) {
-  if (variable_get('textcaptcha_cache_limit', 10) == 0) {
-    $use_cache = FALSE;
+ * Retrieve captcha from service.
+ *
+ * @return array
+ *   Question and appropriate answer hashes.
+ */
+function textcaptcha_get_captcha() {
+
+  // Right now, just proxy textcaptcha_get_random_question().
+  // This should be able to grab a question from multiple sources.
+  return textcaptcha_get_random_question();
+}
+
+/**
+ * Implements hook_cron().
+ *
+ * Every time cron runs it fetches new challenge questions.
+ */
+function textcaptcha_cron() {
+  if (variable_get('textcaptcha_cron', 1)) {
+    textcaptcha_get_new_challenges();
   }
-  if (variable_get('textcaptcha_api_key', FALSE)) {
-    $cached_challenges = cache_get('textcaptcha_cached_challenges');
-    $captcha = drupal_http_request(TEXTCAPTCHA_URL_PREFIX . trim(variable_get('textcaptcha_api_key', '')));
-    if ($captcha->code == '200' && $captcha->data) {
-      $parser = drupal_xml_parser_create($captcha->data);
-      $captcha_xml = array();
-      xml_parse_into_struct($parser, $captcha->data, $captcha_xml);
-      $answers = array();
-      foreach ($captcha_xml as $element) {
-        if ($element['tag'] == 'QUESTION' && $element['type'] == 'complete') {
-          $question = $element['value'];
-        }
-        if ($element['tag'] == 'ANSWER' && $element['type'] == 'complete') {
-          $answers[] = $element['value'];
-        }
-      }
-      $cached_challenges->data[] = array('question' => $question,
-                         'answers' => $answers);
-      if (count($cached_challenges->data) > variable_get('textcaptcha_cache_limit', 10)) {
-        array_shift($cached_challenges->data);
-      }
-      if ($use_cache) {
-        cache_set('textcaptcha_cached_challenges', $cached_challenges->data);
-      }
-      return array('question' => $question, 'answers' => $answers);
-    }
-    elseif ($cached_challenges && $use_cache) {
-      return array_rand($cached_challenges->data);
-    }
+}
+
+/**
+ * Retrieve random question/answer.
+ *
+ * @return array
+ *   Question and appropriate answer hashes from database.
+ */
+function textcaptcha_get_random_question() {
+  $textcaptcha = array();
+
+  $result = db_select('textcaptcha_questions', 'q')
+    ->fields('q')
+    ->range(0, 1)
+    ->orderRandom()
+    ->execute()
+    ->fetchAssoc();
+
+  $textcaptcha['question'] = $result['question'];
+
+  $answers = db_select('textcaptcha_answers', 'a')
+    ->fields('a')
+    ->condition('qid', $result['qid'])
+    ->execute();
+
+  $textcaptcha['answers'] = array();
+  while ($answer = $answers->fetchAssoc()) {
+    $textcaptcha['answers'][] = $answer['answer'];
   }
-  return FALSE;
+
+  return $textcaptcha;
 }
 
 /**
-*  Custom CAPTCHA validation callback.
-*/
+ * Custom CAPTCHA validation callback.
+ */
 function textcaptcha_captcha_validate($solution, $response) {
-  if(!unserialize($solution)) {
+  if (!unserialize($solution)) {
     return TRUE;
   }
   return (in_array(md5(drupal_strtolower(trim($response))), unserialize($solution)));
 }
-
-/**
-*  Implementation of hook_requirements()
-*/
-function textcaptcha_requirements($phase) {
-  $requirements = array();
-  if ($phase == 'runtime') {
-    if (variable_get('textcaptcha_api_key', FALSE)) {
-      $requirements['textcaptcha_api_key'] = array(
-        'title' => t('Text CAPTCHA'),
-        'value' => t('API Key updated and server contacted'),
-        'severity' => REQUIREMENT_OK,
-      );
-    }
-    else {
-       $requirements['textcaptcha_api_key'] = array(
-        'title' => t('Text CAPTCHA'),
-        'value' => t('API Key missing. Plese !link for an API key and then update the !settings.',
-                      array('!link' => l('registering at textcaptcha.com',
-                                 'http://textcaptcha.com/register'),
-                            '!settings' => l('Text CAPTCHA settings',
-                               'admin/user/captcha/textcaptcha'))),
-        'severity' => REQUIREMENT_WARNING,
-      );
-    }
-  }
-  return $requirements;
-}
\ No newline at end of file
