// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreApp } from '@providers/app';
import { CoreEvents } from '@providers/events';
import { CoreFilepool } from '@providers/filepool';
import { CoreWSExternalFile } from '@providers/ws';
import { CoreDomUtils } from '@providers/utils/dom';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { CoreH5P } from '@core/h5p/providers/h5p';
import { CoreH5PDisplayOptions } from '@core/h5p/classes/core';
import { CoreH5PHelper } from '@core/h5p/classes/helper';
import { CoreConstants } from '@core/constants';
import { CoreSite } from '@classes/site';

import {
    AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData, AddonModH5PActivityAccessInfo
} from '../../providers/h5pactivity';

/**
 * Component that displays an H5P activity entry page.
 */
@Component({
    selector: 'addon-mod-h5pactivity-index',
    templateUrl: 'addon-mod-h5pactivity-index.html',
})
export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActivityComponent {
    component = AddonModH5PActivityProvider.COMPONENT;
    moduleName = 'h5pactivity';

    h5pActivity: AddonModH5PActivityData; // The H5P activity object.
    accessInfo: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
    deployedFile: CoreWSExternalFile; // The H5P deployed file.

    stateMessage: string; // Message about the file state.
    downloading: boolean; // Whether the H5P file is being downloaded.
    needsDownload: boolean; // Whether the file needs to be downloaded.
    percentage: string; // Download/unzip percentage.
    progressMessage: string; // Message about download/unzip.
    playing: boolean; // Whether the package is being played.
    displayOptions: CoreH5PDisplayOptions; // Display options for the package.
    onlinePlayerUrl: string; // URL to play the package in online.
    fileUrl: string; // The fileUrl to use to play the package.
    state: string; // State of the file.
    siteCanDownload: boolean;

    protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
    protected site: CoreSite;
    protected observer;

    constructor(injector: Injector,
            @Optional() protected content: Content) {
        super(injector, content);

        this.site = this.sitesProvider.getCurrentSite();
        this.siteCanDownload = this.site.canDownloadFiles() && !CoreH5P.instance.isOfflineDisabledInSite();
    }

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

        this.loadContent();
    }

    /**
     * Check the completion.
     */
    protected checkCompletion(): void {
        this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata);
    }

    /**
     * Get the activity data.
     *
     * @param refresh If it's refreshing content.
     * @param sync If it should try to sync.
     * @param showErrors If show errors to the user of hide them.
     * @return Promise resolved when done.
     */
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
        try {
            this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(this.courseId, this.module.id);

            this.dataRetrieved.emit(this.h5pActivity);
            this.description = this.h5pActivity.intro;
            this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions);

            if (this.h5pActivity.package && this.h5pActivity.package[0]) {
                // The online player should use the original file, not the trusted one.
                this.onlinePlayerUrl = CoreH5P.instance.h5pPlayer.calculateOnlinePlayerUrl(
                            this.site.getURL(), this.h5pActivity.package[0].fileurl, this.displayOptions);
            }

            await Promise.all([
                this.fetchAccessInfo(),
                this.fetchDeployedFileData(),
            ]);

            if (!this.siteCanDownload || this.state == CoreConstants.DOWNLOADED) {
                // Cannot download the file or already downloaded, play the package directly.
                this.play();
            }
        } finally {
            this.fillContextMenu(refresh);
        }
    }

    /**
     * Fetch the access info and store it in the right variables.
     *
     * @return Promise resolved when done.
     */
    protected async fetchAccessInfo(): Promise<void> {
        this.accessInfo = await AddonModH5PActivity.instance.getAccessInformation(this.h5pActivity.id);
    }

    /**
     * Fetch the deployed file data if needed and store it in the right variables.
     *
     * @return Promise resolved when done.
     */
    protected async fetchDeployedFileData(): Promise<void> {
        if (!this.siteCanDownload) {
            // Cannot download the file, no need to fetch the file data.
            return;
        }

        this.deployedFile = await AddonModH5PActivity.instance.getDeployedFile(this.h5pActivity, {
            displayOptions: this.displayOptions,
            siteId: this.siteId,
        });

        this.fileUrl = this.deployedFile.fileurl;

        // Listen for changes in the state.
        const eventName = await CoreFilepool.instance.getFileEventNameByUrl(this.siteId, this.deployedFile.fileurl);

        if (!this.observer) {
            this.observer = CoreEvents.instance.on(eventName, () => {
                this.calculateFileState();
            });
        }

        await this.calculateFileState();
    }

    /**
     * Calculate the state of the deployed file.
     *
     * @return Promise resolved when done.
     */
    protected async calculateFileState(): Promise<void> {
        this.state = await CoreFilepool.instance.getFileStateByUrl(this.siteId, this.deployedFile.fileurl,
                this.deployedFile.timemodified);

        this.showFileState();
    }

    /**
     * Perform the invalidate content function.
     *
     * @return Resolved when done.
     */
    protected invalidateContent(): Promise<any> {
       return AddonModH5PActivity.instance.invalidateActivityData(this.courseId);
    }

    /**
     * Displays some data based on the state of the main file.
     */
    protected showFileState(): void {

        if (this.state == CoreConstants.OUTDATED) {
            this.stateMessage = 'addon.mod_h5pactivity.filestateoutdated';
            this.needsDownload = true;
        } else if (this.state == CoreConstants.NOT_DOWNLOADED) {
            this.stateMessage = 'addon.mod_h5pactivity.filestatenotdownloaded';
            this.needsDownload = true;
        } else if (this.state == CoreConstants.DOWNLOADING) {
            this.stateMessage = '';

            if (!this.downloading) {
                // It's being downloaded right now but the view isn't tracking it. "Restore" the download.
                this.downloadDeployedFile().then(() => {
                    this.play();
                });
            }
        } else {
            this.stateMessage = '';
            this.needsDownload = false;
        }
    }

    /**
     * Download the file and play it.
     *
     * @param e Click event.
     * @return Promise resolved when done.
     */
    async downloadAndPlay(e: MouseEvent): Promise<void> {
        e && e.preventDefault();
        e && e.stopPropagation();

        if (!CoreApp.instance.isOnline()) {
            CoreDomUtils.instance.showErrorModal('core.networkerrormsg', true);

            return;
        }

        try {
            // Confirm the download if needed.
            await CoreDomUtils.instance.confirmDownloadSize({ size: this.deployedFile.filesize, total: true });

            await this.downloadDeployedFile();

            if (!this.isDestroyed) {
                this.play();
            }

        } catch (error) {
            if (CoreDomUtils.instance.isCanceledError(error) || this.isDestroyed) {
                // User cancelled or view destroyed, stop.
                return;
            }

            CoreDomUtils.instance.showErrorModalDefault(error, 'core.errordownloading', true);
        }
    }

    /**
     * Download athe H5P deployed file or restores an ongoing download.
     *
     * @return Promise resolved when done.
     */
    protected async downloadDeployedFile(): Promise<void> {
        this.downloading = true;
        this.progressMessage = 'core.downloading';

        try {
            await CoreFilepool.instance.downloadUrl(this.siteId, this.deployedFile.fileurl, false, this.component, this.componentId,
                    this.deployedFile.timemodified, (data) => {

                if (!data) {
                    return;
                }

                if (data.message) {
                    // Show a message.
                    this.progressMessage = data.message;
                    this.percentage = undefined;
                } else if (typeof data.loaded != 'undefined') {
                    if (this.progressMessage == 'core.downloading') {
                        // Downloading package.
                        this.percentage = (Number(data.loaded / this.deployedFile.filesize) * 100).toFixed(1);
                    } else if (typeof data.total != 'undefined') {
                        // Unzipping package.
                        this.percentage = (Number(data.loaded / data.total) * 100).toFixed(1);
                    } else {
                        this.percentage = undefined;
                    }
                } else {
                    this.percentage = undefined;
                }
            });

        } finally {
            this.progressMessage = undefined;
            this.percentage = undefined;
            this.downloading = false;
        }
    }

    /**
     * Play the package.
     */
    play(): void {
        this.playing = true;

        // Mark the activity as viewed.
        AddonModH5PActivity.instance.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId);
    }

    /**
     * Component destroyed.
     */
    ngOnDestroy(): void {
        this.observer && this.observer.off();
    }
}