// (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, ViewChild, OnDestroy } from '@angular/core';
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
import { CoreEventsProvider } from '@providers/events';
import { CoreAppProvider } from '@providers/app';
import { CoreSplitViewComponent } from '@components/split-view/split-view';

/**
 * Page that displays the list of calendar events.
 */
@IonicPage({ segment: 'addon-calendar-list' })
@Component({
    selector: 'page-addon-calendar-list',
    templateUrl: 'list.html',
})
export class AddonCalendarListPage implements OnDestroy {
    @ViewChild(Content) content: Content;
    @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;

    protected daysLoaded = 0;
    protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
    protected categoriesRetrieved = false;
    protected getCategories = false;
    protected allCourses = {
        id: -1,
        fullname: this.translate.instant('core.fulllistofcourses'),
        category: -1
    };
    protected categories = {};
    protected siteHomeId: number;
    protected obsDefaultTimeChange: any;
    protected eventId: number;

    courses: any[];
    eventsLoaded = false;
    events = [];
    notificationsEnabled = false;
    filteredEvents = [];
    canLoadMore = false;
    filter = {
        course: this.allCourses
    };

    constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
            private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
            private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider,
            localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
            eventsProvider: CoreEventsProvider, private navCtrl: NavController, appProvider: CoreAppProvider) {

        this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
        this.notificationsEnabled = localNotificationsProvider.isAvailable();
        if (this.notificationsEnabled) {
            // Re-schedule events if default time changes.
            this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
                calendarProvider.scheduleEventsNotifications(this.events);
            }, sitesProvider.getCurrentSiteId());
        }

        this.eventId = navParams.get('eventId') || false;
    }

    /**
     * View loaded.
     */
    ionViewDidLoad(): void {
        if (this.eventId) {
            // There is an event to load, open the event in a new state.
            this.gotoEvent(this.eventId);
        }

        this.fetchData().then(() => {
            if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
                // Take first and load it.
                this.gotoEvent(this.events[0].id);
            }
        }).finally(() => {
            this.eventsLoaded = true;
        });
    }

    /**
     * Fetch all the data required for the view.
     *
     * @param {boolean} [refresh] Empty events array first.
     * @return {Promise<any>} Promise resolved when done.
     */
    fetchData(refresh: boolean = false): Promise<any> {
        this.daysLoaded = 0;
        this.emptyEventsTimes = 0;

        // Load courses for the popover.
        return this.coursesProvider.getUserCourses(false).then((courses) => {
            // Add "All courses".
            courses.unshift(this.allCourses);
            this.courses = courses;

            return this.fetchEvents(refresh);
        });
    }

    /**
     * Fetches the events and updates the view.
     *
     * @param {boolean} [refresh] Empty events array first.
     * @return {Promise<any>} Promise resolved when done.
     */
    fetchEvents(refresh: boolean = false): Promise<any> {
        return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
            this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
            if (events.length === 0) {
                this.emptyEventsTimes++;
                if (this.emptyEventsTimes > 5) { // Stop execution if we retrieve empty list 6 consecutive times.
                    this.canLoadMore = false;
                    if (refresh) {
                        this.events = [];
                        this.filteredEvents = [];
                    }
                } else {
                    // No events returned, load next events.
                    return this.fetchEvents();
                }
            } else {
                // Sort the events by timestart, they're ordered by id.
                events.sort((a, b) => {
                    return a.timestart - b.timestart;
                });

                events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
                this.getCategories = this.shouldLoadCategories(events);

                if (refresh) {
                    this.events = events;
                } else {
                    // Filter events with same ID. Repeated events are returned once per WS call, show them only once.
                    this.events = this.utils.mergeArraysWithoutDuplicates(this.events, events, 'id');
                }
                this.filteredEvents = this.getFilteredEvents();
                this.canLoadMore = true;

                // Schedule notifications for the events retrieved (might have new events).
                this.calendarProvider.scheduleEventsNotifications(this.events);
            }

            // Resize the content so infinite loading is able to calculate if it should load more items or not.
            // @todo: Infinite loading is not working if content is not high enough.
            this.content.resize();
        }).catch((error) => {
            this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
            this.canLoadMore = false; // Set to false to prevent infinite calls with infinite-loading.
        }).then(() => {
            // Success retrieving events. Get categories if needed.
            if (this.getCategories) {
                this.getCategories = false;

                return this.loadCategories();
            }
        });
    }

    /**
     * Function to load more events.
     *
     * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
     * @return {Promise<any>} Resolved when done.
     */
    loadMoreEvents(infiniteComplete?: any): Promise<any> {
        return this.fetchEvents().finally(() => {
            infiniteComplete && infiniteComplete();
        });
    }

    /**
     * Get filtered events.
     *
     * @return {any[]} Filtered events.
     */
    protected getFilteredEvents(): any[] {
        if (this.filter.course.id == -1) {
            // No filter, display everything.
            return this.events;
        }

        return this.events.filter(this.shouldDisplayEvent.bind(this));
    }

    /**
     * Check if an event should be displayed based on the filter.
     *
     * @param {any} event Event object.
     * @return {boolean} Whether it should be displayed.
     */
    protected shouldDisplayEvent(event: any): boolean {
        if (event.eventtype == 'user' || event.eventtype == 'site') {
            // User or site event, display it.
            return true;
        }

        if (event.eventtype == 'category') {
            if (!event.categoryid || !Object.keys(this.categories).length) {
                // We can't tell if the course belongs to the category, display them all.
                return true;
            }
            if (event.categoryid == this.filter.course.category) {
                // The event is in the same category as the course, display it.
                return true;
            }

            // Check parent categories.
            let category = this.categories[this.filter.course.category];
            while (category) {
                if (!category.parent) {
                    // Category doesn't have parent, stop.
                    break;
                }

                if (event.categoryid == category.parent) {
                    return true;
                }
                category = this.categories[category.parent];
            }

            return false;
        }

        // Show the event if it is from site home or if it matches the selected course.
        return event.courseid === this.siteHomeId || event.courseid == this.filter.course.id;
    }

    /**
     * Returns if the current state should load categories or not.
     * @param {any[]} events Events to parse.
     * @return {boolean}  True if categories should be loaded.
     */
    protected shouldLoadCategories(events: any[]): boolean {
        if (this.categoriesRetrieved || this.getCategories) {
            // Use previous value
            return this.getCategories;
        }

        // Categories not loaded yet. We should get them if there's any category event.
        const found = events.some((event) => event.categoryid != 'undefined' && event.categoryid > 0);

        return found || this.getCategories;
    }

    /**
     * Load categories to be able to filter events.
     *
     * @return {Promise<any>} Promise resolved when done.
     */
    protected loadCategories(): Promise<any> {
        return this.coursesProvider.getCategories(0, true).then((cats) => {
            this.categoriesRetrieved = true;
            this.categories = {};
            // Index categories by ID.
            cats.forEach((category) => {
                this.categories[category.id] = category;
            });
        }).catch(() => {
            // Ignore errors.
        });
    }

    /**
     * Refresh the events.
     *
     * @param {any} refresher Refresher.
     */
    refreshEvents(refresher: any): void {
        const promises = [];

        promises.push(this.calendarProvider.invalidateEventsList(this.courses));

        if (this.categoriesRetrieved) {
            promises.push(this.coursesProvider.invalidateCategories(0, true));
            this.categoriesRetrieved = false;
        }

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

    /**
     * Show the context menu.
     *
     * @param {MouseEvent} event Event.
     */
    openCourseFilter(event: MouseEvent): void {
        const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
            courses: this.courses,
            courseId: this.filter.course.id
        });
        popover.onDidDismiss((course) => {
            if (course) {
                this.filter.course = course;
                this.domUtils.scrollToTop(this.content);

                this.filteredEvents = this.getFilteredEvents();
            }
        });
        popover.present({
            ev: event
        });
    }

    /**
     * Open calendar events settings.
     */
    openSettings(): void {
        this.navCtrl.push('AddonCalendarSettingsPage');
    }

    /**
     * Navigate to a particular event.
     *
     * @param {number} eventId Event to load.
     */
    gotoEvent(eventId: number): void {
        this.eventId = eventId;
        this.splitviewCtrl.push('AddonCalendarEventPage', { id: eventId });
    }

    /**
     * Page destroyed.
     */
    ngOnDestroy(): void {
        this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
    }
}
