(function (root) {

    "use strict";

    /**
     * Common object params
     * @type {Object}
     */
    var common = {
            publicMethods: ['subscribe', 'unsubscribe', 'fire', 'getStack'],
            className: 'Observer'
        },

        /**
         * Main constructor
         * @return {Object} - this handle
         */
        Protected = function () {

            return this;
        };


    /**
     * Main prototype
     * @type {Object}
     */
    Protected.prototype = {

        stack: {},

        subscribe: function (eventName, fn, context, once) {

            context = context || window;
            once = once || false;

            var id = (+new Date()).toString() + '-' + Math.random().toString(36).substr(2, 16);

            this.stack[eventName] = this.stack[eventName] || [];
            this.stack[eventName].push({
                fn: fn,
                context: context,
                once: once,
                id: id
            });

            return id;
        },

        fire: function (eventName, args) {
            
            var n, i, l;

            args = (typeof args === 'undefined') ? [] : args;
            args = (typeof args === 'array') ? args : [args];

            for (n in this.stack) {
                if (this.stack.hasOwnProperty(n) && eventName === n.toString()) {

                    l = this.stack[n].length;
                    for (i = 0; i < l; i += 1) {
                        this.stack[n][i].fn.apply(this.stack[n][i].context, args);
                        if (this.stack[n][i].once) {
                            this.unsubscribe(this.stack[n][i].id);
                        }
                    }
                }
            }
        },

        unsubscribe: function (id) {
            
            var n, i, l;

            for (n in this.stack) {
                if (this.stack.hasOwnProperty(n)) {

                    l = this.stack[n].length;
                    for (i = 0; i < l; i += 1) {
                        
                        if (this.stack[n][i].id === id.toString()) {

                            delete this.stack[n][i];
                            this.stack[n].length -= 1;

                            if (this.stack[n].length === 0) {
                                delete this.stack[n];
                            }

                            return true;
                        }
                    }
                }
            }

            return false;
        },

        getStack: function (eventName) {
            return (eventName) ? this.stack[eventName] : this.stack;
        }
    };

    /**
     * Encapsulation
     * @return {Object} - this handle
     */
    root[common.className] = function () {

        function construct(constructor, args) {

            function Class() {
                return constructor.apply(this, args);
            }

            Class.prototype = constructor.prototype;
            return new Class();
        }

        var publicly = construct(Protected, arguments),
            i,
            l = common.publicMethods.length;

        for (i = 0; i < l; i += 1) {

            (function () {
                var member = common.publicMethods[i];
                root[common.className].prototype[member] = function () {
                    return publicly[member].apply(publicly, arguments);
                };
            }());
        }

        return this;
    };

}(this));