/**
 * based on push.js
 * =======
 * A compact, cross-browser solution for Javascript desktop notifications
 *
 * Credits
 * -------
 * Tsvetan Tsvetkov (ttsvetko) (https://github.com/Nickersoft/push.js)
 * Alex Gibson (alexgibson) (https://github.com/Nickersoft/push.js)
 * www.pcsg.de (Henning Leutz)
 *
 * License
 * -------
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Tyler Nickerson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
define('qui/classes/utils/Push', function() {
    'use strict';

    var w = window;

    return function() {

        var self = this,
            isUndefined = function(obj) {
                return obj === undefined;
            },
            isString = function(obj) {
                return obj && obj.constructor === String;
            },
            isFunction = function(obj) {
                return obj && obj.constructor === Function;
            },

            /* Whether Push has permission to notify */
            hasPermission = false,

            /* List of active notifications */
            notifications = [],

            /**
             * Closes a notification
             * @param {Notification} notification
             * @return {void}
             */
            close_notification = function(notification) {
                /* Safari 6+, Chrome 23+ */
                if (typeof notification.close === 'function') {
                    notification.close();

                    /* Legacy webkit browsers */
                } else {
                    if (notification.cancel) {
                        notification.cancel();

                        /* IE9+ */
                    } else {
                        if (w.external && w.external.msIsSiteMode) {
                            w.external.msSiteModeClearIconOverlay();
                        }
                    }
                }
            },

            /**
             * Updates the notification count
             * @return {void}
             */
            updateCount = function() {
                self.count = notifications.length;
            },

            /**
             * Callback function for the 'create' method
             * @return {void}
             */
            create_callback = function(title, options) {
                var notification;
                /* Set empty settings if none are specified */
                options = options || {};

                /* Safari 6+, Chrome 23+ */
                if (w.Notification) {

                    notification = new w.Notification(
                        title,
                        {
                            icon: (isString(options.icon) || isUndefined(options.icon)) ? options.icon : options.icon.x32,
                            body: options.body,
                            tag: options.tag
                        }
                    );

                    /* Legacy webkit browsers */
                } else {
                    if (w.webkitNotifications) {

                        notification = w.webkitNotifications.createNotification(
                            options.icon,
                            title,
                            options.body
                        );

                        notification.show();

                        /* Firefox Mobile */
                    } else {
                        if (navigator.mozNotification) {

                            notification = navigator.mozNotification.createNotification(
                                title,
                                options.body,
                                options.icon
                            );

                            notification.show();

                            /* IE9+ */
                        } else {
                            if (w.external && w.external.msIsSiteMode()) {

                                //Clear any previous notifications
                                w.external.msSiteModeClearIconOverlay();
                                w.external.msSiteModeSetIconOverlay(
                                    ((isString(options.icon) || isUndefined(options.icon)) ? options.icon : options.icon.x16),
                                    title
                                );
                                w.external.msSiteModeActivate();

                                notification = {};
                            }
                        }
                    }
                }

                /* Wrapper used to close notification later on */
                var wrapper = {
                    close: function() {
                        close_notification(notification);
                    }
                };

                /* Autoclose timeout */
                if (options.timeout && options.timeout !== false) {
                    setTimeout(function() {
                        wrapper.close();
                    }, options.timeout);
                }

                /* Notification callbacks */
                if (isFunction(options.onShow)) {
                    notification.addEventListener('show', options.onShow);
                }

                if (isFunction(options.onError)) {
                    notification.addEventListener('error', options.onError);
                }

                if (isFunction(options.onClick)) {
                    notification.addEventListener('click', options.onClick);
                }

                if (isFunction(options.onClose)) {
                    notification.addEventListener('close', options.onClose);
                    notification.addEventListener('cancel', options.onClose);
                }

                /* Add it to the global array */
                notifications.push(notification);

                /* Update the notification count */
                updateCount();

                /* Return the wrapper so the user can call close() */
                return wrapper;
            },

            /**
             * Permission types
             * @enum {String}
             */
            Permission = {
                DEFAULT: 'default',
                GRANTED: 'granted',
                DENIED: 'denied'
            },

            Permissions = [Permission.GRANTED, Permission.DEFAULT, Permission.DENIED];

        /* Allow enums to be accessible from Push object */
        self.Permission = Permission;

        /* Number of open notifications */
        self.count = 0;

        /*****************
         Permissions
         /*****************/

        /**
         * Requests permission for desktop notifications
         * @param {Function} [onGranted] - Function to execute once permission is granted
         * @param {Function} [onDenied] - Function to execute once permission is granted
         * @return {void}
         */
        self.Permission.request = function(onGranted, onDenied) {
            /* Return if Push not supported */
            if (!self.isSupported) {
                return;
            }

            /* Default callback */
            var callback = function(result) {
                switch (result) {
                    case self.Permission.GRANTED:
                        hasPermission = true;
                        if (onGranted) {
                            onGranted();
                        }
                        break;

                    case self.Permission.DENIED:
                        hasPermission = false;
                        if (onDenied) {
                            onDenied();
                        }
                        break;
                }
            };

            /* Legacy webkit browsers */
            if (w.webkitNotifications && w.webkitNotifications.checkPermission) {
                w.webkitNotifications.requestPermission(callback);

                /* Safari 6+, Chrome 23+ */
            } else {
                if (w.Notification && w.Notification.requestPermission) {
                    w.Notification.requestPermission(callback);
                }
            }
        };

        /**
         * Returns whether Push has been granted permission to run
         * @return {Boolean}
         */
        self.Permission.has = function() {
            return hasPermission;
        };

        /**
         * Gets the permission level
         * @return {Permission} The permission level
         */
        self.Permission.get = function() {
            var permission;

            /* Return if Push not supported */
            if (!self.isSupported) {
                return;
            }

            /* Safari 6+, Chrome 23+ */
            if (w.Notification && w.Notification.permissionLevel) {
                permission = w.Notification.permissionLevel;

                /* Legacy webkit browsers */
            } else {
                if (w.webkitNotifications && w.webkitNotifications.checkPermission) {
                    permission = Permissions[w.webkitNotifications.checkPermission()];

                    /* Firefox 23+ */
                } else {
                    if (w.Notification && w.Notification.permission) {
                        permission = w.Notification.permission;

                        /* Firefox Mobile */
                    } else {
                        if (navigator.mozNotification) {
                            permission = Permissions.GRANTED;

                            /* IE9+ */
                        } else {
                            if (w.external && w.external.msIsSiteMode() !== undefined) {
                                permission = w.external.msIsSiteMode() ? Permission.GRANTED : Permission.DEFAULT;
                            }
                        }
                    }
                }
            }

            return permission;
        };

        /*********************
         Other Functions
         /*********************/

        /**
         * Detects whether the user's browser supports notifications
         * @return {Boolean}
         */
        self.isSupported = (function() {
            var isSupported = false;

            try {
                isSupported =

                    /* Safari, Chrome */ !!(w.Notification ||

                    /* Chrome & ff-html5notifications plugin */
                    w.webkitNotifications ||

                    /* Firefox Mobile */
                    navigator.mozNotification ||

                    /* IE9+ */
                    (w.external && w.external.msIsSiteMode() !== undefined));

            } catch (e) {
            }

            return isSupported;
        })();

        /**
         * Creates and displays a new notification
         * @param {String} title
         * @param {Array} options
         * @return {void}
         */
        self.create = function(title, options) {
            /* Fail if the browser is not supported */
            if (!self.isSupported) {
                console.error('PushError: push.js is incompatible with self browser.');
                return;
            }

            /* Fail if no or an invalid title is provided */
            if (typeof title !== 'string') {
                throw 'PushError: Title of notification must be a string';
            }

            /* Request permission if it isn't granted */
            if (!self.Permission.has()) {
                self.Permission.request(function() {
                    return create_callback(title, options);
                });
            } else {
                return create_callback(title, options);
            }
        };

        /**
         * Closes a notification with the given tag
         * @param {String} tag - Tag of the notification to close
         * @return {void}
         */
        self.close = function(tag) {
            var notification;

            for (var i = 0; i < notifications.length; i++) {
                notification = notifications[i];

                /* Run only if the tags match */
                if (notification.tag === tag) {

                    /* Call the notification's close() method */
                    close_notification(notification);

                    /* Remove the notification from the global array */
                    notifications.splice(i, 1);

                    /* Update the notification count */
                    updateCount();

                    /* Return after the first notification is closed */
                    return;
                }
            }
        };

        /**
         * Clears all notifications
         * @return {void}
         */
        self.clear = function() {
            for (var i = 0; i < notifications.length; i++) {
                close_notification(notifications[i]);
            }

            /* Reset the global array */
            notifications = [];

            /* Update the notification count */
            updateCount();
        };
    };
});
