// (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, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core';
import { FormControl } from '@angular/forms';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreEditorRichTextEditorComponent } from '@core/editor/components/rich-text-editor/rich-text-editor.ts';
import { AddonModForumProvider } from '../../providers/forum';
import { AddonModForumOfflineProvider } from '../../providers/offline';
import { AddonModForumHelperProvider } from '../../providers/helper';
import { AddonModForumSyncProvider } from '../../providers/sync';

/**
 * Page that displays the new discussion form.
 */
@IonicPage({ segment: 'addon-mod-forum-new-discussion' })
@Component({
    selector: 'page-addon-mod-forum-new-discussion',
    templateUrl: 'new-discussion.html',
})
export class AddonModForumNewDiscussionPage implements OnDestroy {

    @ViewChild('newDiscFormEl') formElement: ElementRef;
    @ViewChild(CoreEditorRichTextEditorComponent) messageEditor: CoreEditorRichTextEditorComponent;

    component = AddonModForumProvider.COMPONENT;
    messageControl = new FormControl();
    groupsLoaded = false;
    showGroups = false;
    hasOffline = false;
    canCreateAttachments = true; // Assume we can by default.
    canPin = false;
    forum: any;
    showForm = false;
    groups = [];
    groupIds = [];
    newDiscussion = {
        subject: '',
        message: null, // Null means empty or just white space.
        postToAllGroups: false,
        groupId: 0,
        subscribe: true,
        pin: false,
        files: []
    };
    advanced = false; // Display all form fields.
    accessInfo: any = {};

    protected courseId: number;
    protected cmId: number;
    protected forumId: number;
    protected timeCreated: number;
    protected syncId: string;
    protected syncObserver: any;
    protected isDestroyed = false;
    protected originalData: any;
    protected forceLeave = false;

    constructor(navParams: NavParams,
            private navCtrl: NavController,
            private translate: TranslateService,
            private domUtils: CoreDomUtilsProvider,
            private eventsProvider: CoreEventsProvider,
            private groupsProvider: CoreGroupsProvider,
            private sitesProvider: CoreSitesProvider,
            private syncProvider: CoreSyncProvider,
            private uploaderProvider: CoreFileUploaderProvider,
            private textUtils: CoreTextUtilsProvider,
            private utils: CoreUtilsProvider,
            private forumProvider: AddonModForumProvider,
            private forumOffline: AddonModForumOfflineProvider,
            private forumSync: AddonModForumSyncProvider,
            private forumHelper: AddonModForumHelperProvider,
            @Optional() private svComponent: CoreSplitViewComponent) {
        this.courseId = navParams.get('courseId');
        this.cmId = navParams.get('cmId');
        this.forumId = navParams.get('forumId');
        this.timeCreated = navParams.get('timeCreated');
    }

    /**
     * Component being initialized.
     */
    ngOnInit(): void {
        this.fetchDiscussionData().finally(() => {
            this.groupsLoaded = true;
        });
    }

    /**
     * User entered the page that contains the component.
     */
    ionViewDidEnter(): void {
        if (this.syncObserver) {
            // Already setup.
            return;
        }

        // Refresh data if this discussion is synchronized automatically.
        this.syncObserver = this.eventsProvider.on(AddonModForumSyncProvider.AUTO_SYNCED, (data) => {
            if (data.forumId == this.forumId && data.userId == this.sitesProvider.getCurrentSiteUserId()) {
                this.domUtils.showAlertTranslated('core.notice', 'core.contenteditingsynced');
                this.returnToDiscussions();
            }
        }, this.sitesProvider.getCurrentSiteId());

        // Trigger view event, to highlight the current opened discussion in the split view.
        this.eventsProvider.trigger(AddonModForumProvider.VIEW_DISCUSSION_EVENT, {
            forumId: this.forumId,
            discussion: -this.timeCreated
        }, this.sitesProvider.getCurrentSiteId());
    }

    /**
     * Fetch if forum uses groups and the groups it uses.
     *
     * @param refresh Whether we're refreshing data.
     * @return Promise resolved when done.
     */
    protected fetchDiscussionData(refresh?: boolean): Promise<any> {
        return this.groupsProvider.getActivityGroupMode(this.cmId).then((mode) => {
            const promises = [];

            if (mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS) {
                promises.push(this.groupsProvider.getActivityAllowedGroups(this.cmId).then((result) => {
                    let promise;
                    if (mode === CoreGroupsProvider.VISIBLEGROUPS) {
                        // We need to check which of the returned groups the user can post to.
                        promise = this.validateVisibleGroups(result.groups);
                    } else {
                        // WS already filters groups, no need to do it ourselves. Add "All participants" if needed.
                        promise = this.addAllParticipantsOption(result.groups, true);
                    }

                    return promise.then((forumGroups) => {
                        if (forumGroups.length > 0) {
                            this.groups = forumGroups;
                            this.groupIds = forumGroups.map((group) => group.id).filter((id) => id > 0);
                            // Do not override group id.
                            this.newDiscussion.groupId = this.newDiscussion.groupId || forumGroups[0].id;
                            this.showGroups = true;
                            if (this.groupIds.length <= 1) {
                                this.newDiscussion.postToAllGroups = false;
                            }
                        } else {
                            const message = mode === CoreGroupsProvider.SEPARATEGROUPS ?
                                    'addon.mod_forum.cannotadddiscussionall' : 'addon.mod_forum.cannotadddiscussion';

                            return Promise.reject(this.translate.instant(message));
                        }
                    });
                }));
            } else {
                this.showGroups = false;
                this.newDiscussion.postToAllGroups = false;

                // Use the canAddDiscussion WS to check if the user can add attachments and pin discussions.
                promises.push(this.forumProvider.canAddDiscussionToAll(this.forumId).then((response) => {
                    this.canPin = !!response.canpindiscussions;
                    this.canCreateAttachments = !!response.cancreateattachment;
                }).catch(() => {
                    // Ignore errors, use default values.
                }));
            }

            // Get forum.
            promises.push(this.forumProvider.getForum(this.courseId, this.cmId).then((forum) => {
                this.forum = forum;
            }));

            // Get access information.
            promises.push(this.forumProvider.getAccessInformation(this.forumId).then((accessInfo) => {
                this.accessInfo = accessInfo;
            }));

            return Promise.all(promises);
        }).then(() => {
            // If editing a discussion, get offline data.
            if (this.timeCreated && !refresh) {
                this.syncId = this.forumSync.getForumSyncId(this.forumId);

                return this.forumSync.waitForSync(this.syncId).then(() => {
                    // Do not block if the scope is already destroyed.
                    if (!this.isDestroyed) {
                        this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId);
                    }

                    return this.forumOffline.getNewDiscussion(this.forumId, this.timeCreated).then((discussion) => {
                        this.hasOffline = true;
                        discussion.options = discussion.options || {};
                        if (discussion.groupid == AddonModForumProvider.ALL_GROUPS) {
                            this.newDiscussion.groupId = this.groups[0].id;
                            this.newDiscussion.postToAllGroups = true;
                        } else {
                            this.newDiscussion.groupId = discussion.groupid;
                            this.newDiscussion.postToAllGroups = false;
                        }
                        this.newDiscussion.subject = discussion.subject;
                        this.newDiscussion.message = discussion.message;
                        this.newDiscussion.subscribe = discussion.options.discussionsubscribe;
                        this.newDiscussion.pin = discussion.options.discussionpinned;
                        this.messageControl.setValue(discussion.message);

                        // Treat offline attachments if any.
                        let promise;
                        if (discussion.options.attachmentsid && discussion.options.attachmentsid.offline) {
                            promise = this.forumHelper.getNewDiscussionStoredFiles(this.forumId, this.timeCreated).then((files) => {
                                this.newDiscussion.files = files;
                            });
                        }

                        return Promise.resolve(promise).then(() => {
                            // Show advanced fields by default if any of them has not the default value.
                            if (!this.newDiscussion.subscribe || this.newDiscussion.pin || this.newDiscussion.files.length ||
                                    this.groups.length > 0 && this.newDiscussion.groupId != this.groups[0].id ||
                                    this.newDiscussion.postToAllGroups) {
                                this.advanced = true;
                            }
                        });
                    });
                });
            }
        }).then(() => {
            if (!this.originalData) {
                // Initialize original data.
                this.originalData = {
                    subject: this.newDiscussion.subject,
                    message: this.newDiscussion.message,
                    files: this.newDiscussion.files.slice(),
                };
            }
            this.showForm = true;
        }).catch((message) => {
            this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.errorgetgroups', true);
            this.showForm = false;
        });
    }

    /**
     * Validate which of the groups returned by getActivityAllowedGroups in visible groups should be shown to post to.
     *
     * @param forumGroups Forum groups.
     * @return Promise resolved with the list of groups.
     */
    protected validateVisibleGroups(forumGroups: any[]): Promise<any[]> {
        // We first check if the user can post to all the groups.
        return this.forumProvider.canAddDiscussionToAll(this.forumId).catch(() => {
            // The call failed, let's assume he can't.
            return {
                status: false,
                canpindiscussions: false,
                cancreateattachment: true
            };
        }).then((response) => {
            this.canPin = !!response.canpindiscussions;
            this.canCreateAttachments = !!response.cancreateattachment;

            if (response.status) {
                // The user can post to all groups, add the "All participants" option and return them all.
                return this.addAllParticipantsOption(forumGroups, false);
            } else {
                // The user can't post to all groups, let's check which groups he can post to.
                const promises = [];
                const filtered = [];

                forumGroups.forEach((group) => {
                    promises.push(this.forumProvider.canAddDiscussion(this.forumId, group.id).catch(() => {
                        /* The call failed, let's return true so the group is shown. If the user can't post to
                           it an error will be shown when he tries to add the discussion. */
                        return {
                            status: true
                        };
                    }).then((response) => {
                        if (response.status) {
                            filtered.push(group);
                        }
                    }));
                });

                return Promise.all(promises).then(() => {
                    return filtered;
                });
            }
        });
    }

    /**
     * Filter forum groups, returning only those that are inside user groups.
     *
     * @param forumGroups Forum groups.
     * @param userGroups User groups.
     * @return Filtered groups.
     */
    protected filterGroups(forumGroups: any[], userGroups: any[]): any[] {
        const filtered = [];
        const userGroupsIds = userGroups.map((g) => g.id);

        forumGroups.forEach((fg) => {
            if (userGroupsIds.indexOf(fg.id) > -1) {
                filtered.push(fg);
            }
        });

        return filtered;
    }

    /**
     * Add the "All participants" option to a list of groups if the user can add a discussion to all participants.
     *
     * @param groups Groups.
     * @param check True to check if the user can add a discussion to all participants.
     * @return Promise resolved with the list of groups.
     */
    protected addAllParticipantsOption(groups: any[], check: boolean): Promise<any[]> {
        if (!this.forumProvider.isAllParticipantsFixed()) {
            // All participants has a bug, don't add it.
            return Promise.resolve(groups);
        }

        let promise;

        if (check) {
            // We need to check if the user can add a discussion to all participants.
            promise = this.forumProvider.canAddDiscussionToAll(this.forumId).then((response) => {
                this.canPin = !!response.canpindiscussions;
                this.canCreateAttachments = !!response.cancreateattachment;

                return response.status;
            }).catch(() => {
                // The call failed, let's assume he can't.
                return false;
            });
        } else {
            // No need to check, assume the user can.
            promise = Promise.resolve(true);
        }

        return promise.then((canAdd) => {
            if (canAdd) {
                groups.unshift({
                    courseid: this.courseId,
                    id: AddonModForumProvider.ALL_PARTICIPANTS,
                    name: this.translate.instant('core.allparticipants')
                });
            }

            return groups;
        });
    }

    /**
     * Pull to refresh.
     *
     * @param refresher Refresher.
     */
    refreshGroups(refresher: any): void {
        const promises = [
            this.groupsProvider.invalidateActivityGroupMode(this.cmId),
            this.groupsProvider.invalidateActivityAllowedGroups(this.cmId),
            this.forumProvider.invalidateCanAddDiscussion(this.forumId),
        ];

        Promise.all(promises).finally(() => {
            this.fetchDiscussionData(true).finally(() => {
                refresher.complete();
            });
        });
    }

    /**
     * Convenience function to update or return to discussions depending on device.
     *
     * @param discussionIds Ids of the new discussions.
     * @param discTimecreated The time created of the discussion (if offline).
     */
    protected returnToDiscussions(discussionIds?: number[], discTimecreated?: number): void {
        const data: any = {
            forumId: this.forumId,
            cmId: this.cmId,
            discussionIds: discussionIds,
            discTimecreated: discTimecreated
        };
        this.eventsProvider.trigger(AddonModForumProvider.NEW_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId());

        // Delete the local files from the tmp folder.
        this.uploaderProvider.clearTmpFiles(this.newDiscussion.files);

        if (this.svComponent && this.svComponent.isOn()) {
            // Empty form.
            this.hasOffline = false;
            this.newDiscussion.subject = '';
            this.newDiscussion.message = null;
            this.newDiscussion.files = [];
            this.newDiscussion.postToAllGroups = false;
            this.messageEditor.clearText();
            this.originalData = this.utils.clone(this.newDiscussion);
            this.forceLeave = true; // Avoid asking for confirmation.

            // Trigger view event, to highlight the current opened discussion in the split view.
            this.eventsProvider.trigger(AddonModForumProvider.VIEW_DISCUSSION_EVENT, {
                forumId: this.forumId,
                discussion: 0
            }, this.sitesProvider.getCurrentSiteId());
        } else {
            this.forceLeave = true; // Avoid asking for confirmation.
            this.navCtrl.pop();
        }
    }

    /**
     * Message changed.
     *
     * @param text The new text.
     */
    onMessageChange(text: string): void {
        this.newDiscussion.message = text;
    }

    /**
     * Add a new discussion.
     */
    add(): void {
        const forumName = this.forum.name;
        const subject = this.newDiscussion.subject;
        let  message = this.newDiscussion.message;
        const pin = this.newDiscussion.pin;
        const attachments = this.newDiscussion.files;
        const discTimecreated = this.timeCreated || Date.now();
        const options: any = {
            discussionsubscribe: !!this.newDiscussion.subscribe
        };

        if (!subject) {
            this.domUtils.showErrorModal('addon.mod_forum.erroremptysubject', true);

            return;
        }
        if (!message) {
            this.domUtils.showErrorModal('addon.mod_forum.erroremptymessage', true);

            return;
        }

        const modal = this.domUtils.showModalLoading('core.sending', true);

        // Add some HTML to the message if needed.
        message = this.textUtils.formatHtmlLines(message);

        if (pin) {
            options.discussionpinned = true;
        }

        const groupIds = this.newDiscussion.postToAllGroups ? this.groupIds : [this.newDiscussion.groupId];

        this.forumHelper.addNewDiscussion(this.forumId, forumName, this.courseId, subject, message, attachments, options, groupIds,
                discTimecreated).then((discussionIds) => {
            if (discussionIds) {
                // Data sent to server, delete stored files (if any).
                this.forumHelper.deleteNewDiscussionStoredFiles(this.forumId, discTimecreated);

                this.eventsProvider.trigger(CoreEventsProvider.ACTIVITY_DATA_SENT, { module: 'forum' });
            }

            if (discussionIds && discussionIds.length < groupIds.length) {
                // Some discussions could not be created.
                this.domUtils.showErrorModalDefault(null, 'addon.mod_forum.errorposttoallgroups', true);
            }

            this.domUtils.triggerFormSubmittedEvent(this.formElement, !!discussionIds, this.sitesProvider.getCurrentSiteId());

            this.returnToDiscussions(discussionIds, discTimecreated);
        }).catch((message) => {
            this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.cannotcreatediscussion', true);
        }).finally(() => {
            modal.dismiss();
        });
    }

    /**
     * Discard an offline saved discussion.
     */
    discard(): void {
        this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
            const promises = [];

            promises.push(this.forumOffline.deleteNewDiscussion(this.forumId, this.timeCreated));
            promises.push(this.forumHelper.deleteNewDiscussionStoredFiles(this.forumId, this.timeCreated).catch(() => {
                // Ignore errors, maybe there are no files.
            }));

            return Promise.all(promises).then(() => {
                this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId());

                this.returnToDiscussions();
            });
        }).catch(() => {
            // Cancelled.
        });
    }

    /**
     * Show or hide advanced form fields.
     */
    toggleAdvanced(): void {
        this.advanced = !this.advanced;
    }

    /**
     * Check if we can leave the page or not.
     *
     * @return Resolved if we can leave it, rejected if not.
     */
    async ionViewCanLeave(): Promise<void> {
        if (this.forceLeave) {
            return;
        }

        if (this.forumHelper.hasPostDataChanged(this.newDiscussion, this.originalData)) {
            // Show confirmation if some data has been modified.
            await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
        }

        // Delete the local files from the tmp folder.
        this.uploaderProvider.clearTmpFiles(this.newDiscussion.files);

        if (this.formElement) {
            this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId());
        }
    }

    /**
     * Runs when the page is about to leave and no longer be the active page.
     */
    ionViewWillLeave(): void {
        this.syncObserver && this.syncObserver.off();
        delete this.syncObserver;
    }

    /**
     * Page destroyed.
     */
    ngOnDestroy(): void {
        if (this.syncId) {
            this.syncProvider.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId);
        }
        this.isDestroyed = true;
    }
}