// jQuizMe 2.2 by Larry Battle.
//Please give me feedback at blarry@bateru.com
//Copyright (c) 2010 Larry Battle 12/30/2010
//Dual licensed under the MIT and GPL licenses.
//http://www.opensource.org/licenses/mit-license.php
//http://www.gnu.org/licenses/gpl.html
// quizData =[ { ques: "", ans : ""},  {ques: "", ans : ""}, etc ];
// quiz( quizData, options ) # wordsList is an arrary of objects with the following format

(function($){ 
  var _jQuizMeLayOut = $("<div/>").addClass( "quiz-el").append( 
    $("<div/>").addClass( "q-header q-innerArea").append(
      $("<div/>").addClass( "q-counter"),
      $("<div/>").addClass( "q-title")
    ),
    $("<div/>").addClass( "q-help q-innerArea").append( 
      $("<div/>").addClass( "q-help-menu" ).append(
        $( "<span/>" ).addClass( "q-quit-area" ).append(
          $( "<input type ='button'/>" ).addClass( "q-quit-btn" ),
          $( "<input type='button'/>" ).addClass( "q-quitYes-btn q-quit-confirm" ).hide(),
          $( "<input type='button'/>" ).addClass( "q-quitNo-btn q-quit-confirm" ).hide()
        ),
        $("<input type='button'/>").addClass( "q-help-btn" ),                 
        $( "<span/>" ).addClass( "q-timer-area" ),
        $("<div/>").addClass( "q-help-info" ).hide()
      )
    ),
    $("<div/>").addClass( "q-review-menu q-innerArea").append( 
      $("<input type='button'/>").addClass( "q-details-btn q-reviewBar-btns" ),
      $("<input type='button'/>").addClass( "q-review-btn q-reviewBar-btns" ),
      $("<div/>").addClass( "q-reviewBar q-innerArea").append(
        $("<input type='button'/>").attr({ "class": "q-leftArrow q-review-arrows", "value": "<-" }),
        $("<input type='button'/>").attr({ "class": "q-rightArrow q-review-arrows", "value": "->" }),
        $( "<span/>" ).addClass( "q-review-nav" ).append(
          $("<select/>").addClass( "q-review-index-all q-review-index"),
          $("<select/>").addClass( "q-review-index-missed q-review-index").hide(),
          $("<span/>").addClass( "q-missMarker" ).show(),
          $("<input type='checkbox'/>").addClass( "q-showOnlyMissed-btn")
        )
      ).hide()
    ),
    $("<div/>").addClass( "q-intro q-innerArea" ).append(
      $( '<p/>' ).addClass( "q-intro-info" ),
      $( '<input type="button"/>' ).addClass( "q-begin-btn" )
    ).hide(),
    $("<div/>").addClass( "q-prob q-innerArea" ).append( 
      $("<div/>").addClass( "q-ques q-probArea"),
      $("<div/>").addClass( "q-ans q-probArea").append(
          $( "<div/>" ).addClass( "q-ansSel" ),
          $( "<input type='button'/>" ).addClass( "q-check-btn" ),
          $( "<input type='button'/>" ).addClass( "q-next-btn" )
        ),
      $("<div/>").addClass( "q-result q-probArea" ).hide() 
    ),
    $("<div/>").addClass( "q-gameOver q-innerArea" ).append(
      $("<div/>").addClass( "q-stat q-probArea").append(
          $("<span/>").addClass( "q-statTotal q-center"),
          $("<hr/>"),
          $("<blockquote/>").addClass( "q-statDetails"),
          $("<div/>").addClass( "q-extraStat" ) 
      ),
      $("<div/>").addClass( "q-options q-probArea q-center").append( 
        $("<input type='button'/>").addClass( "q-restart-btn" ),
        $("<input type='button'/>").addClass( "q-del-btn" ) 
      )
    ).hide()
  );
  // Note: areArraysSame will fail when there is an object within the array.
  var areArraysSame = function( mainArr, testArr) {
    if ( mainArr.length != testArr.length ){ return false; }
    var i = mainArr.length; 
    
    while( i-- ) {
      if ( ( isArray( mainArr[i] ) && !areArraysSame( mainArr[i], testArr[i] ) ) || mainArr[i] !== testArr[i] ) { 
        return false;
      }
    }
    return true;
  },
  getStrWithSeeableHTML = function( str, allow ) {
    if( !allow || typeof str !== "string" ){ return str; }
    return str.replace( /</g, '&#60;' );
  },
  getStrArrOfSeeableHTML = function( arr, allow ){
    if( !allow ){ return arr; }
    if( isArray( arr ) ){
      var i = arr.length;
      while( i-- ){
        arr[ i ] = getStrWithSeeableHTML( arr[ i ], true );
      }
    }
    return arr;
  },
  getNumRange = function( i ){
    var arr = [];
    while( i-- ){
      arr[ i ] = i;
    }
    return arr;
  },
  getObjProps = function( obj ){
    var keys = [];
    for( var prop in obj ){
      if( obj.hasOwnProperty( prop ) ){
        keys[ keys.length ] = prop;
      }
    } 
    return keys;
  },
  rNum = function( len ){
    return Math.floor( Math.random() * len );
  },
  rBool = function(){
    return ( (rNum( 100 ) % 2) != 1);
  },
  makeArrayRandom = function( arr ){
    var j, x, i = arr.length;
    while( i ){
      j = parseInt(Math.random() * i, 10);
      x = arr[--i]; 
      arr[i] = arr[j];
      arr[j] = x;
    }
    return arr;
  },  
  getArrayOfSwappedArrayElements = function( arr, iArr ){
    if( !isArray( arr ) || !isArray( iArr ) || arr.length !== iArr.length ){
      return -1;
    }
    var x = [], i = arr.length;
    while( i-- ){
      x[ i ] = arr[ iArr[ i ] ];
    }
    return x;
  },
  isArray = function( obj ){
    return Object.prototype.toString.call( obj ) == "[object Array]";
  },
  isMSIE = window.navigator.userAgent.match(/MSIE \d/),
  isBadMSIE = ( isMSIE && isMSIE[0].match(/\d/)[0] < 9 ),
  _lang = {
      ans:{ // _lang.ans are shown during the display of .q-result, the answer result.
        corrAns: "Correct answer(s):",
        praise: 'Great Job. Right!',
        retry: 'Incorrect.<br/>Please try another answer.',
        whyAns: "Info:",
        yourAns: "Your Answer:"
      },
      btn:{ // [ "text", "title" ]
        begin: [ "Begin Quiz" ],
        check: [ "Check", "Check your answer" ],
        del: [ "Delete", "Delete quiz" ],
        help: [ "Help", 'Click for help'],
        next: [ "Next", "Next question" ],
        restart: [ "Restart", "Restart the quit over" ],
        details: [ "Details", "View score report" ],
        review: [ "Review", "Review Questions" ],
        showOnlyMissed: [ " *", "Click to show only missed questions." ],
        quit: [ "Quit", "Quit quiz" ],
        quitNo: [ "->", "Go Back" ],
        quitYes: [ '', "Yes, quit" ]
      },
      err:{
        ansInfoNotDefined: "Property ansInfo must be a string and be defined when ansSelInfo is defined.",
        badAnsSelInfoLen: "Property ansSel and ansSelInfo must be the same type and have the same length if an array.",
        badQType: "Invalid quiz type.",
        badKey: " is an invalid key value.",
        error: "Error", 
        noQType: "No quizTypes.",
        noQues: "Quiz has no questions.",
        notArr: "Must be an array.",
        notObj: "Invalid quiz data structure. Must be an array or object."
      },
      stats:{
        right: "Right",
        rate: "Rate",
        score: "Score",
        wrong: "Wrong",
        total: "Total",
        tried: "Tried"
      },
      quiz:{
        tfEqual: " = ",
        tfEnd : "?",
        tfFalse: "False",
        tfTrue: "True"
      }
  },
  setLangBtnTxt = function(langBtn, layout) {
    var btnCls = [ "begin", "check", "del", "help", "next", "restart", "quit", "quitYes", "quitNo", "review", "details", "showOnlyMissed" ];
    var i = btnCls.length,
    el; 
    // !! Replace showOnlyMissed with an input button value="show only wrong" || "show right"
    // !! Are delete showOnlyMisssed and have * for the missed questions.
    if (!langBtn.quitYes[0]) {
      langBtn.quitYes[0] = langBtn.quit[0] + "?";
    }
    $(".q-missMarker", layout).html(langBtn.showOnlyMissed[0]);
    langBtn.showOnlyMissed[0] = '';
    while (i--) {
      el = [".q-", btnCls[i], "-btn"].join('');
      $(el, layout).attr("value", langBtn[btnCls[i]][0]);
      $(el, layout).attr("title", langBtn[btnCls[i]][1]);
    }
    return layout;
  },
  _settings = {
    addToEnd: "",  // This is attached to fill in the blank quiz types.
    activeClass: "q-ol-active", // Used on multiple choice, (multiOl), quiz types.
    allQuizType: '', // This sets all questions to this quiz type.
    allRandom: false, // Randomizes all the questions, regardless of quiz type section.
    alwaysShowAnsInfo: false, // Shows answer information when correct or incorrect answer are received.
    disableRestart: false, // Hide or show the restrart button on gameOver.
    disableDelete: false, // Hide or show the delete button on gameOver.
    enableRetry: true, // Allows the user to repeat a problem before advancing to next question. 
    fxType: 0, // animateType [ show/hide, fadeToggle, slideToggle, weight&heightToggle ];
    fxCode: false, //If a function, then this is used for animation. Please refer to animateType[] for examples.
    fxSpeed: "normal", // "fast", "slow", "normal" or numbers. 
    help: '', // Provide help text/html if needed.
    hoverClass: "q-ol-hover", // Used on multiple choice, (multiOl), quiz types.
    intro: '', // Provide an text/html intro.
    multiLen : 3, // Set the number of multiple choice choices for quizType multi & multiList.
    numOfQuizQues: 0, // Sets the number of questions asked. Must be between 0 and the total questions.
    performErrorChecking : true, // Check all the quiz formats.
    random: true, // Randomizes all the questions in each quiz type section.
    review: true, // Allows for review of questions at gameOver.
    showFeedback: true, // Show the answers after each question.
    showAnsInfo: true, // If provided, show the answers information after each question.
    showHTML: false, //This will show the HTML, by converting to unicode, rather than render it.
    showWrongAns: false,  // If answer is wrong, then show the user's wrong answer after each question.
    statusUpdate: false, // Sends a status Update. Refer to function sendStatus.
    quizType: "fillInTheBlank", // This is only need if you send in an array and not a object with a defined quiz type.
    title: 'jQuizMe' // title displayed for quiz.
  };
    
  $.fn.jQuizMe = function( wordList, options, userLang ){
  
    var settings = $.extend({},_settings, options),
      lang = $.extend(true, {},_lang, userLang),
      layout = setLangBtnTxt( lang.btn, _jQuizMeLayOut.clone(true));

    return this.each( function(){
      // currQuiz is the output file.(this is what the user sees). $( el, currQuiz) must be used when accessing elements.
      // Hide currQuiz until it's ready to be displayed.
      var currQuiz = layout.clone(true).hide(), currIndex = 0, stickyEl = this, quit = false, totalQuesLen = 0,
      // The q object is where the quiz data, that was made before the quiz starts, is stored here.
      // Please beware that all the questions are stored linearly. But you can find a quiz type by using index[Max|Min].
      q = {
        // Each index is a question. [ question1, question2, question3,... ]. But not for indexMax|Min and props.
        ans: [], // Answers.
        ansSel: [], // Answer selections, the elements for inputting an answer. Ex. input text box, or a select tag.
        ansSelInfo: [], // Used to show specific feedback for ansSel choices.
        ansInfo: [], // Stores the answer information if provided.
        retryCount: [], // Stores the number of retries that a user can have.
        indexMax: [], // The stopping point index for a quiz type inside the q. ans, ansSel, ansInfo and ques.
        indexMin: [], // The starting point index.
        prop:[], // Stores the quiz types names in order. Used for creation of each.
        ques: [] // Questions.
      },
      stats = { 
        indexSelected: [], // indexSelected is only used when ansSelInfo is used.
        problem: {},
        numOfRight: 0, 
        numOfWrong : 0,
        quesTried: 0,
        totalQues: 0,
        accur : function(){
          var x = Math.round( this.numOfRight / this.quesTried * 100 );
          return ( this.quesTried ) ? x : 0;
        },
        accurTxt : function(){
          return [ this.numOfRight, "/", this.quesTried," = ", this.accur(), "%"].join('');
        },
        perc : function(){ 
          var x = Math.round( this.numOfRight / this.totalQues * 100 );
          return ( this.totalQues ) ? x : 0; 
        },
        percTxt : function(){
          return [ this.numOfRight, "/", this.numOfQues, " = ", this.perc(), "%"].join('');
        },
        reset : function(){ 
          this.totalQues = totalQuesLen;
          this.quesTried = 0;
          this.numOfRight = 0;
          this.numOfWrong = 0;
          this.problem = {};
          this.indexSelected = [];
        }
      },
      // rAns(): Returns a random answer from either all the questions, or a curtain quiz type.
      // iProp -> index of the desired quiz type.
      rAns = function( iProp, fromAllQues ){
        var p = ( fromAllQues ) ? q.prop[ rNum( q.prop.length ) ] : q.prop[ iProp ],
          ques = wordList[ p ][ rNum( wordList[ p ].length ) ];

        return ques.ans;
      },
      disableMenuBar = function(){
        $( ".q-quit-btn, .q-help-btn", currQuiz ).attr( "disabled", true );
        $( ".q-help-info", currQuiz ).hide();
      },
      haveCorrAnsWithinRetries = function( isFlashCard ){     
        return ( ((getProblemProp( "amountTried" ) - 1) <= q.retryCount[ currIndex ]) && checkAns( isFlashCard ));
      },
      letUserAnswerAgain = function( isFlashCard ){
        return (settings.enableRetry && getProblemProp( "amountTried" ) < q.retryCount[ currIndex ] && !checkAns( isFlashCard ));
      },
      setCheckBtnToBeBlockedUntilAnsSelIsClicked = function(){
        $( ".q-check-btn", currQuiz ).attr( "disabled", true );
        $( ".userInputArea", currQuiz ).one( "click", function(){
          $( ".q-check-btn", currQuiz ).attr( "disabled", function(){
            return !$( ".q-next-btn", currQuiz ).attr( "disabled" );
          });
        });
      },
      displayRetryMsg = function(){
        var show = lang.ans.retry;
        if( settings.showAnsInfo ){
          show += getAnsInfoForDisplay();
        }
        displayFeedback( show );
      },
      // setCheckAndNextBtn(): Sets the check button to toggle from "check" to "next". Or just displays "next".
      // "check" shows the answer results, but "next" just changes the question.
      setCheckAndNextBtn = function(){
        var isFlashCard = '';
        
        $( ".q-check-btn", currQuiz ).attr( "disabled", true )
          .click( function( e ){
            isFlashCard = $( ".userInputArea", currQuiz ).triggerHandler( "getUserAns" );
            
            getStatsProblem().amountTried++;
            if( getProblemProp( "amountTried" ) == 1){
              stats.quesTried++;
            }
            if( !letUserAnswerAgain( isFlashCard ) ){
              stats[ ( checkAns( isFlashCard ) || isFlashCard ) ? "numOfRight" : "numOfWrong" ]++;
              stats.problem[ currIndex ].isCorrect = checkAns( isFlashCard );
              displayAnsResult( isFlashCard );
              $( ".q-next-btn", currQuiz ).attr( "disabled", false );
              $( e.target ).attr( "disabled", true );
            }
            else{
              setCheckBtnToBeBlockedUntilAnsSelIsClicked();
              displayRetryMsg();
            }
            e.preventDefault();
          }
        );
        $(".q-next-btn", currQuiz ).click( function(e){
          nextMove();
          $( e.target ).attr( "disabled", true );
          e.preventDefault();
        });
      },
      // setQuitBtn(): Provides a quit confirm action.
      setQuitBtn = function(){
        $( ".q-quitYes-btn", currQuiz ).click( function(){ 
          quit = true;
          nextMove();
        });
        $( ".q-quit-confirm", currQuiz ).click( function(){ 
          $( ".q-quit-confirm", currQuiz ).hide(); //.q-quit-confirm calls both the quitYes and quitNo btns.
          $( ".q-quit-btn", currQuiz ).css("display", "inline");
        });
        $( ".q-quit-btn", currQuiz ).click( function( e ){ 
          $( e.target ).hide();
          $( ".q-quit-confirm", currQuiz ).css("display", "inline");
        }); 
      },
      setTotalQuesLen = function(){ 
        var i = q.prop.length, len = 0, numOfQQ = settings.numOfQuizQues;
        
        while( i-- ){
          len += wordList[ q.prop[ i ] ].length; 
        }
        totalQuesLen = ( !numOfQQ || isNaN( numOfQQ ) || numOfQQ > len ) ? len : numOfQQ;
        stats.numOfQues = totalQuesLen;
      },
      // setQuizTypePos(): This stores the position of the beginning and end index of each quiz type.
      // This allows for each quiz type to be pinpointed to in an array.
      setQuizTypePos = function(){
        var i = 0, sum = 0, len = 0;
        
        while( q.prop[ i ] ){ 
          len = wordList[ q.prop[ i ] ].length;
          sum += len; 
          q.indexMax[ i ] = sum - 1;
          q.indexMin[ i ] = sum - len;
          i++;
        }
      },
      // createQuizEl(): Creates new quiz element and sets default button behaviors.
      createQuizEl = function(){
        $( ".q-help-btn", currQuiz).toggle( 
          function(){ animateThis( $( ".q-help-info", currQuiz), 1 ); },
          function(){ animateThis( $( ".q-help-info", currQuiz), 0 ); }
        );
        setTotalQuesLen();
        setQuizTypePos();
        setQuitBtn();
        setCheckAndNextBtn();
        if( settings.review ){ setReviewMenu(); }
        $( stickyEl ).append( currQuiz ); 
      },
      // cont(): Checks whether the questions are done or if quit was called.
      cont = function(){ 
        var result = ( !quit && ( currIndex < totalQuesLen ) ); 
        if( !result ){ quit = true; }
        return result;
      },
      // animateType[]: Stores animation styles to animate an element.
      animateType = [
        function( el, on ){ el[ ( on ) ? "show" : "hide" ]( 0 );},
        function( el, on, spd ){ el[ ( on ) ? "fadeIn" : "fadeOut" ]( spd );},
        function( el, on, spd ){ el[ ( on ) ? "slideDown" : "slideUp" ]( spd );},
        function( el, on, spd ){ 
          el.animate( { "height": "toggle", "width": "toggle" }, spd ); 
        }
      ],
      // animateThis(): Calls animations on an element and/or push a callback function.
      animateThis = function( el, isShown, func ){
        if( isShown > -1 ){ 
          animateType[ settings.fxType ]( el, isShown, settings.fxSpeed ); 
        }
        if( func ){ 
          el.queue( function(){ func(); $(el).dequeue();});
        }
        if( isBadMSIE && (isShown > 0) ){
          $(el).animate( { opacity: 1} ).css( "display", "block" );
        }
      },
      // setReviewNav(): Adds events and functions to change the review questions being viewed.
      // changeCurrIndex() is main point of focus.
      setReviewNav = function(){
        var qReviewOpt = ".q-review-index:visible option:", 
          qReviewOptVal = qReviewOpt + "selected",
          changeCurrIndex = function( i ){
            var max = $( qReviewOpt + "last", currQuiz).val(),
              min = $( qReviewOpt + "first", currQuiz).val(),
              isIndexValid = ( i <= max && i >= min );
              
            if( isIndexValid ){
              $( ".q-review-index", currQuiz).val( i );
              currIndex = i;
              changeProb(true);
              displayAnsResult();
              $( ".q-reviewBar > :disabled", currQuiz).attr( "disabled", false );
            }
            // if a limit was reached. Disable the corresponding arrow.
            if( i == max || i == min ){
              $( ".q-" + (( i == min )?"left":"right") + "Arrow", currQuiz ).attr( "disabled", true );
            }
          };
        $( ".q-review-index", currQuiz).change(function(e){
          changeCurrIndex( parseInt( $(e.target).val(), 10) );
        });
        // Note: The prev||next values of the options tag are used because the list might not be in numerical order.
        $( ".q-review-arrows", currQuiz).click(function( e ){
          var isLeft = !!e.target.className.match( /left/ ), 
            reviewVal = $( qReviewOptVal, currQuiz)[ ( isLeft ) ? "prev":"next" ]().val();
          
          changeCurrIndex( parseInt( reviewVal, 10) ); 
        });
      },
      // setReviewMenu(): Sets the events to change the view when the "review" or "details" button is clicked.
      setReviewMenu = function(){
        var qG = $( ".q-gameOver", currQuiz ), qP = $( ".q-prob", currQuiz );
        
        $( ".q-reviewBar-btns", currQuiz ).click( function( e ){
          var isDetailEl = ( e.target.className.search(/details/) > -1),
            showEl = ( isDetailEl ) ? qG : qP,
            hideEl = ( !isDetailEl ) ? qG : qP;
          
          animateThis( $( ".q-reviewBar", currQuiz), !isDetailEl );
          animateThis( hideEl, 0, function(){
            $( ".q-details-btn", currQuiz ).attr( "disabled", isDetailEl );
            $( ".q-review-btn", currQuiz ).attr( "disabled", !isDetailEl );
            animateThis( showEl, 1 );
          });
        });
        $( ".q-showOnlyMissed-btn", currQuiz ).change( function(){
          var ckd = $(this).is(":checked");
          if( ckd ){
            $( ".q-review-index-missed", currQuiz ).fadeIn().css( "display", "inline" );
            $( ".q-review-index-all", currQuiz ).hide();
          }
          else{
            $( ".q-review-index-missed", currQuiz ).hide();
            $( ".q-review-index-all", currQuiz ).fadeIn().css( "display", "inline" );
          }
          $( ".q-review-index:visible", currQuiz ).trigger( "change" );
        });
        setReviewNav();
      },
      // updateReviewIndex(): Creates the review's select tag html for all and wrong answers.
      updateReviewIndex = function(){
        var allMissed = '', optHtml = '', markAsWrong = '', eachOpt = '',
          i = 0, totalQues = stats.totalQues;
        
        while( totalQues - i ){
          markAsWrong = ( getProblemProp( "isCorrect", i ) ) ? "" : " *";
          eachOpt = "<option value=" + i + ">";
          eachOpt += (i+1);
          eachOpt += markAsWrong;
          eachOpt += "</option>";
          optHtml += eachOpt;
          allMissed += (markAsWrong) ? eachOpt : "";
          i++;
        }
        $( ".q-review-index-all", currQuiz).html( optHtml );
        $( ".q-review-index-missed", currQuiz).html( allMissed );
      },
      // disableGameOverButtons(): If requested, on gameOver hide the restart and/or delete buttons.
      disableGameOverButtons = function(){
        if( settings.disableRestart ){ $( ".q-restart-btn", currQuiz).hide(); }
        if( settings.disableDelete ){ $( ".q-del-btn", currQuiz).hide(); }
        if( settings.disableRestart && settings.disableDelete ){
          $( ".q-options", currQuiz ).hide();
        }
      },
      getUserStatDetailsForDisplay = function(){
        return [
            (lang.stats.right + ": " + stats.numOfRight), 
            (lang.stats.wrong + ": " + stats.numOfWrong),
            (lang.stats.tried + ": " + stats.quesTried), 
            (lang.stats.rate + ": " + stats.accurTxt()), 
            (lang.stats.total + ": " + stats.percTxt())
          ].join('<br/>');
      },
      setupReview = function(){
        updateReviewIndex();
        $( ".q-review-btn", currQuiz ).one( "click", function(){
          $( ".q-review-index", currQuiz).trigger( "change" );
          displayAnsResult(); 
          // This forces the answer info to be displayed for first question.
          if( totalQuesLen == 1 ){
            $( ".q-rightArrow, .q-showOnlyMissed-btn", currQuiz ).attr( "disabled", true );
          }           
        });
        $( ".q-details-btn", currQuiz).attr( "disabled", true );
        $( ".q-review-menu", currQuiz).show();
      },
      // gameOver(): Creates a performance list, set's the events for restart and delete, hides unneed elements,
      // then enables the review-bar.
      gameOver = function(){ 
        disableGameOverButtons();
        $( ".q-statTotal", currQuiz).html( lang.stats.score + ": " + stats.perc() + "%" );
        $( ".q-statDetails", currQuiz).html( getUserStatDetailsForDisplay() );
        $( ".q-restart-btn", currQuiz).one( "click", reStartQuiz );     
        $( ".q-del-btn", currQuiz).one( "click", deleteQuiz );
        $( ".q-help, .q-check-btn, .q-prob, .q-intro, .q-ans", currQuiz).hide();
        if( settings.review ){
          setupReview();
        }
        animateThis( $( ".q-gameOver", currQuiz), 1, sendStatus );
      },
      // changeProb(): Changes to next problem and updates status.
      changeProb = function( isReview ){
        var qAS = q.ansSel;
        $( ".q-counter", currQuiz).text( (currIndex + 1) + '/' + totalQuesLen );
        $( ".q-ques", currQuiz).html( q.ques[ currIndex ] );
        if( !isReview ){ 
          $( ".q-ansSel", currQuiz).html( 
          // Checks for an index pointer; used to clone an identical q.ansSel. This was done to speed up buildQuizType.
            qAS[ isNaN( qAS[currIndex] ) ? currIndex : qAS[currIndex] ].clone(true)
          );
          setQResultDisplay( 0 );
          $( ".q-result", currQuiz ).hide();
          $( ".focusThis", currQuiz ).focus();
          setTimeout( function(){
            $( ".clickThis", currQuiz ).trigger("click"); 
            // IE BUG FIX. The list-style-type won't get rendered until hovered. 
            // Resetting the property will forces it to render. Runs slow though.
            var qOlLiEl = $( ".q-ol-li",  currQuiz );
            if( qOlLiEl.length && isBadMSIE ){
              qOlLiEl.css( "list-style-type", qOlLiEl.css( "list-style-type" ) );
            }
          }, 15);
        }
      },
      setHelpBtn = function(){
        if (settings.help) {
          $(".q-help-info", currQuiz).html(settings.help);
        } else {
          $(".q-help-btn", currQuiz).attr("disabled", true);
        }
      },
      // changeQuizInfo(): Changes the main information for the quiz.
      changeQuizInfo = function(){
        setToBeginningView();
        $( ".q-title", currQuiz).html( settings.title );
        setHelpBtn();
        if( settings.intro ){
          $( ".q-prob", currQuiz ).hide();
          $( ".q-intro-info", currQuiz ).html( settings.intro );
          $( ".q-intro", currQuiz ).show();
          $( ".q-begin-btn", currQuiz ).unbind().one( "click", function(){
            animateThis( $( ".q-intro", currQuiz ), 0, function(){ 
              animateThis( $( ".q-prob", currQuiz ), 1, sendStatus );
            });
          });
        }     
      },
      goToNextProb = function( i ){
        if( i && i < totalQuesLen && (currIndex + 1) < i ){
          currIndex = i;
        }
        changeProb();
      },
      sendStatus = function(){
        if( !settings.statusUpdate ){ 
          return;
        }
        var quizInfo = { 
          "currIndex": currIndex,
          "problem": stats.problem,
          "numOfRight":stats.numOfRight,
          "numOfWrong":stats.numOfWrong,
          "score": stats.perc(),
          "total": stats.totalQues,
          "hasQuit": quit,
          "deleteQuiz": deleteQuiz,
          "quitQuiz": ( !quit ) ? quitQuiz : function(){},
          "nextQuestion": ( !quit ) ? goToNextProb : function(){}
        };
          
        settings.statusUpdate( quizInfo, currQuiz );
      },
      // nextMove(): Decides to change the problem or to quit.
      nextMove = function(){
        currIndex++; // currIndex++ must be first, to find out if there are other questions.
        var nextFunc = (cont() ? changeProb : quitQuiz),
          qEl = $( ".q-prob", currQuiz);
        
        if( !quit ){ animateThis( qEl, 0 ); }
        animateThis( qEl, -1, nextFunc ); //Push the function to the stack, so it's called in between the animations.
        if( !quit ){ animateThis( qEl, 1, sendStatus ); }
      },
      // getAns(): Is used to check the answers. 
      getAns = function( i ){ 
        return q.ans[ i || currIndex ]; 
      },
      getStatsProblem = function( i ){
        i = i || currIndex;
        if( !stats.problem[ i ] ){
          stats.problem[ i ] = { "amountTried": 0, "isCorrect":null, "userAnswer": undefined };
        }
        return stats.problem[ i ];
      },
      getProblemProp = function( prop, i ){
        return getStatsProblem( i )[ prop ];
      },
      setUserAnswer = function( value, i ){
        getStatsProblem( i ).userAnswer = value;
      },
      // checkAns(): Changes the user's answer during the quiz and updates the stats.
      checkAns = function( isFlashCard ){
        var ans = getAns(), isAnsCorr = false, userAns = getProblemProp( "userAnswer" );
        
        if( userAns === undefined ){ return false; }
        if( typeof(ans) === "string"){
          ans = $.trim( ans );
        }
        if( isArray( ans ) ){
          // If one element of the array matches the user's input, then it's correct.
          // This enables multiple answers to be possible.
          isAnsCorr = ( userAns == ans.toString() || ($.inArray( userAns, ans ) + 1));
        }
        else{
          isAnsCorr = ( userAns == ans || userAns.toString().toLowerCase() == ans.toString().toLowerCase() );
        }
        return isAnsCorr;
      },
      getUserAnsForDisplay = function( toUni ){
        var ans = lang.ans.yourAns + "<br/>";

        return ( !getProblemProp( "userAnswer" ) ) ? "<del>" + ans +"</del>" :
              ans + getStrWithSeeableHTML( getProblemProp( "userAnswer" ), toUni );
      },
      getAnsInfoForDisplay = function(){
        if( !q.ansInfo[ currIndex ] ){
          return "";
        }
        if( q.ansSelInfo[ currIndex ] ){
          return "<hr/>" + lang.ans.whyAns + "<br/>" + (q.ansSelInfo[ currIndex ][ stats.indexSelected[ currIndex] ] || q.ansInfo[ currIndex ]);
        }else{
          return "<hr/>" + lang.ans.whyAns + "<br/>" + (q.ansInfo[ currIndex ] );
        }
      },
      //setQResultDisplay(): IE Bug Fix. .q-result pops out when hidden. Thus, show() -> append() and hide() -> remove().
      setQResultDisplay = function( doAppend ){
        if( !isBadMSIE ){ return; }
        if( doAppend ){
          if( !$( ".q-result", currQuiz ).size() ){
            $(".q-prob", currQuiz ).append( $("<div/>").addClass( "q-result q-probArea" ).show() );
          }
        }
        else{
          $( ".q-result", currQuiz ).remove();
        }
      },
      displayFeedback = function( str ){
        setQResultDisplay(1);
        $( ".q-result", currQuiz ).html( str );
        if( !quit ){
          animateThis( $( ".q-result", currQuiz ), 1 );
        }
        else{
          $( ".q-result", currQuiz ).show();        
        }
      },
      // displayAnsResult(): Display's the answer result and information during the quiz.
      displayAnsResult = function( isFlashCard ){
        var currAns = getAns(), isUserAnsCorr = haveCorrAnsWithinRetries( isFlashCard ),
          show = "", toUni = settings.showHTML;
          
        currAns = ( isArray( currAns ) ) ? currAns.concat(): [ currAns ];         
        show = ( isUserAnsCorr ) ? lang.ans.praise :
                ( lang.ans.corrAns + '<br/>' + getStrArrOfSeeableHTML( currAns, toUni ).join( '<br/>' ) );
      
        if( !isUserAnsCorr || quit ){ 
          if( settings.showWrongAns || quit ){
            show = getUserAnsForDisplay( toUni ) + "<hr/>" + show;            
          }
          if( settings.showAnsInfo ){
            show += getAnsInfoForDisplay();
          }
        }else{
          if( settings.alwaysShowAnsInfo ){
            show += getAnsInfoForDisplay();
          }
        }
        if( settings.showFeedback || quit ){ 
          displayFeedback( show );
        }
      },
      hasAnsSel = function( iProp, i ){
        return ( !!wordList[ q.prop[ iProp ] ][ i ].ansSel );
      },  
      rAnsSel = function( iProp, i ){
        var wAS = wordList[ q.prop[ iProp ] ][ i ].ansSel,
          j = rNum( ( isArray( wAS ) ) ? wAS.length : 0 );
        
        return { 
          index: j,
          value: ( isArray( wAS ) ) ? wAS[ j ] : wAS
        };
      },
      releaseButtonBlock = function(){
        if( settings.showFeedback ){
          $( ".q-check-btn", currQuiz ).attr( "disabled", !$( ".q-next-btn", currQuiz ).attr("disabled") );
        }
        else{
          $( ".q-next-btn", currQuiz ).attr( "disabled", false );
        }
        
      },
      setAnsSelInfoSelect = function( quizType ){
        if( !q.ansSelInfo[ currIndex ] ){
          return;
        }
        groupClass = { 
          "multi":".q-select:visible > option",
          "multiList":".q-ol-li:visible",
          "tf":".userInputArea > input"
        }[quizType];
        selectedClass = { 
          "multi":".q-select > option:selected",
          "multiList":"."+settings.activeClass,
          "tf":".userInputArea > input:checked"
        }[quizType];
        stats.indexSelected[ currIndex ] = $( groupClass, currQuiz ).index( $(selectedClass, currQuiz ) );
      },
      // buildQuizType{}: contains all the quiz types.
      //Quiz creation: The q object is filled with the questions, answers and answer selections(what the user can choose from). 
      //The user's input gets assigned to stats.problem[ currIndex ].userAnswer ( global inside quizMe ). 
      //Then nextMove() is called. Which checks to see if the user's input is contained in the answer, or is the array.
      buildQuizType = {
        // fillInTheBlank Quiz: ques = ques, ansSel = Input text[ ans ].
        'fillInTheBlank' : function( iProp, flashVer ){
          var d;
          if( flashVer ){
            d = $( "<span/>" ).addClass( "clickThis userInputArea" ) //Enabled the checkBtn.
              .one( "click", function(){
                releaseButtonBlock();
              })
              .bind( "getUserAns", function(){ return 1; });                
          }
          else{
            d = $( '<input type="text"/>' ).addClass( "q-quesInput focusThis userInputArea" )
              .one( "click keypress", function(){
                releaseButtonBlock();
              })
              .bind( "getUserAns", function(){
                setUserAnswer( $.trim( $( ".q-quesInput", currQuiz ).val() ) );
              });
          }
          var i = q.indexMax[ iProp ], qMin = q.indexMin[ iProp ], wList = wordList[ q.prop[ iProp ] ], numOfQues = wList.length;
          
          while( numOfQues-- ){
            q.retryCount[ i ] = wList[ numOfQues ].retry || 0;
            q.ansInfo[ i ] = wList[ numOfQues ].ansInfo || ""; 
            q.ans[ i ] = wList[ numOfQues ].ans;
            q.ansSel[ i ] = qMin;
            q.ques[ i ] = ( wList[ numOfQues ].ques + settings.addToEnd );
            i--;
          }
          q.ansSel[ qMin ] = d; //speed!! All the q.ansSel area reference to qMin. 
        },
        'flashCard' : function( iProp ){
          // flashCard Quiz:  ques = ques, ansSel =null .
          this.fillInTheBlank( iProp, true);
        },      
        'trueOrFalse' : function( iProp ){
          // trueOrFalse Quiz:
          // ques  = (typeof ans != bool) ? (real or fake ans)  : ques;
          // ansSel =  T / F ( radio );
          // If the answer is a bool, then there is no creation of a question: combining a right or wrong answers, to make a true or false statement.
          
          var d = $( ['<div><input type="radio" value="1" class="true-radio trueRadio"/><label class="trueRadio">', lang.quiz.tfTrue,
                '</label> <input type="radio" value="0" class="false-radio falseRadio"/><label class="falseRadio">', 
                lang.quiz.tfFalse, '</label></div>'].join('') );
          
          $( d ).addClass( "userInputArea" ).bind( "getUserAns", function(){
            var isTrue = $( ".true-radio", this ).attr( "checked" );
            
            setUserAnswer( lang.quiz[ ( isTrue ) ? "tfTrue" : "tfFalse" ] );
            setAnsSelInfoSelect( "tf" );
          });
          $( d ).children().one( "click", function(){
            releaseButtonBlock();
          });
          $(".trueRadio", d).click( function(){
            $( ".true-radio", currQuiz ).attr( "checked", true ); 
            $( ".false-radio", currQuiz ).attr( "checked", false ); 
          });
          $(".falseRadio", d).click( function(){
            $( ".true-radio", currQuiz ).attr( "checked", false ); 
            $( ".false-radio", currQuiz ).attr( "checked", true ); 
          });     
          var currAns, shouldAnsBeCorr = true, i = q.indexMax[ iProp ], 
            qMin = q.indexMin[ iProp ], wList = wordList[ q.prop[ iProp ] ], numOfQues = wList.length,
            toUni = settings.showHTML, tfEqual = lang.quiz.tfEqual, tfEnd = lang.quiz.tfEnd, rAS, cAS, result, isAnsReallyCorrect;

          while( numOfQues-- ){
            rAS = undefined;
            cAS = undefined;
            currAns = wList[ numOfQues ].ans;
            q.ansInfo[ i ] = wList[ numOfQues ].ansInfo || ""; 
            q.ansSel[ i ] = qMin;
            q.retryCount[ i ] = wList[ numOfQues ].retry || 0;
            q.ques[ i ] = wList[ numOfQues ].ques;
            if( typeof currAns != "boolean" ){
              shouldAnsBeCorr = rBool();
              if( shouldAnsBeCorr ){
                result = currAns;
              }else{
                if( hasAnsSel( iProp, numOfQues ) ){
                  rAS = rAnsSel( iProp, numOfQues );
                  result = rAS.value || currAns;
                }
                else{
                  result = rAns(iProp);
                }
              }       
              isAnsReallyCorrect = ( !isArray( currAns ) || !isArray( result ) ) ? result == currAns : areArraysSame( result, currAns );
              q.ans[ i ] = lang.quiz[ ( isAnsReallyCorrect ) ? "tfTrue" : "tfFalse" ];
              q.ques[ i ] += tfEqual;
              q.ques[ i ] += getStrWithSeeableHTML( ( isArray( result ) ) ? result.join( ',' ) : result, toUni );
              q.ques[ i ] += tfEnd;
            }
            else{
              q.ans[ i ] = lang.quiz[ ( currAns ) ? "tfTrue" : "tfFalse" ];
            }
            if( wList[ numOfQues ].ansSelInfo ){
              cAS = wList[ numOfQues ].ansSelInfo.concat();
              if( cAS && rAS ){
                cAS = ( isArray( cAS ) ) ? cAS : [ cAS ];
                var qAI = q.ansInfo[ i ];
                q.ansSelInfo[ i ] = ( shouldAnsBeCorr ) ? [ qAI, cAS[ rAS.index ] || qAI ] : [ cAS[ rAS.index ] || qAI, q.ansInfo[ i ] ];
              }
            }
            i--;
          }
          q.ansSel[ qMin ] = d;
        },
        'multipleChoice' : function( iProp, olVer ){
          // multipleChoice Quiz: 
          // ques = ques;
          // ansSel = <select>[ <option> is the answer + <option> * settings.multiLen]
            var d;
            if( olVer ){
              d = $( '<div><ol class="q-ol"></ol></div>' );
              $( d ).one( "click", function(){
                releaseButtonBlock();
              });
            }
            else{
              d = $( '<div><select class="q-select"></select></div>' );
              $( ".q-select", d ).one( "click keypress", function( e ){
                releaseButtonBlock();
              });
            }
            $( d ).addClass( "userInputArea" ).bind( "getUserAns", function(){    
              if( olVer ){ 
                var elHtml = $("." + settings.activeClass, currQuiz).find(".q-ol-li-ansSel")[ settings.showHTML ? "text": "html" ]();
                setUserAnswer( $.trim(elHtml) );
                setAnsSelInfoSelect( "multiList" );
              }
              else{
                setUserAnswer( $( "option:selected", this ).text() );
                setAnsSelInfoSelect( "multi" );
              }
            });
            
            var i = q.indexMax[ iProp ], wList = wordList[ q.prop[ iProp ] ], ansPos, optHtml, val, len,
              j = wList.length, wListRange = getNumRange( j ), numOfQues = wList.length,
//Checks to see, if the request multiLen length can be supported.
              setLen = ( settings.multiLen > j ) ? j : settings.multiLen,
              toUni = settings.showHTML, hasAnsPosition = false, ansSelChoice = "", range, cASI, cProb, ansIndex, x; 
              
            var addClassFunc = function( e ){ 
                $( e.target ).closest( "li.q-ol-li" ).addClass( settings.hoverClass );
            }, 
            removeClassFunc = function( e ){
              $( e.target ).closest( "li.q-ol-li" ).removeClass( settings.hoverClass );
            },
            clickFunc = function( e ){          
              var qOL = $(e.target).closest("li.q-ol-li");
              qOL.addClass(settings.activeClass).siblings().removeClass(settings.activeClass).find(".q-radioBtn:checked").attr("checked", false);
              qOL.find(".q-radioBtn").attr("checked", true);
            };
              
          while (numOfQues--) {
            cProb = wList[numOfQues];
            optHtml = "";
            hasAnsPosition = false;
            cASI = cProb.ansSelInfo;
            cASI = ( cASI ) ? ( ( isArray( cASI ) ) ? cASI.concat() : [ cASI ] ) : undefined;
            if (hasAnsSel(iProp, numOfQues)) {
              val = [];
              if ( isArray( cProb.ansSel ) ) {
                var cAS = cProb.ansSel,
                  k = cAS.length;
                  
                while ( k-- ) {
                  if (cAS[ k ] === null) {
                    val[ k ] = cProb.ans;
                    if( cASI ){
                      cASI.splice( k, 1, cProb.ansInfo );
                    }
                    hasAnsPosition = true;
                  } else {
                    val[ k ] = cAS[k];
                  }
                }
              } else {
                val[0] = cProb.ansSel;
              }             
              if (!hasAnsPosition) {
                val[val.length] = cProb.ans;
                if( cASI ){
                  cASI[ cASI.length ] = cProb.ansInfo;
                }
              }
              len = val.length;
              range = getNumRange( len );
              if( !hasAnsPosition ){
                makeArrayRandom( range );
                if( cASI ){
                  cASI = getArrayOfSwappedArrayElements( cASI, range );
                }
              }
              while (len--) {
                ansSelChoice = getStrWithSeeableHTML( val[ range[ len ] ], toUni );
                if( cASI ){
                  cASI[ len ] = getStrWithSeeableHTML( cASI[ len ], toUni );
                }
                x = (olVer) ? ["<li class = 'q-ol-li'><input type='radio' class='q-radioBtn'/><span class='q-ol-li-ansSel'>", ansSelChoice, "</span></li> "].join("") : ["<option>", ansSelChoice, "</option> "].join("");
                optHtml = x + optHtml;
              }
            } else {
              var randAns = wListRange.concat(); 
              // Get rids of the answer Index from randAns. 
              randAns.splice(numOfQues, 1);
              
              len = setLen;
              ansPos = rNum(len);
              while (len--) {
                var randIndex = rNum(randAns.length),
                ansSelIndex = (len == ansPos) ? numOfQues: (randAns.splice(randIndex, 1) || 0);
                ansSelChoice = getStrWithSeeableHTML(wList[ansSelIndex].ans, toUni);
                x = (olVer) ? ["<li class = 'q-ol-li'><input type='radio' class='q-radioBtn'/><span class='q-ol-li-ansSel'>", ansSelChoice, "</span></li> "].join("") : ["<option>", ansSelChoice, "</option> "].join("");
                optHtml = x + optHtml;
              }
            }
            // below: innerHTML is 2x faster than $.html() but IE generates corrrupt option html
            if (isBadMSIE && !olVer) {
              $("select", d).html(optHtml);
            } else {
              $((olVer) ? ".q-ol": "select", d)[0].innerHTML = optHtml;
            }
            if (isBadMSIE && olVer) { 
            //Fixes IE CSS BUG. No spaces with list-style-position: inline.
              $(".q-ol-li", d).prepend("&nbsp;");
            }
            if( olVer ){
              $( "li.q-ol-li", d ).bind( "click", clickFunc )
                .bind( "mouseover", addClassFunc )
                .bind( "mouseout", removeClassFunc );
            }
            q.retryCount[i] = cProb.retry || 0;
            q.ans[i] = cProb.ans;
            q.ansInfo[i] = cProb.ansInfo || "";
            q.ansSel[i] = d.clone(true);
            q.ansSelInfo[ i ] = ( cASI ) ? cASI.concat() : cASI;
            q.ques[i] = cProb.ques;
            i--;
          } 
        },
        'multipleChoiceOl' : function( iProp ){
          // multipleChoiceOlQuiz: ques = ques, ansSel = ol [ <list> real ans + <list> * settings.multipleChooseLength] 
          this.multipleChoice( iProp, true);
        }
      },
      // makeQuizRandom(): If randomizeQ is false, it will randomize the each quiz type. 
      // But if randomizeQ is true, it will randomize the q object.
      makeQuizRandom = function( randomizeQ ){
        if( randomizeQ ){
          var numArr = getNumRange( totalQuesLen ), i = numArr.length,
            temp = { ans: [], ansSel: [], ansInfo: [], ques: [] }, nArrI, qAS;
          
          makeArrayRandom( numArr );
          i = numArr.length;

          while( i-- ){
            nArrI = numArr[ i ];
            qAS = q.ansSel[ nArrI ];
            
            temp.ans[i] = q.ans[ nArrI ];
            temp.ansInfo[i] = q.ansInfo[ nArrI ] || "";
            temp.ansSel[i] = ( typeof qAS !== "number" ) ? qAS : q.ansSel[ qAS ];
            temp.ques[i] = q.ques[ nArrI ];
          }
          q.ans = temp.ans.concat();
          q.ansInfo = temp.ansInfo.concat();
          q.ansSel = temp.ansSel.concat();          
          q.ques = temp.ques.concat();
        }
        else{       
          var iProp = q.prop.length;
          while( iProp-- ){
            makeArrayRandom( wordList[ q.prop[ iProp ] ] ); 
          }         
        }
      },
      makeQuizType = function(){ 
      //iProp is a pointer to the quizType min and max index.
        var iProp = q.prop.length, currProp = "", p, allP = settings.allQuizType,
          shortProp = {
            fill:"fillInTheBlank", cards:"flashCard", tf:"trueOrFalse", multi:"multipleChoice", multiList:"multipleChoiceOl"
          };
          
        if( settings.random ){ makeQuizRandom();}
        while( iProp-- ){
          p = allP || q.prop[ iProp ];
          currProp = shortProp[ p ] || p;
          buildQuizType[ currProp ]( iProp );
        }
        if( settings.allRandom ){ 
          makeQuizRandom(true);
        }
      },
      // Creates and shows the new quiz.
      createNewQuizProb = function(){
        stats.reset();
        makeQuizType();
        changeQuizInfo();
        changeProb();
        animateThis( $( currQuiz ), 1, sendStatus );
      },
      quitQuiz = function(){
        currIndex = 0; //Used to make updateReviewIndex start at 0.
        quit = true; 
        disableMenuBar();
        gameOver();       
      },
      deleteQuiz = function(){
        animateThis( $( currQuiz ), 0, function(){
          $( currQuiz ).remove();
        });
        q = {};
      },
      setToBeginningView = function(){
        $( ".q-gameOver, .q-result, .q-review-menu", currQuiz).hide();
        $( ".q-ans, .q-help, .q-help-menu, .q-prob", currQuiz ).show();
        $( ".q-check-btn", currQuiz )[ ( settings.showFeedback)?"show":"hide"]().attr( "disabled", true );
        $( ".q-next-btn", currQuiz ).attr( "disabled", settings.showFeedback );
        $( ".q-quit-btn, .q-help-btn", currQuiz ).attr( "disabled", false );
      },
      reStartQuiz = function(){
        //IE likes to hide on restart.
        if( !isBadMSIE || ( isBadMSIE && settings.fxType == 3 ) ){
          animateThis( $( currQuiz ), 0 );
        }
        setToBeginningView();
        quit = false;
        currIndex = 0;
        createNewQuizProb();
      },
      setAnimation = function( s ){
        var spd = s.fxSpeed, spdType = { slow: 600, fast: 200, normal: 400 };
        
        s.fxSpeed = isNaN( spd ) ? ( spdType[ spd ] || 400 ) : spd;
        if( s.fxCode ){
          var custAnimatePos = animateType.length;
          animateType[ custAnimatePos ] = s.fxCode;
          s.fxType = custAnimatePos;
        }
      },
      checkAnsSelInfo = function( ansInfo, ansSel, ansSelInfo ){
        var a = isArray( ansSel ), b = isArray( ansSelInfo );
        
        if( ansInfo === undefined ){
          return lang.err.ansInfoNotDefined;
        }
        if( ( a || b ) && (a != b || ansSel.length !== ansSelInfo.length)){
          return lang.err.badAnsSelInfoLen;
        }
      },
      // checkQTypeKeys(): Checks the keys for each question, wordList[i]. Returns error if unknown.
      checkQTypeKeys = function( quesKeys ){
        var badKey = "", i = quesKeys.length, temp,
          validKeys = { ques:1, ans:1, ansInfo:1, ansSel:1, ansSelInfo:1, retry:1 };
        
        if( !i ){ badKey = lang.err.noQues; }
        while( i-- ){
          if( badKey ){ break;}
          temp = quesKeys[ i ];
          for( var currKey in temp ){
            if( temp.hasOwnProperty( currKey ) ){
              if( !validKeys[ currKey ] ){
                badKey = currKey + lang.err.badKey;
                break;
              }
              if( currKey == "ansSelInfo" ){
                badKey = checkAnsSelInfo( temp.ansInfo || undefined, temp.ansSel || undefined, temp.ansSelInfo );
                break;
              }
            }
          }
        }
        return badKey;
      },
      hasBadSyntax = function( wNames ){
        var badKey = "", goodKeys = [],
          validKeys = { 
            fillInTheBlank:1, flashCard:1, trueOrFalse:1, multipleChoice:1, multipleChoiceOl:1,
            fill:1, cards:1, tf:1, multi:1, multiList:1
          };
        
        for( var prop in wNames ){
          if( wNames.hasOwnProperty( prop ) ){
            if( !validKeys[ prop ] ){
              badKey = lang.err[ ( typeof wNames === "object" ) ? "badQType" : "lang.err.notObj" ];
            }
            if( !badKey ){
              badKey = ( isArray( wNames[prop] ) ) ? checkQTypeKeys( wNames[prop] ) : lang.err.notArr;
            }
            if( badKey ){ 
              badKey = prop + " -> " + badKey;
              break; 
            }
          }
        }
        
        if( !prop ){ badKey = lang.err.noQType; }
        if( !badKey && settings.allQuizType && !validKeys[ settings.allQuizType ] ){ 
          badKey = settings.allQuizType + " -> " + lang.err.badQType; 
        }
        return badKey;
      },
      checkForErrors = function( obj ){
        var err = ( obj ) ? hasBadSyntax( obj ) : lang.err.noQues;
        if( err ){ 
          err = "jQuizMe " + lang.err.error + ": " + err;
          $( stickyEl ).text( err );
          throw new Error( err );
        }
      },
      convertArrToObj = function( obj ){
        var tempObj = {};
        tempObj[ settings.allQuizType || settings.quizType ] = obj;
        return tempObj;
      },
      // start(): This is the first function called.
      start = function(){
        wordList = ( isArray( wordList ) ) ? convertArrToObj( wordList ) : wordList;
        if( settings.performErrorChecking ){
          checkForErrors( wordList );
        }   
        setAnimation( settings );
        q.prop = getObjProps( wordList );
        createQuizEl();
        createNewQuizProb();
        return currQuiz;
      }();  // Auto starts.
    });
  };
  $.fn.jQuizMe.version = "2.2";
})(jQuery);
