// (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, ViewChild } from '@angular/core';
import { Content, NavController } from 'ionic-angular';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { AddonModAssignProvider } from '../../providers/assign';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignOfflineProvider } from '../../providers/assign-offline';
import { AddonModAssignSyncProvider } from '../../providers/assign-sync';
import * as moment from 'moment';
import { AddonModAssignSubmissionComponent } from '../submission/submission';

/**
 * Component that displays an assignment.
 */
@Component({
    selector: 'addon-mod-assign-index',
    templateUrl: 'addon-mod-assign-index.html',
})
export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityComponent {
    @ViewChild(AddonModAssignSubmissionComponent) submissionComponent: AddonModAssignSubmissionComponent;

    component = AddonModAssignProvider.COMPONENT;
    moduleName = 'assign';

    assign: any; // The assign object.
    canViewSubmissions: boolean; // Whether the user can view all submissions.
    timeRemaining: string; // Message about time remaining to submit.
    lateSubmissions: string; // Message about late submissions.
    showNumbers = true; // Whether to show number of submissions with each status.
    summary: any; // The summary.
    needsGradingAvalaible: boolean; // Whether we can see the submissions that need grading.

    // Status.
    submissionStatusSubmitted = AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED;
    submissionStatusDraft = AddonModAssignProvider.SUBMISSION_STATUS_DRAFT;
    needGrading = AddonModAssignProvider.NEED_GRADING;

    protected userId: number; // Current user ID.
    protected syncEventName = AddonModAssignSyncProvider.AUTO_SYNCED;

    // Observers.
    protected savedObserver;
    protected submittedObserver;
    protected gradedObserver;

    constructor(injector: Injector, protected assignProvider: AddonModAssignProvider, @Optional() content: Content,
            protected assignHelper: AddonModAssignHelperProvider, protected assignOffline: AddonModAssignOfflineProvider,
            protected syncProvider: AddonModAssignSyncProvider, protected timeUtils: CoreTimeUtilsProvider,
            protected groupsProvider: CoreGroupsProvider, protected navCtrl: NavController) {
        super(injector, content);
    }

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

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

        this.loadContent(false, true).then(() => {
            this.assignProvider.logView(this.assign.id).then(() => {
                this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
            }).catch(() => {
                // Ignore errors.
            });

            if (!this.canViewSubmissions) {
                // User can only see his submission, log view the user submission.
                this.assignProvider.logSubmissionView(this.assign.id).catch(() => {
                    // Ignore errors.
                });
            } else {
                // User can see all submissions, log grading view.
                this.assignProvider.logGradingView(this.assign.id).catch(() => {
                    // Ignore errors.
                });
            }
        });

        // Listen to events.
        this.savedObserver = this.eventsProvider.on(AddonModAssignProvider.SUBMISSION_SAVED_EVENT, (data) => {
            if (this.assign && data.assignmentId == this.assign.id && data.userId == this.userId) {
                // Assignment submission saved, refresh data.
                this.showLoadingAndRefresh(true, false);
            }
        }, this.siteId);

        this.submittedObserver = this.eventsProvider.on(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, (data) => {
            if (this.assign && data.assignmentId == this.assign.id && data.userId == this.userId) {
                // Assignment submitted, check completion.
                this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);

                // Reload data since it can have offline data now.
                this.showLoadingAndRefresh(true, false);
            }
        }, this.siteId);

        this.gradedObserver = this.eventsProvider.on(AddonModAssignProvider.GRADED_EVENT, (data) => {
            if (this.assign && data.assignmentId == this.assign.id && data.userId == this.userId) {
                // Assignment graded, refresh data.
                this.showLoadingAndRefresh(true, false);
            }
        }, this.siteId);
    }

    /**
     * Expand the description.
     */
    expandDescription(ev?: Event): void {
        ev && ev.preventDefault();
        ev && ev.stopPropagation();

        if (this.assign && (this.description || this.assign.introattachments)) {
            this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component,
                    this.module.id, this.assign.introattachments);
        }
    }

    /**
     * Get assignment data.
     *
     * @param {boolean} [refresh=false] If it's refreshing content.
     * @param {boolean} [sync=false] If it should try to sync.
     * @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> {

        // Get assignment data.
        return this.assignProvider.getAssignment(this.courseId, this.module.id).then((assignData) => {
            this.assign = assignData;

            this.dataRetrieved.emit(this.assign);
            this.description = this.assign.intro || this.description;

            if (sync) {
                // Try to synchronize the assign.
                return this.syncActivity(showErrors).catch(() => {
                    // Ignore errors.
                });
            }
        }).then(() => {
            // Check if there's any offline data for this assign.
            return this.assignOffline.hasAssignOfflineData(this.assign.id);
        }).then((hasOffline) => {
            this.hasOffline = hasOffline;

            // Get assignment submissions.
            return this.assignProvider.getSubmissions(this.assign.id).then((data) => {
                const time = this.timeUtils.timestamp();

                this.canViewSubmissions = data.canviewsubmissions;

                if (data.canviewsubmissions) {

                    // Calculate the messages to display about time remaining and late submissions.
                    if (this.assign.duedate > 0) {
                        if (this.assign.duedate - time <= 0) {
                            this.timeRemaining = this.translate.instant('addon.mod_assign.assignmentisdue');
                        } else {
                            this.timeRemaining = this.timeUtils.formatDuration(this.assign.duedate - time, 3);

                            if (this.assign.cutoffdate) {
                                if (this.assign.cutoffdate > time) {
                                    const dateFormat = this.translate.instant('core.dfmediumdate');

                                    this.lateSubmissions = this.translate.instant('addon.mod_assign.latesubmissionsaccepted',
                                            {$a: moment(this.assign.cutoffdate * 1000).format(dateFormat)});
                                } else {
                                    this.lateSubmissions = this.translate.instant('addon.mod_assign.nomoresubmissionsaccepted');
                                }
                            } else {
                                this.lateSubmissions = '';
                            }
                        }
                    } else {
                        this.timeRemaining = '';
                        this.lateSubmissions = '';
                    }

                    // Check if groupmode is enabled to avoid showing wrong numbers.
                    return this.groupsProvider.activityHasGroups(this.assign.cmid).then((hasGroups) => {
                        this.showNumbers = !hasGroups;

                        return this.assignProvider.getSubmissionStatus(this.assign.id).then((response) => {
                            this.summary = response.gradingsummary;

                            this.needsGradingAvalaible = response.gradingsummary.submissionsneedgradingcount > 0 &&
                                    this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.2');
                        });
                    });
                }
            });
        }).then(() => {
            // All data obtained, now fill the context menu.
            this.fillContextMenu(refresh);
        });
    }

    /**
     * Go to view a list of submissions.
     *
     * @param {string} status Status to see.
     * @param {number} count Number of submissions with the status.
     */
    goToSubmissionList(status: string, count: number): void {
        if (typeof status == 'undefined') {
            this.navCtrl.push('AddonModAssignSubmissionListPage', {
                courseId: this.courseId,
                moduleId: this.module.id,
                moduleName: this.moduleName
            });
        } else if (count || !this.showNumbers) {
            this.navCtrl.push('AddonModAssignSubmissionListPage', {
                status: status,
                courseId: this.courseId,
                moduleId: this.module.id,
                moduleName: this.moduleName
            });
        }
    }

    /**
     * Checks if sync has succeed from result sync data.
     *
     * @param {any} result Data returned by the sync function.
     * @return {boolean} If succeed or not.
     */
    protected hasSyncSucceed(result: any): boolean {
        if (result.updated) {
            this.submissionComponent && this.submissionComponent.invalidateAndRefresh();
        }

        return result.updated;
    }

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

        promises.push(this.assignProvider.invalidateAssignmentData(this.courseId));

        if (this.assign) {
            promises.push(this.assignProvider.invalidateAllSubmissionData(this.assign.id));

            if (this.canViewSubmissions) {
                promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id));
            }
        }

        return Promise.all(promises).finally(() => {
            this.submissionComponent && this.submissionComponent.invalidateAndRefresh();
        });
    }

    /**
     * User entered the page that contains the component.
     */
    ionViewDidEnter(): void {
        super.ionViewDidEnter();

        this.submissionComponent && this.submissionComponent.ionViewDidEnter();
    }

    /**
     * User left the page that contains the component.
     */
    ionViewDidLeave(): void {
        super.ionViewDidLeave();

        this.submissionComponent && this.submissionComponent.ionViewDidLeave();
    }

    /**
     * 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.assign && syncEventData.assignId == this.assign.id) {
            if (syncEventData.warnings && syncEventData.warnings.length) {
                // Show warnings.
                this.domUtils.showErrorModal(syncEventData.warnings[0]);
            }

            return true;
        }

        return false;
    }

    /**
     * Performs the sync of the activity.
     *
     * @return {Promise<any>} Promise resolved when done.
     */
    protected sync(): Promise<any> {
        return this.syncProvider.syncAssign(this.assign.id);
    }

    /**
     * Component being destroyed.
     */
    ngOnDestroy(): void {
        super.ngOnDestroy();

        this.savedObserver && this.savedObserver.off();
        this.submittedObserver && this.submittedObserver.off();
        this.gradedObserver && this.gradedObserver.off();
    }
}
