import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { NotifierActionType } from './../models/notifier-action.model';
import { NotifierNotification } from './../models/notifier-notification.model';
import { NotifierQueueService } from './../services/notifier-queue.service';
import { NotifierService } from './../services/notifier.service';
/**
 * Notifier container component
 * ----------------------------
 * This component acts as a wrapper for all notification components; consequently, it is responsible for creating a new notification
 * component and removing an existing notification component. Being more precicely, it also handles side effects of those actions, such as
 * shifting or even completely removing other notifications as well. Overall, this components handles actions coming from the queue service
 * by subscribing to its action stream.
 *
 * Technical sidenote:
 * This component has to be used somewhere in an application to work; it will not inject and create itself automatically, primarily in order
 * to not break the Angular AoT compilation. Moreover, this component (and also the notification components) set their change detection
 * strategy onPush, which means that we handle change detection manually in order to get the best performance. (#perfmatters)
 */
export var NotifierContainerComponent = (function () {
    /**
     * Constructor
     *
     * @param {ChangeDetectorRef}    changeDetector       Change detector, used for manually triggering change detection runs
     * @param {NotifierQueueService} notifierQueueService Notifier queue service
     * @param {NotifierService}      notifierService      Notifier service
     */
    function NotifierContainerComponent(changeDetector, notifierQueueService, notifierService) {
        this.changeDetector = changeDetector;
        this.queueService = notifierQueueService;
        this.config = notifierService.getConfig();
        this.notifications = [];
    }
    /**
     * Component initialization lifecycle hook, connects this component to the action queue, and then handles incoming actions
     */
    NotifierContainerComponent.prototype.ngOnInit = function () {
        var _this = this;
        this.queueServiceSubscription = this.queueService.actionStream.subscribe(function (action) {
            _this.handleAction(action).then(function () {
                _this.queueService.continue();
            });
        });
    };
    /**
     * Component destroyment lifecycle hook, cleans up the observable subsciption
     */
    NotifierContainerComponent.prototype.ngOnDestroy = function () {
        if (this.queueServiceSubscription) {
            this.queueServiceSubscription.unsubscribe();
        }
    };
    /**
     * Event handler, handles clicks on notification dismiss buttons
     *
     * @param {string} notificationId ID of the notification to dismiss
     */
    NotifierContainerComponent.prototype.onNotificationDismiss = function (notificationId) {
        this.queueService.push({
            payload: notificationId,
            type: NotifierActionType.HIDE
        });
    };
    /**
     * Event handler, handles notification ready events
     *
     * @param {NotifierNotificationComponent} notificationComponent Notification component reference
     */
    NotifierContainerComponent.prototype.onNotificationReady = function (notificationComponent) {
        var currentNotification = this.notifications[this.notifications.length - 1]; // Get the latest notification
        currentNotification.component = notificationComponent; // Save the new omponent reference
        this.continueHandleShowAction(currentNotification); // Continue with handling the show action
    };
    /**
     * Handle incoming actions by mapping action types to methods, and then running them
     *
     * @param   {NotifierAction}     action Action object
     * @returns {Promise<undefined>}        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.handleAction = function (action) {
        switch (action.type) {
            case NotifierActionType.SHOW:
                return this.handleShowAction(action);
            case NotifierActionType.HIDE:
                return this.handleHideAction(action);
            case NotifierActionType.HIDE_OLDEST:
                return this.handleHideOldestAction(action);
            case NotifierActionType.HIDE_NEWEST:
                return this.handleHideNewestAction(action);
            case NotifierActionType.HIDE_ALL:
                return this.handleHideAllAction(action);
            default:
                return new Promise(function (resolve, reject) {
                    resolve(); // Ignore unknown action types
                });
        }
    };
    /**
     * Show a new notification
     *
     * We simply add the notification to the list, and then wait until its properly initialized / created / rendered.
     *
     * @param   {NotifierAction}     action Action object
     * @returns {Promise<undefined>}        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.handleShowAction = function (action) {
        var _this = this;
        return new Promise(function (resolve, reject) {
            _this.tempPromiseResolver = resolve; // Save the promise resolve function so that it can be called later on by another method
            _this.addNotificationToList(new NotifierNotification(action.payload));
        });
    };
    /**
     * Continue to show a new notification (after the notification components is initialized / created / rendered).
     *
     * If this is the first (and thus only) notification, we can simply show it. Otherwhise, if stacking is disabled (or a low value), we
     * switch out notifications, in particular we hide the existing one, and then show our new one. Yet, if stacking is enabled, we first
     * shift all older notifications, and then show our new notification. In addition, if there are too many notification on the screen,
     * we hide the oldest one first. Furthermore, if configured, animation overlapping is applied.
     *
     * @param {NotifierNotification} notification New notification to show
     */
    NotifierContainerComponent.prototype.continueHandleShowAction = function (notification) {
        var _this = this;
        // First (which means only one) notification in the list?
        var numberOfNotifications = this.notifications.length;
        if (numberOfNotifications === 1) {
            notification.component.show().then(this.tempPromiseResolver); // Done
        }
        else {
            // Stacking enabled? (stacking value below 2 means stacking is disabled)
            if (this.config.behaviour.stacking === false || this.config.behaviour.stacking < 2) {
                this.notifications[0].component.hide().then(function () {
                    _this.removeNotificationFromList(_this.notifications[0]);
                    notification.component.show().then(_this.tempPromiseResolver); // Done
                });
            }
            else {
                var stepPromises_1 = [];
                // Are there now too many notifications?
                if (numberOfNotifications > this.config.behaviour.stacking) {
                    var oldNotifications_1 = this.notifications.slice(1, numberOfNotifications - 1);
                    // Are animations enabled?
                    if (this.config.animations.enabled) {
                        // Is animation overlap enabled?
                        if (this.config.animations.overlap !== false && this.config.animations.overlap > 0) {
                            stepPromises_1.push(this.notifications[0].component.hide());
                            setTimeout(function () {
                                stepPromises_1.push(_this.shiftNotifications(oldNotifications_1, notification.component.getHeight(), true));
                            }, this.config.animations.hide.speed - this.config.animations.overlap);
                            setTimeout(function () {
                                stepPromises_1.push(notification.component.show());
                            }, this.config.animations.hide.speed + this.config.animations.shift.speed - this.config.animations.overlap);
                        }
                        else {
                            stepPromises_1.push(new Promise(function (resolve, reject) {
                                _this.notifications[0].component.hide().then(function () {
                                    _this.shiftNotifications(oldNotifications_1, notification.component.getHeight(), true).then(function () {
                                        notification.component.show().then(resolve);
                                    });
                                });
                            }));
                        }
                    }
                    else {
                        stepPromises_1.push(this.notifications[0].component.hide());
                        stepPromises_1.push(this.shiftNotifications(oldNotifications_1, notification.component.getHeight(), true));
                        stepPromises_1.push(notification.component.show());
                    }
                }
                else {
                    var oldNotifications_2 = this.notifications.slice(0, numberOfNotifications - 1);
                    // Are animations enabled?
                    if (this.config.animations.enabled) {
                        // Is animation overlap enabled?
                        if (this.config.animations.overlap !== false && this.config.animations.overlap > 0) {
                            stepPromises_1.push(this.shiftNotifications(oldNotifications_2, notification.component.getHeight(), true));
                            setTimeout(function () {
                                stepPromises_1.push(notification.component.show());
                            }, this.config.animations.shift.speed - this.config.animations.overlap);
                        }
                        else {
                            stepPromises_1.push(new Promise(function (resolve, reject) {
                                _this.shiftNotifications(oldNotifications_2, notification.component.getHeight(), true).then(function () {
                                    notification.component.show().then(resolve);
                                });
                            }));
                        }
                    }
                    else {
                        stepPromises_1.push(this.shiftNotifications(oldNotifications_2, notification.component.getHeight(), true));
                        stepPromises_1.push(notification.component.show());
                    }
                }
                Promise.all(stepPromises_1).then(function () {
                    if (numberOfNotifications > _this.config.behaviour.stacking) {
                        _this.removeNotificationFromList(_this.notifications[0]);
                    }
                    _this.tempPromiseResolver();
                }); // Done
            }
        }
    };
    /**
     * Hide an existing notification
     *
     * Fist, we skip everything if there are no notifications at all, or the given notification does not exist. Then, we hide the given
     * notification. If there exist older notifications, we then shift them around to fill the gap. Once both hiding the given notification
     * and shifting the older notificaitons is done, the given notification gets finally removed (from the DOM).
     *
     * @param   {NotifierAction}     action Action object, payload contains the notification ID
     * @returns {Promise<undefined>}        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.handleHideAction = function (action) {
        var _this = this;
        return new Promise(function (resolve, reject) {
            var stepPromises = [];
            // Does the notification exist / are there even any notifications? (let's prevent accidential errors)
            var notification = _this.findNotificationById(action.payload);
            if (notification === undefined) {
                resolve();
                return;
            }
            // Get older notifications
            var notificationIndex = _this.findNotificationIndexById(action.payload);
            if (notificationIndex === undefined) {
                resolve();
                return;
            }
            var oldNotifications = _this.notifications.slice(0, notificationIndex);
            // Do older notifications exist, and thus do we need to shift other notifications as a consequence?
            if (oldNotifications.length > 0) {
                // Are animations enabled?
                if (_this.config.animations.enabled && _this.config.animations.hide.speed > 0) {
                    // Is animation overlap enabled?
                    if (_this.config.animations.overlap !== false && _this.config.animations.overlap > 0) {
                        stepPromises.push(notification.component.hide());
                        setTimeout(function () {
                            stepPromises.push(_this.shiftNotifications(oldNotifications, notification.component.getHeight(), false));
                        }, _this.config.animations.hide.speed - _this.config.animations.overlap);
                    }
                    else {
                        notification.component.hide().then(function () {
                            stepPromises.push(_this.shiftNotifications(oldNotifications, notification.component.getHeight(), false));
                        });
                    }
                }
                else {
                    stepPromises.push(notification.component.hide());
                    stepPromises.push(_this.shiftNotifications(oldNotifications, notification.component.getHeight(), false));
                }
            }
            else {
                stepPromises.push(notification.component.hide());
            }
            // Wait until both hiding and shifting is done, then remove the notification from the list
            Promise.all(stepPromises).then(function () {
                _this.removeNotificationFromList(notification);
                resolve(); // Done
            });
        });
    };
    /**
     * Hide the oldest notification (bridge to handleHideAction)
     *
     * @param   {NotifierAction}     action Action object
     * @returns {Promise<undefined>}        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.handleHideOldestAction = function (action) {
        // Are there any notifications? (prevent accidential errors)
        if (this.notifications.length === 0) {
            return new Promise(function (resolve, reject) {
                resolve();
            }); // Done
        }
        else {
            action.payload = this.notifications[0].id;
            return this.handleHideAction(action);
        }
    };
    /**
     * Hide the newest notification (bridge to handleHideAction)
     *
     * @param   {NotifierAction}     action Action object
     * @returns {Promise<undefined>}        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.handleHideNewestAction = function (action) {
        // Are there any notifications? (prevent accidential errors)
        if (this.notifications.length === 0) {
            return new Promise(function (resolve, reject) {
                resolve();
            }); // Done
        }
        else {
            action.payload = this.notifications[this.notifications.length - 1].id;
            return this.handleHideAction(action);
        }
    };
    /**
     * Hide all notifications at once
     *
     * @param   {NotifierAction}     action Action object
     * @returns {Promise<undefined>}        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.handleHideAllAction = function (action) {
        var _this = this;
        return new Promise(function (resolve, reject) {
            // Are there any notifications? (prevent accidential errors)
            var numberOfNotifications = _this.notifications.length;
            if (numberOfNotifications === 0) {
                resolve(); // Done
                return;
            }
            // Are animations enabled?
            if (_this.config.animations.enabled && _this.config.animations.hide.speed > 0 && _this.config.animations.hide.offset !== false &&
                _this.config.animations.hide.offset > 0) {
                var _loop_1 = function(i) {
                    var animationOffset = _this.config.position.vertical.position === 'top' ? numberOfNotifications - 1 : i;
                    setTimeout(function () {
                        _this.notifications[i].component.hide().then(function () {
                            // Are we done here, was this the last notification to be hidden?
                            if ((_this.config.position.vertical.position === 'top' && i === 0) ||
                                (_this.config.position.vertical.position === 'bottom' && i === numberOfNotifications - 1)) {
                                _this.removeAllNotificationsFromList();
                                resolve(); // Done
                            }
                        });
                    }, _this.config.animations.hide.offset * animationOffset);
                };
                for (var i = numberOfNotifications - 1; i >= 0; i--) {
                    _loop_1(i);
                }
            }
            else {
                var stepPromises = [];
                for (var i = numberOfNotifications - 1; i >= 0; i--) {
                    stepPromises.push(_this.notifications[i].component.hide());
                }
                Promise.all(stepPromises).then(function () {
                    _this.removeAllNotificationsFromList();
                    resolve(); // Done
                });
            }
        });
    };
    /**
     * Shift multiple notifications at once
     *
     * @param   {Array<NotifierNotification>} notifications List containing the notifications to be shifted
     * @param   {number}                      distance      Distance to shift (in px)
     * @param   {boolean}                     toMakePlace   Flag, defining in which direciton to shift
     * @returns {Promise<undefined>}                        Promise, resolved when done
     */
    NotifierContainerComponent.prototype.shiftNotifications = function (notifications, distance, toMakePlace) {
        return new Promise(function (resolve, reject) {
            // Are there any notifications to shift?
            if (notifications.length === 0) {
                resolve();
                return;
            }
            var notificationPromises = [];
            for (var i = notifications.length - 1; i >= 0; i--) {
                notificationPromises.push(notifications[i].component.shift(distance, toMakePlace));
            }
            Promise.all(notificationPromises).then(resolve); // Done
        });
    };
    /**
     * Add a new notification to the list of notifications (triggers change detection)
     *
     * @param {NotifierNotification} notification Notification to add to the list of notifications
     */
    NotifierContainerComponent.prototype.addNotificationToList = function (notification) {
        this.notifications.push(notification);
        this.changeDetector.markForCheck(); // Run change detection because the notification list changed
    };
    /**
     * Remove an existing notification from the list of notifications (triggers change detection)
     *
     * @param {NotifierNotification} notification Notification to be removed from the list of notifications
     */
    NotifierContainerComponent.prototype.removeNotificationFromList = function (notification) {
        this.notifications =
            this.notifications.filter(function (item) { return item.component !== notification.component; });
        this.changeDetector.markForCheck(); // Run change detection because the notification list changed
    };
    /**
     * Remove all notifications from the list (triggers change detection)
     */
    NotifierContainerComponent.prototype.removeAllNotificationsFromList = function () {
        this.notifications = [];
        this.changeDetector.markForCheck(); // Run change detection because the notification list changed
    };
    /**
     * Helper: Find a notification in the notification list by a given notification ID
     *
     * @param   {string}                           notificationId Notification ID, used for finding notification
     * @returns {NotifierNotification | undefined}                Notification, undefined if not found
     */
    NotifierContainerComponent.prototype.findNotificationById = function (notificationId) {
        return this.notifications.find(function (currentNotification) { return currentNotification.id === notificationId; });
    };
    /**
     * Helper: Find a notification's index by a given notification ID
     *
     * @param   {string}             notificationId Notification ID, used for finding a notification's index
     * @returns {number | undefined}                Notification index, undefined if not found
     */
    NotifierContainerComponent.prototype.findNotificationIndexById = function (notificationId) {
        var notificationIndex = this.notifications.findIndex(function (currentNotification) { return currentNotification.id === notificationId; });
        return (notificationIndex !== -1 ? notificationIndex : undefined);
    };
    NotifierContainerComponent.decorators = [
        { type: Component, args: [{
                    changeDetection: ChangeDetectionStrategy.OnPush,
                    host: {
                        class: 'x-notifier__container'
                    },
                    selector: 'x-notifier-container',
                    template: "<ul><li *ngFor=\"let notification of notifications; trackBy: notification?.id\" class=\"x-notifier__container-list\"><x-notifier-notification  (dismiss)=\"onNotificationDismiss( $event )\" (ready)=\"onNotificationReady( $event )\" [notification]=\"notification\"> </x-notifier-notification></ul>"
                },] },
    ];
    /** @nocollapse */
    NotifierContainerComponent.ctorParameters = function () { return [
        { type: ChangeDetectorRef, },
        { type: NotifierQueueService, },
        { type: NotifierService, },
    ]; };
    return NotifierContainerComponent;
}());
//# sourceMappingURL=notifier-container.component.js.map