// (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 { ModalController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { AddonModQuizProvider } from './quiz';
import { AddonModQuizOfflineProvider } from './quiz-offline';
import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate';

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

    constructor(private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider,
            private accessRuleDelegate: AddonModQuizAccessRuleDelegate, private quizProvider: AddonModQuizProvider,
            private modalCtrl: ModalController, private quizOfflineProvider: AddonModQuizOfflineProvider) { }

    /**
     * Validate a preflight data or show a modal to input the preflight data if required.
     * It calls AddonModQuizProvider.startAttempt if a new attempt is needed.
     *
     * @param {any} quiz Quiz.
     * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation.
     * @param {any} preflightData Object where to store the preflight data.
     * @param {any} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt.
     * @param {boolean} [offline] Whether the attempt is offline.
     * @param {boolean} [prefetch] Whether user is prefetching.
     * @param {string} [title] The title to display in the modal and in the submit button.
     * @param {string} [siteId] Site ID. If not defined, current site.
     * @param {boolean} [retrying] Whether we're retrying after a failure.
     * @return {Promise<any>} Promise resolved when the preflight data is validated. The resolve param is the attempt.
     */
    getAndCheckPreflightData(quiz: any, accessInfo: any, preflightData: any, attempt: any, offline?: boolean, prefetch?: boolean,
            title?: string, siteId?: string, retrying?: boolean): Promise<any> {

        const rules = accessInfo.activerulenames;
        let isPreflightCheckRequired = false;

        // Check if the user needs to input preflight data.
        return this.accessRuleDelegate.isPreflightCheckRequired(rules, quiz, attempt, prefetch, siteId).then((required) => {
            isPreflightCheckRequired = required;

            if (required) {
                // Preflight check is required but no preflightData has been sent. Show a modal with the preflight form.
                return this.getPreflightData(quiz, accessInfo, attempt, prefetch, title, siteId).then((data) => {
                    // Data entered by the user, add it to preflight data and check it again.
                    Object.assign(preflightData, data);
                });
            }
        }).then(() => {
            // Get some fixed preflight data from access rules (data that doesn't require user interaction).
            return this.accessRuleDelegate.getFixedPreflightData(rules, quiz, preflightData, attempt, prefetch, siteId);
        }).then(() => {

            // All the preflight data is gathered, now validate it.
            return this.validatePreflightData(quiz, accessInfo, preflightData, attempt, offline, prefetch, siteId)
                    .catch((error) => {

                if (prefetch) {
                    return Promise.reject(error);
                } else if (retrying && !isPreflightCheckRequired) {
                    // We're retrying after a failure, but the preflight check wasn't required.
                    // This means there's something wrong with some access rule or user is offline and data isn't cached.
                    // Don't retry again because it would lead to an infinite loop.
                    return Promise.reject(error);
                } else {
                    // Show error and ask for the preflight again.
                    // Wait to show the error because we want it to be shown over the preflight modal.
                    setTimeout(() => {
                        this.domUtils.showErrorModalDefault(error, 'core.error', true);
                    }, 100);

                    return this.getAndCheckPreflightData(quiz, accessInfo, preflightData, attempt, offline, prefetch,
                            title, siteId, true);
                }
            });
        });
    }

    /**
     * Get the preflight data from the user using a modal.
     *
     * @param {any} quiz Quiz.
     * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation.
     * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt.
     * @param {boolean} [prefetch] Whether the user is prefetching the quiz.
     * @param {string} [title] The title to display in the modal and in the submit button.
     * @param {string} [siteId] Site ID. If not defined, current site.
     * @return {Promise<any>} Promise resolved with the preflight data. Rejected if user cancels.
     */
    getPreflightData(quiz: any, accessInfo: any, attempt: any, prefetch?: boolean, title?: string, siteId?: string): Promise<any> {
        const notSupported: string[] = [];

        // Check if there is any unsupported rule.
        accessInfo.activerulenames.forEach((rule) => {
            if (!this.accessRuleDelegate.isAccessRuleSupported(rule)) {
                notSupported.push(rule);
            }
        });

        if (notSupported.length) {
            return Promise.reject(this.translate.instant('addon.mod_quiz.errorrulesnotsupported') + ' ' +
                    JSON.stringify(notSupported));
        }

        // Create and show the modal.
        const modal = this.modalCtrl.create('AddonModQuizPreflightModalPage', {
            title: title,
            quiz: quiz,
            attempt: attempt,
            prefetch: !!prefetch,
            siteId: siteId,
            rules: accessInfo.activerulenames
        });

        modal.present();

        // Wait for modal to be dismissed.
        return new Promise((resolve, reject): void => {
            modal.onDidDismiss((data) => {
                if (typeof data != 'undefined') {
                    resolve(data);
                } else {
                    reject(this.domUtils.createCanceledError());
                }
            });
        });
    }

    /**
     * Gets the mark string from a question HTML.
     * Example result: "Marked out of 1.00".
     *
     * @param  {string} html Question's HTML.
     * @return {string}      Question's mark.
     */
    getQuestionMarkFromHtml(html: string): string {
        const element = this.domUtils.convertToElement(html);

        return this.domUtils.getContentsOfElement(element, '.grade');
    }

    /**
     * Add some calculated data to the attempt.
     *
     * @param {any} quiz Quiz.
     * @param {any} attempt Attempt.
     * @param {boolean} highlight Whether we should check if attempt should be highlighted.
     * @param {number} [bestGrade] Quiz's best grade (formatted). Required if highlight=true.
     */
    setAttemptCalculatedData(quiz: any, attempt: any, highlight?: boolean, bestGrade?: string): void {

        attempt.rescaledGrade = this.quizProvider.rescaleGrade(attempt.sumgrades, quiz, false);
        attempt.finished = this.quizProvider.isAttemptFinished(attempt.state);
        attempt.readableState = this.quizProvider.getAttemptReadableState(quiz, attempt);

        if (quiz.showMarkColumn && attempt.finished) {
            attempt.readableMark = this.quizProvider.formatGrade(attempt.sumgrades, quiz.decimalpoints);
        } else {
            attempt.readableMark = '';
        }

        if (quiz.showGradeColumn && attempt.finished) {
            attempt.readableGrade = this.quizProvider.formatGrade(attempt.rescaledGrade, quiz.decimalpoints);

            // Highlight the highest grade if appropriate.
            attempt.highlightGrade = highlight && !attempt.preview && attempt.state == AddonModQuizProvider.ATTEMPT_FINISHED &&
                                        attempt.readableGrade == bestGrade;
        } else {
            attempt.readableGrade = '';
        }
    }

    /**
     * Add some calculated data to the quiz.
     *
     * @param {any} quiz Quiz.
     * @param {any} options Options returned by AddonModQuizProvider.getCombinedReviewOptions.
     */
    setQuizCalculatedData(quiz: any, options: any): void {
        quiz.sumGradesFormatted = this.quizProvider.formatGrade(quiz.sumgrades, quiz.decimalpoints);
        quiz.gradeFormatted = this.quizProvider.formatGrade(quiz.grade, quiz.decimalpoints);

        quiz.showAttemptColumn = quiz.attempts != 1;
        quiz.showGradeColumn = options.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX &&
                                    this.quizProvider.quizHasGrades(quiz);
        quiz.showMarkColumn = quiz.showGradeColumn && quiz.grade != quiz.sumgrades;
        quiz.showFeedbackColumn = quiz.hasfeedback && options.alloptions.overallfeedback;
    }

    /**
     * Validate the preflight data. It calls AddonModQuizProvider.startAttempt if a new attempt is needed.
     *
     * @param {any} quiz Quiz.
     * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation.
     * @param {any} preflightData Object where to store the preflight data.
     * @param {any} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt.
     * @param {boolean} [offline] Whether the attempt is offline.
     * @param {boolean} [sent] Whether preflight data has been entered by the user.
     * @param {boolean} [prefetch] Whether user is prefetching.
     * @param {string} [title] The title to display in the modal and in the submit button.
     * @param {string} [siteId] Site ID. If not defined, current site.
     * @return {Promise<any>} Promise resolved when the preflight data is validated.
     */
    validatePreflightData(quiz: any, accessInfo: any, preflightData: any, attempt: any, offline?: boolean, prefetch?: boolean,
            siteId?: string): Promise<any> {

        const rules = accessInfo.activerulenames;
        let promise;

        if (attempt) {
            if (attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !attempt.finishedOffline) {
                // We're continuing an attempt. Call getAttemptData to validate the preflight data.
                const page = attempt.currentpage;

                promise = this.quizProvider.getAttemptData(attempt.id, page, preflightData, offline, true, siteId).then(() => {
                    if (offline) {
                        // Get current page stored in local.
                        return this.quizOfflineProvider.getAttemptById(attempt.id).then((localAttempt) => {
                            attempt.currentpage = localAttempt.currentpage;
                        }).catch(() => {
                            // No local data.
                        });
                    }
                });
            } else {
                // Attempt is overdue or finished in offline, we can only see the summary.
                // Call getAttemptSummary to validate the preflight data.
                promise = this.quizProvider.getAttemptSummary(attempt.id, preflightData, offline, true, false, siteId);
            }
        } else {
            // We're starting a new attempt, call startAttempt.
            promise = this.quizProvider.startAttempt(quiz.id, preflightData, false, siteId).then((att) => {
                attempt = att;
            });
        }

        return promise.then(() => {
            // Preflight data validated.
            this.accessRuleDelegate.notifyPreflightCheckPassed(rules, quiz, attempt, preflightData, prefetch, siteId);

            return attempt;
        }).catch((error) => {
            if (this.utils.isWebServiceError(error)) {
                // The WebService returned an error, assume the preflight failed.
                this.accessRuleDelegate.notifyPreflightCheckFailed(rules, quiz, attempt, preflightData, prefetch, siteId);
            }

            return Promise.reject(error);
        });
    }
}
