/**
 * ufAlerts jQuery plugin. Fetches and renders alerts from the UF alert stream.
 * 
 * Based on template from https://github.com/jquery-boilerplate/jquery-boilerplate
 *
 * === USAGE ===

 * ufAlerts can be initialized on any container element as follows:
 *
 * $('#myDiv').ufAlerts(options);
 *
 * `options` is an object containing any of the following parameters:
 * @param {string} url The absolute URL from which to fetch flash alerts.
 * @param {bool} scrollToTop Whether to automatically scroll back to the top of the page after rendering alerts.
 * @param {string} alertMessageClass The CSS class(es) to be applied to each alert message.
 * @param {string} alertTemplateId The CSS id(es) for the Handlebar alert template.
 * @param {bool} agglomerate Set to true to render all alerts together, applying styling for the highest-priority alert being rendered.
 *
 * == EVENTS ==
 *
 * uf-form triggers the following events:
 *
 * `fetch.ufAlerts`: triggered when the alerts have been successfully fetched from the server.
 * `render.ufAlerts`: triggered when all alerts have been rendered and the call to render() has completed.
 *
 * == METHODS ==
 *
 * `fetch()`: Asynchronously gets alerts from the server.
 * `push(options)`: Adds a alert of a specified type (danger, warning, info, success) to the internal collection of alerts.
 * `clear()`: Removes all messages from the internal collection.
 * `render()`: Renders the collection of alerts to the container, awaiting results of `fetch()` if required.
 *
 * UserFrosting https://www.userfrosting.com
 * @author Alexander Weissman <https://alexanderweissman.com>
 */
;(function($, window, document, undefined) {
	'use strict';

    // Define plugin name and defaults.
    var pluginName = 'ufAlerts',
        defaults = {
            url                 : site.uri.public + '/alerts',
            scrollToTop         : true,
            scrollWhenVisible   : false,
            agglomerate         : false,
            alertMessageClass   : 'uf-alert-message',
            alertTemplateId     : 'uf-alert-template',
            DEBUG               : false
        };

    // Constructor
    function Plugin (element, options) {
        this.element = element[0];
        this.$element = $(this.element);
        this.settings = $.extend(true, {}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;

        // Detect changes to element attributes
        this.$element.attrchange({ callback: function (event) { this.element = event.target; }.bind(this) });

        // Plugin variables
        this.alerts = [];
        this._newAlertsPromise = $.Deferred().resolve();
        this._alertTemplateHtml = $('#' + this.settings.alertTemplateId).html();
        this._alertTypePriorities = {
            danger : 3,
            warning: 2,
            success: 1,
            info   : 0
        };
        this._alertTypeIcon = {
            danger : 'fa-ban',
            warning: 'fa-warning',
            success: 'fa-check',
            info   : 'fa-info'
        };

        return this;
    }

    // Functions
    $.extend(Plugin.prototype, {
        /**
         * Clear all alerts from the current uf-alerts collection.
         */
        clear: function() {
            // See http://stackoverflow.com/a/1232046/2970321
            this.alerts.length = 0;

            if (this.settings.agglomerate) {
                this.element.toggleClass('alert', false)
                    .toggleClass('alert-info', false)
                    .toggleClass('alert-success', false)
                    .toggleClass('alert-warning', false)
                    .toggleClass('alert-danger', false);
            }

            // Clear any alert HTML
            this.$element.empty();

            return this.$element;
        },
        /**
         * Fetches alerts from the alert stream
         */
        fetch: function() {
            // Set a promise, so that any chained calls after fetch can wait until the messages have been retrieved
            this._newAlertsPromise = $.ajax({
                url: this.settings.url,
                cache: false
            }).then(
                // Success
                this._fetchSuccess.bind(this),
                // Failure
                this._fetchFailure.bind(this)
            );
            
            return this.$element;
        },
        /**
         * Success callback for fetch
         */
        _fetchSuccess: function(alerts) {
            if (alerts != null) this.alerts = $.merge(this.alerts, alerts);
            this.$element.trigger('fetch.' + this._name);
        },
        /**
         * Failure callback for fetch
         */
        _fetchFailure: function(response) {
            this.$element.trigger('error.' + this._name);
            if ((typeof site !== 'undefined') && site.debug.ajax && response.responseText) {
                document.write(response.responseText);
                document.close();
            } else {
                if (this.settings.DEBUG) {
                    console.warn('Error (' + response.status + '): ' + response.responseText );
                }
            }
        },
        /**
         * Push a given message to the current uf-alerts collection.
         */
        push: function(options) {
            this.alerts.push({
                type   : options[0],
                message: options[1]
            });

            return this.$element;
        },
        /**
         * Renders the alerts.
         */
        render: function() {
            // Wait for promise completion, only if promise is unresolved.
            if (this._newAlertsPromise.state() == 'resolved' || this._newAlertsPromise.state() == 'rejected') {
                this._render();
            }
            else {
                $.when(this._newAlertsPromise).then(this._render.bind(this));
            }

            return this.$element;
        },
        /*
         * Internal private method that physically handles rendering operation.
         */
        _render: function() {
            // Holds generated HTML
            var alertHtml = '';
            // Only compile alerts if there are alerts to display
            if (this.alerts.length > 0) {
                // Prepare template
                var alertTemplate = Handlebars.compile(this._alertTemplateHtml, {noEscape: true});
                var i;
                // If agglomeration is enabled, set the container to the highest priority alert type
                if (this.settings.agglomerate) {
                    // Holds generated agglomerated alerts
                    var alertMessage = '<ul>';

                    // Determine overall alert priority
                    var alertContainerType = 'info';
                    for (i = 0; i < this.alerts.length; i++) {
                        if (this._alertTypePriorities[this.alerts[i].type] > this._alertTypePriorities[alertContainerType]) {
                            alertContainerType = this.alerts[i].type;
                        }
                    }

                    // Compile each alert
                    var aggTemplate = Handlebars.compile('<li class=' + this.settings.alertMessageClass + '>{{ message }}</li>');
                    for (i = 0; i < this.alerts.length; i++) {
                        alertMessage += aggTemplate(this.alerts[i]);
                    }

                    alertMessage += '</ul>';

                    // Generate complete alert HTML
                    alertHtml = alertTemplate({
                        type   : alertContainerType,
                        message: alertMessage,
                        icon   : this._alertTypeIcon[alertContainerType]
                    });
                }
                else {
                    // Compile each alert.
                    for (i = 0; i < this.alerts.length; i++) {
                        var alert = this.alerts[i];

                        // Inject icon
                        alert.icon = this._alertTypeIcon[alert.type];

                        // Compile alert
                        alertHtml += alertTemplate(alert);
                    }
                } 
            }
            // Show alerts
            this.$element.html(alertHtml);

            // Scroll to top of alert location is new alerts output, and auto scrolling is enabled
            if (this.settings.scrollToTop && alertHtml !== '') {
                // Don't scroll if already visible, unless scrollWhenVisible is true
                if (!this._alertsVisible() || this.settings.scrollWhenVisible) {
                    $('html, body').animate({ scrollTop: this.$element.offset().top }, 'fast');
                }
            }

            // Trigger render events
            this.$element.trigger('render.' + this._name);
        },
        /**
         * Returns true if alerts container is completely within the viewport.
         */
        _alertsVisible: function() {
            var rect = this.element.getBoundingClientRect();
            return (
                rect.top >= 0 &&
                rect.left >= 0 &&
                rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&     
                rect.right <= (window.innerWidth || document.documentElement.clientWidth)
            );
        },
        /**
         * Completely destroy the ufAlerts plugin on the element.
         */
        destroy: function() {
            // Unbind any bound events
            this.$element.off('.' + this._name);

            // Remove plugin from element
            this.$element.removeData(this._name);

            return this.$element;
        }
    });

    // Handles instantiation and access to non-private methods.
    $.fn[pluginName] = function(methodOrOptions) {
        // Grab plugin instance
        var instance = $(this).data(pluginName);
        // If undefined or object, initialize plugin.
        if (methodOrOptions === undefined || typeof methodOrOptions === 'object') {
            // Only initialize if not previously done.
            if (!instance) {
                $(this).data(pluginName, new Plugin(this, methodOrOptions));
            }
            return this;
        }
        // Otherwise ensure first parameter is a valid string, and is the name of an actual function.
        else if (typeof methodOrOptions === 'string' && typeof instance[methodOrOptions] === 'function') {
            // Ensure not a private function
            if (methodOrOptions.indexOf('_') !== 0) {
                return instance[methodOrOptions]( Array.prototype.slice.call(arguments, 1));
            }
            else {
                console.warn('Method ' +  methodOrOptions + ' is private!');
            }
        }
        else {
            console.warn('Method ' +  methodOrOptions + ' does not exist.');
        }
    };
})(jQuery, window, document);