// (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 { Component, Optional, Injector } from '@angular/core';
import { Content } from 'ionic-angular';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { AddonModChoiceProvider } from '../../providers/choice';
import { AddonModChoiceOfflineProvider } from '../../providers/offline';
import { AddonModChoiceSyncProvider } from '../../providers/sync';
import * as moment from 'moment';

/**
 * Component that displays a choice.
 */
@Component({
    selector: 'addon-mod-choice-index',
    templateUrl: 'index.html',
})
export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityComponent {
    component = AddonModChoiceProvider.COMPONENT;
    moduleName = 'choice';

    choice: any;
    options = [];
    selectedOption: any;
    choiceNotOpenYet = false;
    choiceClosed = false;
    canEdit = false;
    canDelete = false;
    canSeeResults = false;
    data = [];
    labels = [];
    results = [];
    publishInfo: string; // Message explaining the user what will happen with his choices.

    protected userId: number;
    protected syncEventName = AddonModChoiceSyncProvider.AUTO_SYNCED;
    protected hasAnsweredOnline = false;
    protected now: number;

    constructor(injector: Injector, private choiceProvider: AddonModChoiceProvider, @Optional() content: Content,
            private choiceOffline: AddonModChoiceOfflineProvider, private choiceSync: AddonModChoiceSyncProvider) {
        super(injector, content);
    }

    /**
     * Component being initialized.
     */
    ngOnInit(): void {
        super.ngOnInit();

        this.userId = this.sitesProvider.getCurrentSiteUserId();

        this.loadContent(false, true).then(() => {
            if (!this.choice) {
                return;
            }
            this.choiceProvider.logView(this.choice.id).then(() => {
                this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
            }).catch((error) => {
                // Ignore errors.
            });
        });
    }

    /**
     * Perform the invalidate content function.
     *
     * @return {Promise<any>} Resolved when done.
     */
    protected invalidateContent(): Promise<any> {
        const promises = [];

        promises.push(this.choiceProvider.invalidateChoiceData(this.courseId));

        if (this.choice) {
            promises.push(this.choiceProvider.invalidateOptions(this.choice.id));
            promises.push(this.choiceProvider.invalidateResults(this.choice.id));
        }

        return Promise.all(promises);
    }

    /**
     * Compares sync event data with current data to check if refresh content is needed.
     *
     * @param {any} syncEventData Data receiven on sync observer.
     * @return {boolean}          True if refresh is needed, false otherwise.
     */
    protected isRefreshSyncNeeded(syncEventData: any): boolean {
        if (this.choice && syncEventData.choiceId == this.choice.id && syncEventData.userId == this.userId) {
            this.content.scrollToTop();

            return true;
        }

        return false;
    }

    /**
     * Download choice contents.
     *
     * @param  {boolean}      [refresh=false]    If it's refreshing content.
     * @param  {boolean}      [sync=false]       If the refresh is needs syncing.
     * @param  {boolean}      [showErrors=false] If show errors to the user of hide them.
     * @return {Promise<any>} Promise resolved when done.
     */
    protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
        this.now = new Date().getTime();

        return this.choiceProvider.getChoice(this.courseId, this.module.id).then((choice) => {
            this.choice = choice;
            this.choice.timeopen = parseInt(choice.timeopen) * 1000;
            this.choice.openTimeReadable = moment(choice.timeopen).format('LLL');
            this.choice.timeclose = parseInt(choice.timeclose) * 1000;
            this.choice.closeTimeReadable = moment(choice.timeclose).format('LLL');

            this.description = choice.intro || choice.description;
            this.choiceNotOpenYet = choice.timeopen && choice.timeopen > this.now;
            this.choiceClosed = choice.timeclose && choice.timeclose <= this.now;

            this.dataRetrieved.emit(choice);

            if (sync) {
                // Try to synchronize the choice.
                return this.syncActivity(showErrors).then((updated) => {
                    if (updated) {
                        // Responses were sent, update the choice.
                        return this.choiceProvider.getChoice(this.courseId, this.module.id).then((choice) => {
                            this.choice = choice;
                        });
                    }
                });
            }
        }).then(() => {
            // Check if there are responses stored in offline.
            return this.choiceOffline.hasResponse(this.choice.id);
        }).then((hasOffline) => {
            this.hasOffline = hasOffline;

            // We need fetchOptions to finish before calling fetchResults because it needs hasAnsweredOnline variable.
            return this.fetchOptions(hasOffline).then(() => {
                return this.fetchResults();
            });
        }).then(() => {
            // All data obtained, now fill the context menu.
            this.fillContextMenu(refresh);
        });
    }

    /**
     * Convenience function to get choice options.
     *
     * @param {boolean} hasOffline True if there are responses stored offline.
     * @return {Promise<any>} Promise resolved when done.
     */
    protected fetchOptions(hasOffline: boolean): Promise<any> {
        return this.choiceProvider.getOptions(this.choice.id).then((options) => {
            let promise;

            // Check if the user has answered (synced) to allow show results.
            this.hasAnsweredOnline = options.some((option) => option.checked);

            if (hasOffline) {
                promise = this.choiceOffline.getResponse(this.choice.id).then((response) => {
                    const optionsKeys = {};
                    options.forEach((option) => {
                        optionsKeys[option.id] = option;
                    });
                    // Update options with the offline data.
                    if (response.deleting) {
                        // Uncheck selected options.
                        if (response.responses.length > 0) {
                            // Uncheck all options selected in responses.
                            response.responses.forEach((selected) => {
                                if (optionsKeys[selected] && optionsKeys[selected].checked) {
                                    optionsKeys[selected].checked = false;
                                    optionsKeys[selected].countanswers--;
                                }
                            });
                        } else {
                            // On empty responses, uncheck all selected.
                            Object.keys(optionsKeys).forEach((key) => {
                                if (optionsKeys[key].checked) {
                                    optionsKeys[key].checked = false;
                                    optionsKeys[key].countanswers--;
                                }
                            });
                        }
                    } else {
                        // Uncheck all options to check again the offlines'.
                        Object.keys(optionsKeys).forEach((key) => {
                            if (optionsKeys[key].checked) {
                                optionsKeys[key].checked = false;
                                optionsKeys[key].countanswers--;
                            }
                        });
                        // Then check selected ones.
                        response.responses.forEach((selected) => {
                            if (optionsKeys[selected]) {
                                optionsKeys[selected].checked = true;
                                optionsKeys[selected].countanswers++;
                            }
                        });
                    }

                    // Convert it again to array.
                    return Object.keys(optionsKeys).map((key) => optionsKeys[key]);
                });
            } else {
                promise = Promise.resolve(options);
            }

            promise.then((options) => {
                const isOpen = this.isChoiceOpen();

                let hasAnswered = false;
                this.selectedOption = {id: -1}; // Single choice model.
                options.forEach((option) => {
                    if (option.checked) {
                        hasAnswered = true;
                        if (!this.choice.allowmultiple) {
                            this.selectedOption.id = option.id;
                        }
                    }
                });

                this.canEdit = isOpen && (this.choice.allowupdate || !hasAnswered);
                this.canDelete = isOpen && this.choice.allowupdate && hasAnswered;
                this.options = options;

                if (this.canEdit) {

                    // Calculate the publish info message.
                    switch (this.choice.showresults) {
                        case AddonModChoiceProvider.RESULTS_NOT:
                            this.publishInfo = 'addon.mod_choice.publishinfonever';
                            break;

                        case AddonModChoiceProvider.RESULTS_AFTER_ANSWER:
                            if (this.choice.publish == AddonModChoiceProvider.PUBLISH_ANONYMOUS) {
                                this.publishInfo = 'addon.mod_choice.publishinfoanonafter';
                            } else {
                                this.publishInfo = 'addon.mod_choice.publishinfofullafter';
                            }
                            break;

                        case AddonModChoiceProvider.RESULTS_AFTER_CLOSE:
                            if (this.choice.publish == AddonModChoiceProvider.PUBLISH_ANONYMOUS) {
                                this.publishInfo = 'addon.mod_choice.publishinfoanonclose';
                            } else {
                                this.publishInfo = 'addon.mod_choice.publishinfofullclose';
                            }
                            break;

                        default:
                            // No need to inform the user since it's obvious that the results are being published.
                            this.publishInfo = '';
                    }
                }
            });
        });
    }

    /**
     * Convenience function to get choice results.
     *
     * @return {Promise<any>} Resolved when done.
     */
    protected fetchResults(): Promise<any> {
        if (this.choiceNotOpenYet) {
            // Cannot see results yet.
            this.canSeeResults = false;

            return Promise.resolve();
        }

        return this.choiceProvider.getResults(this.choice.id).then((results) => {
            let hasVotes = false;
            this.data = [];
            this.labels = [];
            results.forEach((result) => {
                if (result.numberofuser > 0) {
                    hasVotes = true;
                }
                result.percentageamount = parseFloat(result.percentageamount).toFixed(1);
                this.data.push(result.numberofuser);
                this.labels.push(result.text);
            });
            this.canSeeResults = hasVotes || this.choiceProvider.canStudentSeeResults(this.choice, this.hasAnsweredOnline);
            this.results = results;
        });
    }

    /**
     * Check if a choice is open.
     *
     * @return {boolean} True if choice is open, false otherwise.
     */
    protected isChoiceOpen(): boolean {
        return (this.choice.timeopen === 0 || this.choice.timeopen <= this.now) &&
                (this.choice.timeclose === 0 || this.choice.timeclose > this.now);
    }

    /**
     * Return true if the user has selected at least one option.
     *
     * @return {boolean} True if the user has responded.
     */
    canSave(): boolean {
        if (this.choice.allowmultiple) {
            return this.options.some((option) => option.checked);
        } else {
            return this.selectedOption.id !== -1;
        }
    }

    /**
     * Save options selected.
     */
    save(): void {
        // Only show confirm if choice doesn't allow update.
        let promise;
        if (this.choice.allowupdate) {
            promise = Promise.resolve();
        } else {
            promise = this.domUtils.showConfirm(this.translate.instant('core.areyousure'));
        }

        promise.then(() => {
            const responses = [];
            if (this.choice.allowmultiple) {
                this.options.forEach((option) => {
                    if (option.checked) {
                        responses.push(option.id);
                    }
                });
            } else {
                responses.push(this.selectedOption.id);
            }

            const modal = this.domUtils.showModalLoading('core.sending', true);
            this.choiceProvider.submitResponse(this.choice.id, this.choice.name, this.courseId, responses).then(() => {
                // Success!
                // Check completion since it could be configured to complete once the user answers the choice.
                this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
                this.content.scrollToTop();

                // Let's refresh the data.
                return this.refreshContent(false);
            }).catch((message) => {
                this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true);
            }).finally(() => {
                modal.dismiss();
            });
        });
    }

    /**
     * Delete options selected.
     */
    delete(): void {
        this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
            const modal = this.domUtils.showModalLoading('core.sending', true);
            this.choiceProvider.deleteResponses(this.choice.id, this.choice.name, this.courseId).then(() => {
                this.content.scrollToTop();

                // Success! Let's refresh the data.
                return this.refreshContent(false);
            }).catch((message) => {
                this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true);
            }).finally(() => {
                modal.dismiss();
            });
        }).catch(() => {
            // Ingore cancelled modal.
        });
    }

    /**
     * Performs the sync of the activity.
     *
     * @return {Promise<any>} Promise resolved when done.
     */
    protected sync(): Promise<any> {
        return this.choiceSync.syncChoice(this.choice.id, this.userId);
    }

    /**
     * Checks if sync has succeed from result sync data.
     *
     * @param  {any} result Data returned on the sync function.
     * @return {boolean} Whether it succeed or not.
     */
    protected hasSyncSucceed(result: any): boolean {
        return result.updated;
    }
}