/**
 *  Observable Object
 *  Just call it in constructor of your class (delegate pattern)
 *  Ex: var me = Observable(); // create new object
 *      or
 *      Observable(me);        // extend existing object
 *
 *  @author   mista_k
 *  @version  2.3
 */
var Observable = function (target) {
    // callbacks and events holders
    var observers = {}, events = {};
    // create new object if needed
    target = target || {};

    target.attachObserver = function (eventType, callback, force) {
        if (!observers[eventType]) {
            observers[eventType] = [];
        }
        observers[eventType].push(callback);
        // execute callback if such event has happened
        if (force && events[eventType]) {
            if (typeof callback === 'string') {
                callback = this[callback];
            }
            if (callback instanceof Function) {
                callback.apply(this, events[eventType]);
            }
        }
        return this;
    };

    target.detachObserver = function (eventType, callback) {
        var a, i;
        if (eventType) {
            if (observers[eventType] && callback) {
                a = observers[eventType];
                for (i = 0; i < a.length; i++) {
                    if (a[i] === callback) {
                        a.splice(i, 1);
                        break;
                    }
                }
            } else {
                delete observers[eventType];
            }
        } else {
            observers = {};
        }
        return this;
    };

    target.notify = function () {
        var a, i, handler, data = [], eventType;
        // copy notification arguments
        Array.prototype.push.apply(data, arguments);
        eventType = data.shift();
        a = observers[eventType];
        if (a && a.length > 0) {
            for (i = 0; i < a.length; i++) {
                handler = a[i];
                if (typeof handler === 'string') {
                    handler = this[handler];
                }
                if (handler instanceof Function) {
                    handler.apply(this, data);
                }
            }
        }
        // store triggered events and its custom data
        events[eventType] = data;
        return this;
    };

    return target;
};


/**
 * Синхронная очередь
 * 
 * @method  add       добавить функцию в очередь
 * @method  interate  выполнить первую в очереди функцию и удалить ее из очереди
 * @method  clear     очистить очередь
 *
 * @author  mista_k
 */
var Queue = function () {};
Queue.prototype = {
    // вспомогательный флаг для определения выполняется очередь или нет
    isLocked: false,
    // элементы очереди
    members: [],
    // публичные методы
    add: function (f) {
        if (f instanceof Function) {
            this.members.push(f);
        }
    },
    iterate: function () {
        if (this.members.length > 0) {
            var func = this.members.shift();
            func.call(this);
        }
    },
    clear: function () {
        this.members = [];
    }
};


/**
 * Таймер
 *
 */
var Timer = function () {};

Timer.prototype = {
    later: function (when, func, data, scope) {
        var me = this;
        scope = scope || window;
        me.cancel();
        me.method = "Timeout";
        me.id = setTimeout(function () {
            func.apply(scope, data || []);
        }, when);
    },
    periodic: function (when, func, data, scope) {
        var me = this;
        scope = scope || window;
        me.cancel();
        me.method = "Interval";
        me.id = setInterval(function () {
            func.apply(scope, data || []);
        }, when);
    },
    cancel: function () {
        var method;
        if (this.id && this.method) {
            method = "clear" + this.method;
            if (window[method]) {
                window[method](this.id);
                delete this.id;
                delete this.method;
            }
        }
    }
};