/**
 * Controls the taking of a quiz
 *
 * @author Simon Willan <simon.willan@claromentis.com>
 */
(function () {
    'use strict';

    angular.module('cla.quiz')
        .service('quizTracker', quizTracker);

    //quizTracker.$inject = [];

    /**
     *
     * @constructor
     */
    function quizTracker() {
        var service = {
            options: {                // tracker options
                showHelpText: false,  // show single or multiple answer help text
                shuffleOptions: false // shuffle question options
            },

            questions: [],          // all questions in this quiz
            optionIndexes: {},      // dictionary of question option indexes
            question: 0,		    // current question index
            currentQuestion: {},    // current question object
            count: 0,

            timer: 0,			    // question timer
            chosen: {},			    // what the user has chosen
            loaded: false,		    // tracker initialised?
            highlightedIndex: null, // highlighted option index (for keyboard navigation and selection)

            tracker: [],            // track array for question tracking (question indices)
            trackpos: 0,            // current position of tracker index

            results: null,          // results objects
            can_retake: null,       // whether the quiz can be attempted again
            attempt_id: null,       // the current attempt id

                               init: init,
                               prev: prev,
                               next: next,
                               goto: goto,
                             update: update,
                          setOption: setOption,
                         setTracker: setTracker,
                        getComplete: getComplete,
                       toggleOption: toggleOption,
                       resetTracker: resetTracker,
                      getIncomplete: getIncomplete,
                     isOptionChosen: isOptionChosen,
                     getChosenCount: getChosenCount,
                    getOptionsCount: getOptionsCount,
                 buildOptionIndexes: buildOptionIndexes,
             getCorrectOptionsCount: getCorrectOptionsCount,
            firstUnansweredQuestion: firstUnansweredQuestion
        };
        return service;

        /**
         * initialise tracker
         * @param {object[]} questions
         * @param {object[]} answers
         * @param {object}   [options]
         */
        function init(questions, answers, options) {
            // skip initialisation if we have already loaded the data
            if (service.loaded)
                return;

            reset();

            service.questions = angular.extend(service.questions, questions);
            service.options = angular.extend(service.options, options || {});

            // set tracker
            resetTracker();

            // set prefilled choices if choices are given
            setAnswers(answers);

            // quiz started, timer set time
            service.timer = Date.now();
            service.loaded = true;

            // set up question option indexes
            service.buildOptionIndexes();
        }

        /**
         * Set any preloaded user answers.
         *
         * Usually the result of continuing a suspended quiz.
         *
         * @param {object} answers
         */
        function setAnswers(answers) {
            // have we been given a choice? #deep
            if (answers && answers.constructor !== Array) {
                var i;

                // create a dictionary with question ids as key, and their index as a value
                var questionIndexMap = {};
                for (i in service.questions) {
                    questionIndexMap[service.questions[i].id] = i
                }

                // for every choice...
                for (i in answers) {
                    // temporarily set active question
                    service.question = parseInt(questionIndexMap[i]);

                    // check properties
                    var properties = 'properties' in answers[i] ? answers[i].properties : null;

                    var response = [];
                    if (properties && 'response' in answers[i].properties)
                        response = answers[i].properties.response;

                    var time_taken = null;
                    if (properties && 'time_taken' in answers[i].properties)
                        time_taken = answers[i].properties.time_taken;

                    // get the users responses
                    var responseKeys = [];
                    for (var j in response) {
                        responseKeys.push(parseInt(j));
                    }

                    // set the option
                    service.setOption(responseKeys, parseInt(time_taken));
                    service.chosen[service.question].submitted = true;
                }

                service.question = 0;
            }
        }

        /**
         * Count the number of questions a user has answered.
         *
         * @returns {number}
         */
        function getChosenCount() {
            var count = 0;
            for (var i in service.chosen) {
                if (service.chosen[i].submitted === true) {
                    count++;
                }
            }
            return count;
        }

        /**
         * Get the options count for a question.
         *
         * @returns {number}
         */
        function getOptionsCount(question_index) {
            var count = 0;
            var question = service.questions[question_index];
            for (var i in question.options) {
                if (question.options.hasOwnProperty(i)) {
                    count++;
                }
            }
            return count;
        }

        /**
         * Count the number of correct options for this question.
         *
         * @returns {number}
         */
        function getCorrectOptionsCount(question_index) {
            var question = service.questions[question_index];

            return question.correct_options_count;
        }

        /**
         * Get the first unanswered question for the current Quiz attempt.
         *
         * @returns {number}
         */
        function firstUnansweredQuestion() {
            for (var i = 0; i < service.questions.length; i++) {
                if (!service.chosen[i]) {
                    return i;
                }
            }
            return (service.questions.length - 1);
        }

        /**
         * Update the current question view.
         */
        function update() {
            // angular extend used to update the existing object rather than overwrite entirely
            service.currentQuestion = angular.extend(service.currentQuestion, service.questions[service.question]);

            // reset timer whenever there is an object update (change in question)
            service.timer = Date.now();
            service.highlightedIndex = null;
        }

        /**
         * Build the option index arrays for each question.
         *
         * This facilitates shuffling answer order, if enabled, and is used in
         * the template to iterate over the questions in that order.
         */
        function buildOptionIndexes() {
            var i, j, question, questions = service.questions;

            for (i = 0; i < questions.length; i++) {
                question = questions[i];

                // Build the dictionary of options per question
                if (!service.optionIndexes[question.id]) {
                    service.optionIndexes[question.id] = [];
                }

                for (j in question.options) {
                    if (question.options.hasOwnProperty(j)) {
                        service.optionIndexes[question.id].push(parseInt(j));
                    }
                }

                // Shuffle the options if configured
                if (service.options.shuffleOptions) {
                    service.optionIndexes[question.id] = _.shuffle(service.optionIndexes[question.id]);
                }
            }
        }

        /**
         * Set the selected option for the current question.
         *
         * @param {int[]} option_ids
         * @param {int} selected_time
         */
        function setOption(option_ids, selected_time) {
            service.chosen[service.question] = {
                options: option_ids,
                time_taken: selected_time,
                submitted: false
            };
        }

        /**
         * Toggle the selection of an option for the current question.
         *
         * @param {int} option_id
         * @param {int} selected_time
         */
        function toggleOption(option_id, selected_time) {

            var correctOptionsCount = service.getCorrectOptionsCount(service.question);

            // If an answer hasn't been created yet, create it and return
            if (!service.chosen[service.question]) {
                service.setOption([option_id], selected_time);
                return;
            }

            // Update the answer
            var answer = service.chosen[service.question];
            var answerOptionIndex = answer.options.indexOf(option_id);

            if (answerOptionIndex >= 0) {
                // If the option has already been chosen, we will remove it
                // from the selection
                answer.options.splice(answerOptionIndex, 1);
            } else {
                if (correctOptionsCount > 1) {
                    // If there are multiple correct options, we will allow
                    // multiple option selections
                    answer.options.push(option_id);
                } else {
                    // Otherwise we only allow one option to be chosen
                    answer.options = [option_id];
                }
            }

            answer.time_taken = selected_time;
        }

        /**
         * Check whether the given option ID is chosen for a question index.
         *
         * @param {int} question_index - Index of the question to check
         * @param {int} option_id - Option ID to check
         * @returns {boolean}
         */
        function isOptionChosen(question_index, option_id) {
            var i, answer = service.chosen[question_index];

            if (answer) {
                for (i in answer.options) {
                    if (answer.options[i] == option_id) {
                        return true;
                    }
                }
            }

            return false;
        }

        /**
         * returns a list of questions that have been answered
         * @returns {Array}
         */
        function getComplete() {
            var complete = [];

            // get the keys of our chosen questions
            for (var i in service.chosen) {
                complete.push(i);
            }

            return complete;
        }

        /**
         * returns a list of questions not yet answered
         * @returns {Array}
         */
        function getIncomplete() {
            var incomplete = [];

            for (var i in service.questions) {
                if (!service.chosen[i]) {
                    incomplete.push(i);
                }
            }

            return incomplete;
        }

        /**
         * move on to the next question
         */
        function next() {
            track(++service.trackpos);
            service.update();
        }

        /**
         * move back to the previous question
         */
        function prev() {
            // clear selection if it hasn't been submitted
            track(--service.trackpos);
            service.update();
        }

        /**
         * go to a specific question
         */
        function goto(question) {
            track(question);
            service.update();
        }

        /**
         * reset any initial vars back to their default states without breaking references.
         */
        function reset() {
            service.questions.length = 0;

            service.highlightedIndex = null;
            service.currentQuestion = {};
            service.chosen = {};

            // reset current question to 0
            service.question = 0;
        }

        /**
         * ensure question index cannot go outside of the bounds of available questions.
         */
        function clamp() {
            // clamp
            if (service.trackpos >= service.count - 1) {
                service.trackpos = service.count - 1;
            } else if (service.trackpos < 0) {
                service.trackpos = 0;
            }
        }

        /**
         * next and previous should use a track to navigate
         */
        function track(pos) {
            service.trackpos = pos;
            if (service.tracker.length > 0) {
                clamp();
                service.question = service.tracker[service.trackpos];
            }
        }

        /**
         * fill a track with indexes
         */
        function setTracker(array) {
            service.tracker = [];
            service.trackpos = 0;
            for (var i = 0; i < array.length; i++) {
                service.tracker.push(parseInt(array[i]));
            }
            service.count = service.tracker.length;
        }

        /**
         * reset tracker to question list
         */
        function resetTracker() {
            // set tracking array
            var tracking = [];
            for (var i = 0; i < service.questions.length; i++) {
                tracking.push(i);
            }
            service.setTracker(tracking);
            service.question = 0;
            service.update();
        }
    }
}());
