
/**
 * Client side component of AVERT's quiz application.
 *
 * @author Karl Rixon karl.rixon@gmail.com
 */

/**
 * This quiz class encapsulates all of the functionality required to manage the
 * quiz. The object will self-populate its data via an Ajax call using the
 * provided quizId. The structure of the object's data property after population
 * is:
 * quiz object
 *   quiz data
 *   levels array
 *     level object n
 *       level data
 *       questions array
 *         question object n
 *           question data
 *           answers array
 *             answer object n
 *               answer data
 * So groups of levels, questions and answers are array properties of the parent
 * object, ie questions is an array property of the levels object, which in turn
 * is an array property of the quiz object.
 */
function Quiz(quizId) {
    
    this.init = function() {
        this.loadData();
    }
    
    /**
     * Populates the object with all the necessary data for the current quiz.
     */
    this.loadData = function() {
        $.post(
            '/apps/quiz/callbacks.php',
            {'function' : 'getData', 'quizId' : this.quizId},
            function(data) {
                self.onDataLoaded(data);
            }
            ,'json'
        );
    }
    
    /**
     * Callback fired when a quiz's data has been retrieved from the server.
     */
    this.onDataLoaded = function(data) {
        this.data = data.data;
        this.playerId = data.playerId;
        // fire the attached listener function
        this.dataLoadedListener(data.status);
    }
    
    /**
     * Updates the display area with the first question from the selected level.
     *
     * @param levelId {integer} The id of the level which was selected.
     * @return void
     */
    this.selectLevel = function(levelId) {
        this.currentLevelIndex = parseInt(levelId);
        this.currentLevel = this.data.levels[this.currentLevelIndex];
    }
    
    this.getNextQuestion = function() {
        this.currentQuestionIndex++;
        this.currentQuestion = this.currentLevel.questions[this.currentQuestionIndex];
        return this.currentQuestion;
    }
    
    this.hasNextQuestion = function() {
        return (this.currentLevel.questions[this.currentQuestionIndex + 1] != undefined);
    }
    
    /**
     * Logs the user's answer to the current question
     */
    this.markCurrentQuestion = function(answer) {
        this.currentQuestion.userCorrect = answer;
        var link = {
            url: this.currentQuestion.webpage_url,
            title: this.currentQuestion.webpage_title
        }
        var inArray = false;
        if (answer == false) {
            // add a new suggested link
            for (index in this.suggestedLinks) {
                if (this.suggestedLinks[index].url == link.url) {
                    inArray = true;
                    break;
                }
            }
            if (!inArray) this.suggestedLinks.push(link);
        } else {
            // add a new general link
            for (index in this.generalLinks) {
                if (this.generalLinks[index].url == link.url) {
                    inArray = true;
                    break;
                }
            }
            if (!inArray) this.generalLinks.push(link);
        }
    }
    
    /**
     * Counts the number of levels in the quiz. Since this value does not change,
     * the count is cached after being computed for the first time, so subsequent
     * calls to the method incur as little overhead as possible.
     */
    this.getNumberOfLevels = function() {
        if (this.levelCount === undefined) {
            this.levelCount = 0;
            for (k in this.data.levels) {
                if (this.data.levels.hasOwnProperty(k)) {
                    this.levelCount++;
                }
            }
        }
        return this.levelCount;
    }
    
    this.getNumberOfQuestions = function() {
        if (this.currentLevel == null) {
            return undefined;
        }
        if (this.questionCount === undefined) {
            this.questionCount = 0;
            for (k in this.currentLevel.questions) {
                if (this.currentLevel.questions.hasOwnProperty(k)) {
                    this.questionCount++;
                }
            }
        }
        return this.questionCount;
    }
    
    /**
     * Selects numberOfLinks urls and titles from the questions which were answered
     * incorrectly.
     */
    this.getSuggestedLinks = function(numberOfLinks) {
        return this.randomArray(this.suggestedLinks, numberOfLinks);
    }
    
    /**
     * Returns numberOfLinks links at random from all of the questions's links
     * (regardless of whether the user got the question right or not)
     */
    this.getGeneralLinks = function(numberOfLinks) {
        return this.randomArray(this.generalLinks, numberOfLinks);
    }
    
    /**
     * Extracts numberOfElements elements from the provied array. If there are less elements
     * than requested, returns the entire array.
     */
    this.randomArray = function(array, numberOfElements) {
        if (array.length <= numberOfElements) {
            return array;
        }
        var random = new Array;
        for (i=0; i<numberOfElements; i++) {
            var index = Math.floor(Math.random() * array.length);
            random.push(array[index]);
            array.splice(index, 1);
        }
        return random;
    }
    
    this.attachDataLoadedListener = function(fn) {
        this.dataLoadedListener = fn;
    }
    
    // Stores a reference to the object's instance for use inside sub-functions.
    var self = this;
    
    // Object properties
    this.quizId = quizId;
    this.playerId;
    this.currentLevel;
    this.currentLevelIndex;
    this.currentQuestion;
    this.currentQuestionIndex = -1;
    this.suggestedLinks = new Array;
    this.generalLinks = new Array;
    
    // Get the ball rolling
    this.init();
    
}

/**
 * Global variable to hold the quiz object.
 */
var quiz;
/**
 * The speed of transition fade effect
 */
var transitionSpeed = 350;
/**
 * The user's current score
 */
var currentScore = 0;

$(document).ready(function() {
    showSpinner();
    quiz = new Quiz(1);
    quiz.attachDataLoadedListener(quizLoadedListener);
});

/**
 * Shows the loading message and spinner graphic.
 */
function showSpinner() {
    $("#quizLoading").show();
}

/**
 * Hides the loading message and spinner graphic.
 */
function hideSpinner() {
    $("#quizLoading").fadeOut(transitionSpeed);
}

/**
 * Called automatically when the quiz's data has loaded.
 *
 * @param success {boolean} Indicates if the loading of the data was succesful or not
 * @return void
 */
function quizLoadedListener(success) {
    if (success == true) {
        showTitle();
        showLevelSelection();
    } else {
        alert("failed");
    }
    hideSpinner();
}

function showTitle() {
    $("#quizContainer h2").text(quiz.data.title).fadeIn(transitionSpeed);
}

/**
 * Displays the quiz level selection screen. Provides a button for each level in
 * the current quiz.
 */
function showLevelSelection() {
    $("#quizDynamicContent").html(getLevelSelectionHtml()).fadeIn(transitionSpeed);
    // define listeners for the button click events
    $("button[id^='level-']").click(function(event) {
        onLevelSelect(this, event);
    });
}

/**
 * Displays the next question.
 */
function showNextQuestion() {
    $("#quizDynamicContent").html(getQuestionHtml()).fadeIn(transitionSpeed);
    // define listeners for the button click events
    $("input[id^='answer-']").click(function(event) {
        onAnswerSelect(this, event);
    });
    if (quiz.hasNextQuestion()) {
        $('#quiz_next_question').click(function(event) {
            onNextQuestionClicked(this, event);
        });
    } else {
        $('#quiz_finish').click(function(event) {
            onFinishClicked(this, event);
        });
    }
}

function showResults() {
    $("#quizDynamicContent").html(getResultsHtml()).fadeIn(transitionSpeed);
}

/**
 * Called after a user selects a level. Initializes the quiz and displays the
 * first question.
 */
function onLevelSelect(elem, event) {
    quiz.selectLevel(elem.id.replace('level-', ''));
    $("#quizDynamicContent").fadeOut(transitionSpeed, function() {
        showNextQuestion();
        $("#quizContainer").append(getScoreBarHtml());
        $("#quizScore").fadeIn(transitionSpeed);
    });
    event.preventDefault();
}

/**
 * Called after a user selects an answer.
 */
function onAnswerSelect(elem, event) {
    var answerId = elem.id.replace('answer-', '');
    var correct = false;
    // disable all answer checkboxes so the user cannot re-select
    $("input[id^='answer-']").attr('disabled', 'disabled');
    // mark the answer
    if ($(elem).parent().hasClass('correct')) {
        increaseScore();
        correct = true;
        $(elem).next("label").addClass("markedCorrect").after("<span>Right</span>");
        quiz.markCurrentQuestion(true);
    } else {
        $(elem).next("label").addClass("markedIncorrect").after("<span>Wrong</span>");
        $("#quizQuestionMain li.correct label").addClass("markedCorrect").after("<span>Right</span>");
        quiz.markCurrentQuestion(false);
    }
    // log the answer
    sendStat(quiz.playerId, quiz.quizId, quiz.currentLevel.id, quiz.currentQuestion.id, answerId, correct);
    // show the related information
    $("#quizQuestionDetails").fadeIn(transitionSpeed);
}

function onNextQuestionClicked(elem, event) {
    $("#quizDynamicContent").fadeOut(transitionSpeed, function() {
        showNextQuestion();
    });
}

function onFinishClicked(elem, event) {
    $("#quizContainer h2").fadeOut(transitionSpeed, function() {
        $(this).css('top', '50px').fadeIn(transitionSpeed);
    });
    $("#quizDynamicContent").fadeOut(transitionSpeed, function() {
        showResults();
    });
}

function sendStat(playerId, quizId, levelId, questionId, answerId, correct) {
    $.post(
        '/apps/quiz/callbacks.php',
        {
            'function': 'logStat',
            'quizId': quizId,
            'levelId': levelId,
            'playerId': playerId,
            'questionId': questionId,
            'answerId': answerId,
            'correct': correct
        },
        function(data) {
            if (!data.status) {
                alert(data.error);
            }
        },
        'json'
    );
}

function increaseScore() {
    currentScore++;
    // display the new score
    $("#quizCurrentScore").text(currentScore);
}

/**
 * Returns the HTML used for the level selection screen.
 */
function getLevelSelectionHtml() {
    var html = '<h3>Please pick a quiz level</h3>';
    html += '<ul id="quizLevelSelection">';
    for (levelId in quiz.data.levels) {
        html += '<li><button id="level-' + levelId + '" href="#" class="mediumBt">';
        html += '<span>' + quiz.data.levels[levelId].title + '</span></button></li>';
    }
    html += '</ul>';
    html += '<p id="printQuiz">Download a printable version of this <a href="' + quiz.data.pdf_url + '">' + quiz.data.title + ' [PDF]</a></p>';
    return html;
}

function getScoreBarHtml() {
    var maxScore = quiz.getNumberOfQuestions();
    var html = '<div id="quizScore" class="hidden">';
    html += 'Your score: ';
    html += '<span id="quizCurrentScore">0</span>/' + maxScore;
    html += '</div>';
    return html;
}

/**
 * Returns the HTML used for the question display screen.
 */
function getQuestionHtml() {
    var question = quiz.getNextQuestion();
    var html = '<div id="quizCurrentQuestionNo">#<span>' + Number(quiz.currentQuestionIndex + 1) + '</span></div>';
    html += '<div id="quizQuestion" style="background: transparent url(' + question.image + ') no-repeat 0 0">';
    html += '<div id="quizQuestionMain">';
    html += '<div id="titleWrap">';
    html += '<h3>' + question.title + '</h3>';
    html += '</div>';
    html += '<ul>';
    for (answerId in question.answers) {
        var liClass = question.answers[answerId].correct ? 'correct' : 'incorrect';
        html += '<li class="' + liClass + '">';
        html += '<input id="answer-' + question.answers[answerId].id + '" type="checkbox" />';
        html += '<label for="answer-' + question.answers[answerId].id + '">' + question.answers[answerId].title + '</label>';
        html += '</li>';
    }
    html += '</ul>';
    html += '<div id="quizQuestionDetails" class="hidden">';
    html += '<p>' + question.description + '</p>';
    if (quiz.hasNextQuestion()) {
        html += '<button id="quiz_next_question" class="smallBt"><span>Next Question</span></button>';
    } else {
        html += '<button id="quiz_finish" class="smallBt"><span>Get Results</span></button>';
    }
    html += '</div></div></div>';
    return html;
}

function getResultsHtml() {
    var suggestedLinks = quiz.getSuggestedLinks(4);
    var generalLinks = quiz.getGeneralLinks(4 - suggestedLinks.length);
    var html = '<div id="quizResults">';
    html += '<h3 style="top: 110px;">Your score: ' + currentScore + ' out of ' + quiz.getNumberOfQuestions() + '</h3>';
    html += '<div id="quizBox">';
    if (suggestedLinks.length > 0) {
        html += '<p>These links will help you to understand the areas of the quiz in which you didn\'t do well:</p>';
        html += '<ul>'
        for (k in suggestedLinks) {
            html += '<li><a href="' + suggestedLinks[k].url + '">' + suggestedLinks[k].title + '</a></li>';
        }
        html += '</ul>';
    }
    if (generalLinks.length > 0) {
        html += '<p>For more on the topics raised in this quiz, please try the following:</p>';
        html += '<ul>'
        for (k in generalLinks) {
            html += '<li><a href="' + generalLinks[k].url + '">' + generalLinks[k].title + '</a></li>';
        }
        html += '</ul>';
    }
    html += '</div>';
    html += '<ul id="quizWhatNextButtons">';
    html += '<li><a class="smallBt" href="#" onclick="window.location.reload()">Play again</a></li>';
    html += '<li><a class="smallBt" href="/quizzes.htm">Menu</a></li>';
    html += '</ul>'
    html += '</div>';
    return html;
}
