// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { AddonModLessonProvider } from './lesson';
import * as moment from 'moment';

/**
 * Helper service that provides some features for quiz.
 */
@Injectable()
export class AddonModLessonHelperProvider {

    constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService,
            private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { }

    /**
     * Given the HTML of next activity link, format it to extract the href and the text.
     *
     * @param {string} activityLink HTML of the activity link.
     * @return {{formatted: boolean, label: string, href: string}} Formatted data.
     */
    formatActivityLink(activityLink: string): {formatted: boolean, label: string, href: string} {
        const element = this.domUtils.convertToElement(activityLink),
            anchor = element.querySelector('a');

        if (!anchor) {
            // Anchor not found, return the original HTML.
            return {
                formatted: false,
                label: activityLink,
                href: ''
            };
        }

        return {
            formatted: true,
            label: anchor.innerHTML,
            href: anchor.href
        };
    }

    /**
     * Given the HTML of an answer from a content page, extract the data to render the answer.
     *
     * @param  {String} html Answer's HTML.
     * @return {{buttonText: string, content: string}} Data to render the answer.
     */
    getContentPageAnswerDataFromHtml(html: string): {buttonText: string, content: string} {
        const data = {
                buttonText: '',
                content: ''
            },
            element = this.domUtils.convertToElement(html);

        // Search the input button.
        const button = <HTMLInputElement> element.querySelector('input[type="button"]');

        if (button) {
            // Extract the button content and remove it from the HTML.
            data.buttonText = button.value;
            button.remove();
        }

        data.content = element.innerHTML.trim();

        return data;
    }

    /**
     * Get the buttons to change pages.
     *
     * @param {string} html Page's HTML.
     * @return {any[]} List of buttons.
     */
    getPageButtonsFromHtml(html: string): any[] {
        const buttons = [],
            element = this.domUtils.convertToElement(html);

        // Get the container of the buttons if it exists.
        let buttonsContainer = element.querySelector('.branchbuttoncontainer');

        if (!buttonsContainer) {
            // Button container not found, might be a legacy lesson (from 1.9).
            if (!element.querySelector('form input[type="submit"]')) {
                // No buttons found.
                return buttons;
            }
            buttonsContainer = element;
        }

        const forms = Array.from(buttonsContainer.querySelectorAll('form'));
        forms.forEach((form) => {
            const buttonSelector = 'input[type="submit"], button[type="submit"]',
                buttonEl = <HTMLInputElement | HTMLButtonElement> form.querySelector(buttonSelector),
                inputs = Array.from(form.querySelectorAll('input'));

            if (!buttonEl || !inputs || !inputs.length) {
                // Button not found or no inputs, ignore it.
                return;
            }

            const button = {
                id: buttonEl.id,
                title: buttonEl.title || buttonEl.value,
                content: buttonEl.tagName == 'INPUT' ? buttonEl.value : buttonEl.innerHTML.trim(),
                data: {}
            };

            inputs.forEach((input) => {
                if (input.type != 'submit') {
                    button.data[input.name] = input.value;
                }
            });

            buttons.push(button);
        });

        return buttons;
    }

    /**
     * Given a page data (result of getPageData), get the page contents.
     *
     * @param {any} data Page data.
     * @return {string} Page contents.
     */
    getPageContentsFromPageData(data: any): string {
        // Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered.
        const element = this.domUtils.convertToElement(data.pagecontent),
            contents = element.querySelector('.contents');

        if (contents) {
            return contents.innerHTML.trim();
        }

        // Cannot find contents element, return the page.contents (some elements like videos might not work).
        return data.page.contents;
    }

    /**
     * Get a question and all the data required to render it from the page data (result of AddonModLessonProvider.getPageData).
     *
     * @param {FormGroup} questionForm The form group where to add the controls.
     * @param {any} pageData Page data (result of $mmaModLesson#getPageData).
     * @return {any} Question data.
     */
    getQuestionFromPageData(questionForm: FormGroup, pageData: any): any {
        const question: any = {},
            element = this.domUtils.convertToElement(pageData.pagecontent);

        // Get the container of the question answers if it exists.
        const fieldContainer = element.querySelector('.fcontainer');

        // Get hidden inputs and add their data to the form group.
        const hiddenInputs = <HTMLInputElement[]> Array.from(element.querySelectorAll('input[type="hidden"]'));
        hiddenInputs.forEach((input) => {
            questionForm.addControl(input.name, this.fb.control(input.value));
        });

        // Get the submit button and extract its value.
        const submitButton = <HTMLInputElement> element.querySelector('input[type="submit"]');
        question.submitLabel = submitButton ? submitButton.value : this.translate.instant('addon.mod_lesson.submit');

        if (!fieldContainer) {
            // Element not found, return.
            return question;
        }

        let type;

        switch (pageData.page.qtype) {
            case AddonModLessonProvider.LESSON_PAGE_TRUEFALSE:
            case AddonModLessonProvider.LESSON_PAGE_MULTICHOICE:
                question.template = 'multichoice';
                question.options = [];

                // Get all the inputs. Search radio first.
                let inputs = <HTMLInputElement[]> Array.from(fieldContainer.querySelectorAll('input[type="radio"]'));
                if (!inputs || !inputs.length) {
                    // Radio buttons not found, it might be a multi answer. Search for checkbox.
                    question.multi = true;
                    inputs = <HTMLInputElement[]> Array.from(fieldContainer.querySelectorAll('input[type="checkbox"]'));

                    if (!inputs || !inputs.length) {
                        // No checkbox found either. Stop.
                        return question;
                    }
                }

                let controlAdded = false;
                inputs.forEach((input) => {
                    const option: any = {
                            id: input.id,
                            name: input.name,
                            value: input.value,
                            checked: !!input.checked,
                            disabled: !!input.disabled
                        },
                        parent = input.parentElement;

                    if (option.checked || question.multi) {
                        // Add the control.
                        const value = question.multi ? {value: option.checked, disabled: option.disabled} : option.value;
                        questionForm.addControl(option.name, this.fb.control(value));
                        controlAdded = true;
                    }

                    // Remove the input and use the rest of the parent contents as the label.
                    input.remove();
                    option.text = parent.innerHTML.trim();

                    question.options.push(option);
                });

                if (!question.multi) {
                    question.controlName = inputs[0].name; // All option have the same name in single choice.

                    if (!controlAdded) {
                        // No checked option for single choice, add the control with an empty value.
                        questionForm.addControl(question.controlName, this.fb.control(''));
                    }
                }

                break;

            case AddonModLessonProvider.LESSON_PAGE_NUMERICAL:
                type = 'number';
            case AddonModLessonProvider.LESSON_PAGE_SHORTANSWER:
                question.template = 'shortanswer';

                // Get the input.
                const input = <HTMLInputElement> fieldContainer.querySelector('input[type="text"], input[type="number"]');
                if (!input) {
                    return question;
                }

                question.input = {
                    id: input.id,
                    name: input.name,
                    maxlength: input.maxLength,
                    type: type || 'text'
                };

                // Init the control.
                questionForm.addControl(input.name, this.fb.control({value: input.value, disabled: input.readOnly}));
                break;

            case AddonModLessonProvider.LESSON_PAGE_ESSAY:
                question.template = 'essay';

                // Get the textarea.
                const textarea = fieldContainer.querySelector('textarea');

                if (!textarea) {
                    // Textarea not found, probably review mode.
                    const answerEl = fieldContainer.querySelector('.reviewessay');
                    if (!answerEl) {
                        // Answer not found, stop.
                        return question;
                    }
                    question.useranswer = answerEl.innerHTML;

                } else {
                    question.textarea = {
                        id: textarea.id,
                        name: textarea.name || 'answer[text]'
                    };

                    // Init the control.
                    question.control = this.fb.control('');
                    questionForm.addControl(question.textarea.name, question.control);
                }

                break;

            case AddonModLessonProvider.LESSON_PAGE_MATCHING:
                question.template = 'matching';

                const rows = Array.from(fieldContainer.querySelectorAll('.answeroption'));
                question.rows = [];

                rows.forEach((row) => {
                    const label = row.querySelector('label'),
                        select = row.querySelector('select'),
                        options = Array.from(row.querySelectorAll('option')),
                        rowData: any = {};

                    if (!label || !select || !options || !options.length) {
                        return;
                    }

                    // Get the row's text (label).
                    rowData.text = label.innerHTML.trim();
                    rowData.id = select.id;
                    rowData.name = select.name;
                    rowData.options = [];

                    // Treat each option.
                    let controlAdded = false;
                    options.forEach((option) => {
                        if (typeof option.value == 'undefined') {
                            // Option not valid, ignore it.
                            return;
                        }

                        const opt = {
                            value: option.value,
                            label: option.innerHTML.trim(),
                            selected: option.selected
                        };

                        if (opt.selected) {
                            controlAdded = true;
                            questionForm.addControl(rowData.name, this.fb.control({value: opt.value, disabled: !!select.disabled}));
                        }

                        rowData.options.push(opt);
                    });

                    if (!controlAdded) {
                        // No selected option, add the control with an empty value.
                        questionForm.addControl(rowData.name, this.fb.control({value: '', disabled: !!select.disabled}));
                    }

                    question.rows.push(rowData);
                });
                break;
            default:
                // Nothing to do.
        }

        return question;
    }

    /**
     * Given the HTML of an answer from a question page, extract the data to render the answer.
     *
     * @param {string} html Answer's HTML.
     * @return {any} Object with the data to render the answer. If the answer doesn't require any parsing, return a string with
     *               the HTML.
     */
    getQuestionPageAnswerDataFromHtml(html: string): any {
        const data: any = {},
            element = this.domUtils.convertToElement(html);

        // Check if it has a checkbox.
        let input = <HTMLInputElement> element.querySelector('input[type="checkbox"][name*="answer"]');

        if (input) {
            // Truefalse or multichoice.
            data.isCheckbox = true;
            data.checked = !!input.checked;
            data.name = input.name;
            data.highlight = !!element.querySelector('.highlight');

            input.remove();
            data.content = element.innerHTML.trim();

            return data;
        }

        // Check if it has an input text or number.
        input = <HTMLInputElement> element.querySelector('input[type="number"],input[type="text"]');
        if (input) {
            // Short answer or numeric.
            data.isText = true;
            data.value = input.value;

            return data;
        }

        // Check if it has a select.
        const select = <HTMLSelectElement> element.querySelector('select');
        if (select && select.options) {
            // Matching.
            const selectedOption = select.options[select.selectedIndex];
            data.isSelect = true;
            data.id = select.id;
            if (selectedOption) {
                data.value = selectedOption.value;
            } else {
                data.value = '';
            }

            select.remove();
            data.content = element.innerHTML.trim();

            return data;
        }

        // The answer doesn't need any parsing, return the HTML as it is.
        return html;
    }

    /**
     * Get a label to identify a retake (lesson attempt).
     *
     * @param {any} retake Retake object.
     * @param {boolean} [includeDuration] Whether to include the duration of the retake.
     * @return {string} Retake label.
     */
    getRetakeLabel(retake: any, includeDuration?: boolean): string {
        const data = {
                retake: retake.try + 1,
                grade: '',
                timestart: '',
                duration: ''
            },
            hasGrade = retake.grade != null;

        if (hasGrade || retake.end) {
            // Retake finished with or without grade (if the lesson only has content pages, it has no grade).
            if (hasGrade) {
                data.grade = this.translate.instant('core.percentagenumber', {$a: retake.grade});
            }
            data.timestart = moment(retake.timestart * 1000).format('LLL');
            if (includeDuration) {
                data.duration = this.timeUtils.formatTime(retake.timeend - retake.timestart);
            }
        } else {
            // The user has not completed the retake.
            data.grade = this.translate.instant('addon.mod_lesson.notcompleted');
            if (retake.timestart) {
                data.timestart = moment(retake.timestart * 1000).format('LLL');
            }
        }

        return this.translate.instant('addon.mod_lesson.retakelabel' + (includeDuration ? 'full' : 'short'), data);
    }

    /**
     * Prepare the question data to be sent to server.
     *
     * @param {any} question Question to prepare.
     * @param {any} data Data to prepare.
     * @return {any} Data to send.
     */
    prepareQuestionData(question: any, data: any): any {
        if (question.template == 'essay' && question.textarea) {
            // Add some HTML to the answer if needed.
            data[question.textarea.name] = this.textUtils.formatHtmlLines(data[question.textarea.name]);
        } else if (question.template == 'multichoice' && question.multi) {
            // Only send the options with value set to true.
            for (const name in data) {
                if (name.match(/answer\[\d+\]/) && data[name] == false) {
                    delete data[name];
                }
            }
        }

        return data;
    }

    /**
     * Given the feedback of a process page in HTML, remove the question text.
     *
     * @param {string} html Feedback's HTML.
     * @return {string} Feedback without the question text.
     */
    removeQuestionFromFeedback(html: string): string {
        const element = this.domUtils.convertToElement(html);

        // Remove the question text.
        this.domUtils.removeElement(element, '.generalbox:not(.feedback):not(.correctanswer)');

        return element.innerHTML.trim();
    }
}
