/**
 * Controls the taking of a survey
 */
(function () {
    'use strict';

    angular.module('cla.survey').service('surveyTracker', surveyTracker);

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

            sections: {},          // all sections in this survey
            attempt: {},
            optionIndexes: {},      // dictionary of question option indexes
            question: 0,		    // current question index
            currentSection: {},    // 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 survey can be attempted again
            attempt_id: null,       // the current attempt id

            anonymous: false,       // when survey is anonymous
            preview: false,         // when survey is in preview mode

            showFormErrors: false,  // should the form highlight unanswered mandatory questions?

            init: init,
            prev: prev,
            next: next,
            goto: goto,
            update: update,
            setOption: setOption,
            setTracker: setTracker,
            getComplete: getComplete,
            mergeAnswer: mergeAnswer,
            toggleOption: toggleOption,
            resetTracker: resetTracker,
            getIncomplete: getIncomplete,
            isOptionChosen: isOptionChosen,
            getChosenCount: getChosenCount,
            getOptionsCount: getOptionsCount,
            isSectionAnswered: isSectionAnswered,
            buildOptionIndexes: buildOptionIndexes,
            isQuestionAnswered: isQuestionAnswered,
            isQuestionMandatory: isQuestionMandatory,
            getCorrectOptionsCount: getCorrectOptionsCount,
            firstUnansweredQuestion: firstUnansweredQuestion,
            areAllMandatoryQuestionsAnswered: areAllMandatoryQuestionsAnswered,
            unansweredMandatoryQuestionsCount: unansweredMandatoryQuestionsCount,
            setAnonymous: setAnonymous
        };

        return service;

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

            reset();

            service.sections = angular.extend(service.sections, sections);
            service.options = Object.values(options);
            service.attempt = Object.assign(service.attempt, attempt);
            if (!service.preview)
                service.answers = Object.values(answers);
            else
                service.answers = [];


            // remove key length if exist
            if (service.sections.hasOwnProperty('length')) {
                delete service.sections.length
            }

            // set tracker
            resetTracker();

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

            // survey 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 survey.
         *
         * @param {object} answers
         */
        function setAnswers(answers) {
            if (service.preview)
                return false;

            // from service.answers
            if (!answers && answers.prototype === Array) {
                return false;
            }

            let arrayWithAnswersIds = [];

            /**
             * create array with answer ids only for checkboxes and select fields,
             * strings are passed directly to model
             */
            answers.map(answer => {
                if (typeof answer.properties.response === 'object' && answer.properties.response !== null) {
                    Object.keys(answer.properties.response).map(response_id => arrayWithAnswersIds.push(response_id))
                }
            })

            // assign answer to the question object
            for (let section in service.sections) {
                for (let question_id in service.sections[section].questions) {
                    let id = service.sections[section].questions[question_id].id
                    let answer = answers.filter(answer => answer.question_id === id)

                    Object.assign(service.sections[section].questions[question_id], {answer: answer[0]})

                    for (let option_id in service.sections[section].questions[question_id].options) {
                        let isAnswer = arrayWithAnswersIds.includes(option_id.toString());
                        Object.assign(service.sections[section].questions[question_id].options[option_id], {isSelected: isAnswer})
                    }
                }
            }

            service.question = 0;
        }

        function mergeAnswer(requestAnswer) {
            var found = false;
            for (var answerIndex in service.answers) {
                var answer = service.answers[answerIndex];
                if (answer.question_id === requestAnswer.question_id) {
                    found = true;

                    // Update stored answer
                    if (typeof requestAnswer.response === 'string')
                        answer.properties.response_text = requestAnswer.response;
                    else
                        answer.properties.response = requestAnswer.response;
                }
            }

            if (!found)
            {
                for (var sectionIndex in service.sections) {
                    var section = service.sections[sectionIndex];

                    for (var questionIndex in section.questions) {
                        var question = section.questions[questionIndex];

                        if (question.id === requestAnswer.question_id) {
                            // Build a new answer to store
                            var fullAnswer = {
                                id: 0,
                                attempt_id: 0,
                                question_id: question.id,
                                created_date: 0,
                                properties: {
                                    question_text: question.text,
                                    response: null,
                                    response_text: null,
                                    time_taken: 0
                                }
                            };

                            if (typeof requestAnswer.response === 'string')
                                fullAnswer.properties.response_text = requestAnswer.response;
                            else
                                fullAnswer.properties.response = requestAnswer.response;

                            service.answers.push(fullAnswer);
                        }
                    }
                }
            }
        }

        /**
         * Count the number of sections a user has answered.
         *
         * @returns {number}
         */
        function getChosenCount() {
            let count = 0;

            for (let 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.sections[question_index];

            for (let 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.sections[question_index];

            return question.correct_options_count;
        }

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

        /**
         * Update the current question view.
         */
        function update() {
            // angular extend used to update the existing object rather than overwrite entirely
            service.currentSection = angular.extend(service.currentSection, service.sections[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 sections in that order.
         */
        function buildOptionIndexes() {
            var i, j, question, sections = service.sections;

            for (i = 0; i < sections.length; i++) {
                question = sections[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[question_id] = {
                options: [option_ids],
                time_taken: selected_time,
                submitted: false
            };
        }

        /**
         * Toggle the selection of an option for the current question.
         *
         * @param {int} question_id
         * @param {int} option_id
         * @param {int} selected_time
         */
        function toggleOption(question_id, option_id, selected_time) {
            if (!service.chosen[question_id]) {
                service.chosen[question_id] = {
                    options: {option_id},
                    question_id: question_id,
                    time_taken: selected_time,
                    submitted: true
                };
            }

            // add answer prop to chosen option
            Object.keys(service.currentSection.questions).map(key_question => {
                if (service.currentSection.questions[key_question].id === question_id) {
                    Object.keys(service.currentSection.questions[key_question].options).map(key_option => {

                        var optionMatched = parseInt(key_option) === option_id;
                        if (parseInt(service.currentSection.questions[key_question].type) === 0 || parseInt(service.currentSection.questions[key_question].type) === 4) {
                            // For radio/single-selects, set all options to make sure only the selected one is true
                            Object.assign(service.currentSection.questions[key_question].options[key_option], {'isSelected': optionMatched})
                        } else {
                            // For multi-selects, just toggle the individual option
                            if (optionMatched)
                                service.currentSection.questions[key_question].options[key_option].isSelected = !service.currentSection.questions[key_question].options[key_option].isSelected;
                        }

                        if (service.currentSection.questions[key_question].options[key_option].id === option_id) {
                            Object.keys(service.sections).map(sectionKey => {
                                if (service.sections[sectionKey].id === service.currentSection.id) {
                                    service.sections[sectionKey].id = service.currentSection.id
                                }
                            })

                        }
                    })
                }
            })
        }

        /**
         * 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 sections that have been answered
         * @returns {Array}
         */
        function getComplete() {
            var complete = [];

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

            return complete;
        }

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

            for (var i in service.sections) {
                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() {
            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.sections.length = 0;

            service.highlightedIndex = null;
            service.currentSection = {};
            service.chosen = [];

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

        /**
         * ensure question index cannot go outside of the bounds of available sections.
         */
        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 (let 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
            let tracking = Object.keys(service.sections);
            service.setTracker(tracking);
            service.question = 0;
            service.update();
        }

        function setAnonymous(is_anonymous) {
            service.anonymous = !!parseInt(is_anonymous)
        }

        function isFilledResponse(response)
        {
            if (response === null || response === undefined || response === "")
                return false;

            // Empty array means not answered
            if (Array.isArray(response)) {
                return response.length > 0;
            }

            return true;
        }

        function isQuestionAnswered(question_id) {
            for (var answerIndex in service.answers) {
                var answer = service.answers[answerIndex];

                if (question_id === answer.question_id) {
                    // The question has been answered, but is it actual content?
                    return isFilledResponse(answer.properties.response) || isFilledResponse(answer.properties.response_text);
                }
            }

            // Fallback when if the question has no answer at all yet
            return false;
        }

        function isQuestionMandatory(question_id) {
            for (var sectionIndex in service.sections) {
                var section = service.sections[sectionIndex];

                for (var questionIndex in section.questions) {
                    var question = section.questions[questionIndex];

                    if (question_id === question.id)
                        return question.is_mandatory;
                }
            }

            // Fallback when if the question didn't exist
            return false;
        }

        function isSectionAnswered(section_id) {
            for (var sectionIndex in service.sections) {
                var section = service.sections[sectionIndex];

                if (section.id === section_id) {
                    for (var questionIndex in section.questions) {
                        var question = section.questions[questionIndex];

                        if (question.is_mandatory && !isQuestionAnswered(question.id))
                            return false;
                    }

                    // Checked every question in the section and nothing exited early so must be ok
                    return true;
                }
            }

            // Section not found
            return false;
        }

        function areAllMandatoryQuestionsAnswered() {
            for (var sectionIndex in service.sections) {
                var section = service.sections[sectionIndex];

                for (var questionIndex in section.questions) {
                    var question = section.questions[questionIndex];
                    if (question.is_mandatory && !isQuestionAnswered(question.id)) {
                        return false;
                    }
                }
            }

            return true;
        }

        function unansweredMandatoryQuestionsCount() {
            var count = 0;
            for (var sectionIndex in service.sections) {
                var section = service.sections[sectionIndex];

                for (var questionIndex in section.questions) {
                    var question = section.questions[questionIndex];
                    if (question.is_mandatory && !isQuestionAnswered(question.id)) {
                        count++;
                    }
                }
            }

            return count;
        }
    }
}());
