/*
 * Copyright (c) 2005 Absolutely Training Limited
 * 
 * Created on 01-Dec-2005
 */
 
/* JSLint global function comment */
/*global MultipleChoiceQuestion, TrueFalseQuestion, RomanNumeralQuestion,
  AnswerChoice, TestUtils */ 
 

/**
 * Translation of the Java TestQuestionFactory class.
 * Factory utility class for the creation of {@link TestQuestion} objects with answer combinations for multiple
 * choice, true false and roman numeral questions
 * 
 * @author paulb
 */
function TestQuestionFactory() {}

TestQuestionFactory.QUESTION_AMOUNT = 4;

TestQuestionFactory.createMultipleChoice = function(origQuestion) {
    if ((origQuestion.getAlwaysPresentCorrectAnswers().length +
         origQuestion.getCorrectAnswers().length) == 0) {
        throw new Error( "Multiple-Choice question needs at least one correct answer: " + origQuestion);
    }
    if ((origQuestion.getAlwaysPresentIncorrectAnswers().length +
            origQuestion.getIncorrectAnswers().length) == 0) {
        throw new Error( "Multiple-Choice question needs at least one incorrect answer: " + origQuestion);
    }
    if (origQuestion.getAnswers().length == 0) {
        throw new Error("Question has no answers associated with it " + origQuestion);
    }
    // defensive copy of question, because question creation algorithm modifies answers
    // collections.
    var question = origQuestion.copy();
    var answers = TestQuestionFactory.generateAnswersMultipleChoice(question, 1, TestQuestionFactory.QUESTION_AMOUNT - 1);
    TestQuestionFactory.shuffle(answers);
    var mcQuestion = new MultipleChoiceQuestion(question, answers);
    mcQuestion.generateAnswerChoices();
    return mcQuestion;
};

TestQuestionFactory.createTrueFalse = function(origQuestion) {
    if (origQuestion.getAnswers().length == 0) {
        throw new Error("Question has no answers associated with it " + origQuestion);
    }
    // defensive copy of question, because question creation algorithm modifies answers
    // collections.
    var question = origQuestion.copy();
    var answers = TestQuestionFactory.generateAnswersRomanAndTrueFalse(question, TestQuestionFactory.QUESTION_AMOUNT);
    TestQuestionFactory.shuffle(answers);
    var tfQuestion = new TrueFalseQuestion(question, answers);
    tfQuestion.generateAnswerChoices();
    return tfQuestion;
};

TestQuestionFactory.createRomanNumeral = function(origQuestion) {
    if (origQuestion.getAlwaysPresentCorrectAnswers().length +
            origQuestion.getCorrectAnswers().length == 0) {
        throw new Error("Roman numeral question needs at least one correct answer: " + origQuestion);
    }
    if (origQuestion.getAnswers().length == 0) {
        throw new Error("Question has no answers associated with it " + origQuestion);
    }
    // defensive copy of question, because question creation algorithm modifies answers
    // collections.
    var question = origQuestion.copy();
    var answers = TestQuestionFactory.generateAnswersRomanAndTrueFalse(question, TestQuestionFactory.QUESTION_AMOUNT);
    TestQuestionFactory.shuffle(answers);
    var rnQuestion = new RomanNumeralQuestion(question, answers);
    var answerChoices = TestQuestionFactory.generateAnswerChoicesForRomanNumerals( rnQuestion.getPossibleAnswers() );
    rnQuestion.setAnswerChoices(answerChoices);
    rnQuestion.setCorrectChoices();
    return rnQuestion;
};

/**
 * Randomly creates multiple-choice, true false or roman numeral question
 */
TestQuestionFactory.createRandomQuestion = function(question) {
    var random = Math.floor( Math.random() * 3 );

    switch (random) {
        case 0:
            return TestQuestionFactory.createMultipleChoice(question);
        case 1:
            return TestQuestionFactory.createRomanNumeral(question);
        default:
            return TestQuestionFactory.createTrueFalse(question);
    }
};

/**
 * Generates a list of answers for test questions. The list contains all answers that are set to
 * always-present, plus additional randomly selected answers to make up a total answer number
 * specified by <code>amountOfCorrectAnswers</code> and <code>amountOfIncorrectAnswers</code>.
 * If amount of available answers is not sufficient to make up the specified amount, the method
 * just returns the available answers.
 * 
 * @param question content store question, which test question is based on.
 * @param amountOfCorrectAnswers maximum amount of correct answer
 * @param amountOfIncorrectAnswers maximum amount of incorrect answers
 * @return list of answer objects.
 */
TestQuestionFactory.generateAnswersMultipleChoice = function(question, amountOfCorrectAnswers, amountOfIncorrectAnswers)  {

    if (question.getAnswers().length == 0) {
        throw new Error("Question does not contain any answers: " + question);
    }

    var allAnswers = [];
    var rightAnswersPresent = question.getAlwaysPresentCorrectAnswers();
    var wrongAnswersPresent = question.getAlwaysPresentIncorrectAnswers();
    var otherRightAnswers = question.getCorrectAnswers();
    var otherWrongAnswers = question.getIncorrectAnswers();

    /*
     * Set the number of right answers and wrong answers to pick
     */
    allAnswers = allAnswers.concat( rightAnswersPresent );
    amountOfCorrectAnswers -= rightAnswersPresent.length;
    allAnswers = allAnswers.concat( wrongAnswersPresent );
    amountOfIncorrectAnswers -= wrongAnswersPresent.length;

    /*
     * Create combinations based on not always present outcomes
     */
    // NOTE this is simplified from the Java version as there is no possibility (in either version)
    // of selecting an answer that is already in allAnswers
    for ( var i = 0; i < amountOfIncorrectAnswers && otherWrongAnswers.length > 0; i++) {
        var answerIndex = Math.floor( (Math.random() * otherWrongAnswers.length) );
        allAnswers.push( otherWrongAnswers[ answerIndex ] );
        otherWrongAnswers.splice( answerIndex, 1 );
    }

    for ( i = 0; i < amountOfCorrectAnswers && otherRightAnswers.length > 0; i++) {
        answerIndex = Math.floor( (Math.random() * otherRightAnswers.length) );
        allAnswers.push( otherRightAnswers[ answerIndex ] );
        otherRightAnswers.splice( answerIndex, 1 );
    }

    return allAnswers;
};

TestQuestionFactory.generateAnswersRomanAndTrueFalse = function(question, amountOfAnswers) {

    if (question.getAnswers().length == 0) {
        throw new Error("Question does not contain any answers: " + question);
    }

    var	allAnswers = [];
    var rightAnswersPresent = question.getAlwaysPresentCorrectAnswers();
    var wrongAnswersPresent = question.getAlwaysPresentIncorrectAnswers();
    var otherRightAnswers = question.getCorrectAnswers();
    var otherWrongAnswers = question.getIncorrectAnswers();

	allAnswers = allAnswers.concat( rightAnswersPresent );
	allAnswers = allAnswers.concat( wrongAnswersPresent );
    var amountOfIncorrectAnswers = 0;
    var amountOfCorrectAnswers = 0;

    if (otherRightAnswers.length == 0 && rightAnswersPresent.length == 0) {
        throw new Error("Roman numeral question has no correct questions " + question);
    }

    while ((amountOfIncorrectAnswers + amountOfCorrectAnswers + allAnswers.length) < amountOfAnswers) {
        if (amountOfIncorrectAnswers >= otherWrongAnswers.length && 
        		amountOfCorrectAnswers < otherRightAnswers.length) {
            amountOfCorrectAnswers++;
        } else if (amountOfCorrectAnswers >= otherRightAnswers.length &&
                	amountOfIncorrectAnswers < otherWrongAnswers.length) {
            amountOfIncorrectAnswers++;
        } else {
            if ( Math.random() >= 0.5 ) {
                amountOfIncorrectAnswers++;
            } else {
                amountOfCorrectAnswers++;
            }
        }
    }

	if (amountOfCorrectAnswers==0){
		amountOfCorrectAnswers=1;
	}
	if (amountOfIncorrectAnswers==amountOfAnswers)
	{
		amountOfIncorrectAnswers--;
	}

    for ( var i = 0; i < amountOfIncorrectAnswers && otherWrongAnswers.length > 0; i++) {
        var answerIndex = Math.floor( (Math.random() * otherWrongAnswers.length) );
        allAnswers.push( otherWrongAnswers[ answerIndex ] );
        otherWrongAnswers.splice( answerIndex, 1 );
    }

    for ( i = 0; i < amountOfCorrectAnswers && otherRightAnswers.length > 0; i++) {
        answerIndex = Math.floor( (Math.random() * otherRightAnswers.length) );
        allAnswers.push( otherRightAnswers[ answerIndex ] );
        otherRightAnswers.splice( answerIndex, 1 );
    }

    return allAnswers;
};

TestQuestionFactory.generateAnswerChoicesForRomanNumerals = function(answers) {

    var answerChoices = [];

    /* combination Number */
    var combNums = {};

    /*
     * convert the answer Vector to a Binary style vector, if the vector does not contain four
     * items, add a '0'
     */
    var correct = 0;

    for (var i = 0; i < answers.length; i++) {
    	if ( answers[i].isCorrect() ) {
    		correct += Math.pow(2,i);
    	}
    }

    /* add the correct combination to the combinations vector */
    combNums[ correct ] = true;
    var choiceStr = TestQuestionFactory.binaryToString(correct);
    var choice = new AnswerChoice(choiceStr, true, "");
    choice.setTestAnswers( TestQuestionFactory.selectFromBinary( answers, correct ) );
    answerChoices.push( choice );

    /* common option usage */
    var commonUsage = correct;
    var exclusion = correct;
    var r = 0;

    /* get three wrong combinations */
    for ( i = 0; i < 3; i++) {

        do {
            r = Math.floor( Math.random() * 14) + 1;
        } while ( combNums[r] );

        var oldCommonUsage = commonUsage;
        commonUsage = r | commonUsage;
        var oldExp = exclusion;
        exclusion = r & exclusion;

        while ((i == 2) && ((commonUsage < 15) || (exclusion > 0))) {
            if (commonUsage < 15) {
                r = (r | (15 - commonUsage)) ^ exclusion;
                commonUsage = r | commonUsage;
            } else {
                for (; exclusion > 0; exclusion = (r & oldExp)) {
                    commonUsage = oldCommonUsage;
                    r = Math.floor( Math.random() * 14) + 1;
                }
                commonUsage = r | commonUsage;
            }
        }
        combNums[r] = true;
    	choiceStr = TestQuestionFactory.binaryToString(r);
        choice = new AnswerChoice(choiceStr, false, "");
    	choice.setTestAnswers( TestQuestionFactory.selectFromBinary( answers, r ) );
        answerChoices.push(choice);
    }

    TestQuestionFactory.shuffle(answerChoices);
    for ( i = 0; i < answerChoices.length; i++) {
        choice = answerChoices[i];
        choice.setLetter(TestUtils.getLetter(i));
    }
    return answerChoices;
};

TestQuestionFactory.binaryToString = function(binNumber) {
    var binString = "";
    var count = 0;
    while (binNumber > 0) {
        if (binNumber & 1) {
            if (binString.length > 0) {
                binString = binString + ", ";
            }
            binString = binString + TestUtils.getNumeral(count);
        }
        binNumber = binNumber >> 1;  // divide by 2
        count++;
    }
    return binString;
};

TestQuestionFactory.selectFromBinary = function(arr, binNumber) {
    var result = [];
    var count = 0;
    while (binNumber > 0) {
        if (binNumber & 1) {
        	var element = arr[count];
            result.push(element);
        }
        binNumber = binNumber >> 1;  // divide by 2
        count++;
    }
    return result;
};

TestQuestionFactory.shuffle = function(list) {
    for (var i=list.length; i > 1; i--) {
    	var swapPos = Math.floor( Math.random() * i );
    	var temp = list[i - 1];
    	list[i - 1] = list[swapPos];
    	list[swapPos] = temp;
    }
};

