Effects = function(params) {
    if (!params || !params['obj'] || !params['props'] || !params['path']) return false;
    this.init(params); return true;
};

Effects.prototype = {
    'obj'               : null,
    'props'             : null,
    'suffixe'           : '',
    'path'              : [0],
    'duration'          : 1000,
    'timeout'           : 0,
    'methodName'        : null,
    'method'            : function(t, b, c, d) {return c*t/d + b; },
    'isPlaying'         : 0,
    'simpleCrossPoint'  : 0,

    /**
     * Constructor
     * @metod init
     * @param {object} params: Parameters to be animated
     */
    'init'              : function(params)
    {
        /**
         * Library of effects
         * @param {Number} t Time value used to compute current value
         * @param {Number} b Starting value
         * @param {Number} c Delta between start and end values
         * @param {Number} d Total length of animation
         * @param {Number} a Amplitude (optional)
         * @param {Number} p Period (optional)
         * @return {Number} The computed value for the current animation frame
         */
        var fLib =
        {
            'linearTween'   : function(t,b,c,d) { return c*t/d + b; },
            'backIn'        : function(t,b,c,d,a,p) { if (s==undefined) var s = 1.70158; return c*(t/=d)*t*((s+1)*t - s) + b; },
            'backOut'       : function(t,b,c,d,a,p) { if (s==undefined) var s = 1.70158; return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; },
            'backInOut'     : function(t,b,c,d,a,p) { if (s==undefined) var s = 1.70158; if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; },
            'elasticIn'     : function(t,b,c,d,a,p) { if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (!a || a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; },
            'elasticOut'    : function(t,b,c,d,a,p) { if (t==0) return b; if ((t/=d)==1) return b+c;  if (!p) p=d*.3; if (!a || a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); return (a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b); },
            'elasticInOut'  : function(t,b,c,d,a,p) { if (t==0) return b; if ((t/=d/2)==2) return b+c;  if (!p) var p=d*(.3*1.5); if (!a || a < Math.abs(c)) {var a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; },
            'bounceOut'     : function(t,b,c,d) { if ((t/=d) < (1/2.75)) { return c*(7.5625*t*t) + b; } else if (t < (2/2.75)) { return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; } else if (t < (2.5/2.75)) { return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; } else { return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; } },
            'bounceIn'      : function(t,b,c,d) { return c - Tween.bounceOut (d-t, 0, c, d) + b; },
            'bounceInOut'   : function(t,b,c,d) { if (t < d/2) return Tween.bounceIn (t*2, 0, c, d) * .5 + b; else return Tween.bounceOut (t*2-d, 0, c, d) * .5 + c*.5 + b; },
            'quadIn'        : function(t,b,c,d) { return c*(t/=d)*t + b; },
            'quadOut'       : function(t,b,c,d) { return -c *(t/=d)*(t-2) + b; },
            'quadInOut'     : function(t,b,c,d) { if ((t/=d/2) < 1) return c/2*t*t + b; return -c/2 * ((--t)*(t-2) - 1) + b; },
            'cubicIn'       : function(t,b,c,d) { return c*(t/=d)*t*t + b; },
            'cubicOut'      : function(t,b,c,d) { return c*((t=t/d-1)*t*t + 1) + b; },
            'cubicInOut'    : function(t,b,c,d) { if ((t/=d/2) < 1) return c/2*t*t*t + b; return c/2*((t-=2)*t*t + 2) + b; },
            'quartIn'       : function(t,b,c,d) { return c*(t/=d)*t*t*t + b; },
            'quartOut'      : function(t,b,c,d) { return -c * ((t=t/d-1)*t*t*t - 1) + b; },
            'quartInOut'    : function(t,b,c,d) { if ((t/=d/2) < 1) return c/2*t*t*t*t + b; return -c/2 * ((t-=2)*t*t*t - 2) + b; },
            'quintIn'       : function(t,b,c,d) { return c*(t/=d)*t*t*t*t + b; },
            'quintOut'      : function(t,b,c,d) { return c*((t=t/d-1)*t*t*t*t + 1) + b; },
            'quintInOut'    : function(t,b,c,d) { if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; return c/2*((t-=2)*t*t*t*t + 2) + b; },
            'sineIn'        : function(t,b,c,d) { return -c * Math.cos(t/d * (Math.PI/2)) + c + b; },
            'sineOut'       : function(t,b,c,d) { return c * Math.sin(t/d * (Math.PI/2)) + b; },
            'sineInOut'     : function(t,b,c,d) { return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; },
            'expoIn'        : function(t,b,c,d) { return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; },
            'expoOut'       : function(t,b,c,d) { return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; },
            'expoInOut'     : function(t,b,c,d) { if (t==0) return b; if (t==d) return b+c; if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; },
            'circIn'        : function(t,b,c,d) { return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; },
            'circOut'       : function(t,b,c,d) { return c * Math.sqrt(1 - (t=t/d-1)*t) + b; },
            'circInOut'     : function(t,b,c,d) { if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; }
        };
        this.obj = params['obj'] || this.obj;
        this.suffixe = params['suffixe'] || this.suffixe;
        this.duration = params['duration'] || this.duration;
        this.methodName = params['methodName'] || this.methodName;
        if (this.methodName)
        {
            if (typeof(this.methodName == 'string') && this.methodName in fLib)
            {
                this.method = fLib[this.methodName];
            }
            else
            {
                this.method = fLib[this.methodName[0]];
            }
        }
        if (params['path']) this.path = params['path'];
        if (params['props']) this.props = params['props'];
        this.timeout = params['timeout'] || this.timeout;

        var getDelta = function()
        {
            var delta;
            if (typeof(startPoint)=='number')
            {
                delta = endPoint - startPoint;
            }
            else
            {
                delta = [];
                for (var i = this.props.length; i >= 0; i--)
                {
                    delta[i] = endPoint[i] - startPoint[i];
                }
            }
            return delta;
        }.bind(this);

        var getCurPoint = function()
        {
            var point;
            if (typeof(this.props)=='string')
            {
                if (this.props == 'opacity')
                {
                    point = parseInt(this.obj[this.props]) * 100;
                }
                else
                {
                    point = parseInt(this.obj[this.props]);
                }
            }
            else
            {
                point = [];
                for (var i = this.props.length; i >= 0; i--)
                {
                    point[i] = parseInt(this.obj[this.props[i]]);
                }
            }
            return point;
        }.bind(this);

        var tweenStack      = [];
        var pathLen         = this.path.length;
        var isSequence      = pathLen > 2;
        var startPointIndex = 0;
        var curDuration     = isSequence ? Math.ceil(this.duration/ pathLen) : this.duration;
        var startPoint      = this.path[startPointIndex];
        var endPoint        = (pathLen > 1) ? this.path[startPointIndex+1] : startPoint;
        var curPoint        = startPoint;
        var delta           = getDelta();
        var startTime       = 0;
        var frameTimer      = null;
        var events          =
        {
            'onbeforeplay'       : [],
            'onafterplay'        : [],
            'onbeforestop'       : [],
            'onafterstop'        : [],
            'onbeforecontinueto' : [],
            'onaftercontinueto'  : []
        };

        /**
         * Adding/deleting event to play methods
         * @metod addEvent
         * @param {string} event: Event's name
         * @param {function} func: Called function
         */
        this.addEvent       = function(eName, func, permanent) { events[eName.toLowerCase()].push({'func':func, 'permanent':permanent || false}); };
        this.delEvent       = function(eName, func) { eName = eName.toLowerCase(); for (var i = 0; i < events[eName].length; i++) { if (events[eName][i].func == func) { events[eName].splice(i, 1); } } };
        this.executeEvents  = function(eName)
        {
            if (eName in events)
            {
                if (events[eName].length)
                {
                    var i = 0;
                    while (events[eName][i])
                    {
                        try { events[eName][i].func(); } catch (e){};
                        if (!events[eName][i].permanent)
                        {
                            this.delEvent(eName, events[eName][i].func);
                        }
                        else
                        {
                            i++;
                        }
                    }
                }
            }
        };

        this.clearEvents  = function()
        {
            for (eName in events)
            {
                if (events[eName].length)
                {
                    var i = 0;
                    while (events[eName][i])
                    {
                        if (!events[eName][i].permanent)
                        {
                            this.delEvent(eName, events[eName][i].func);
                        }
                        else
                        {
                            i++;
                        }
                    }
                }
            }
        };

        this.play           = function()
        {
            clearTimeout(frameTimer);
            curTime = curDuration;
            startTime = new Date().getTime() + this.timeout;
            if (/*@cc_on!@*/false && this.props == 'opacity')
            {//IE
                if (startPoint == 0)
                {
                    this.obj.visibility = '';
                    this.obj.filter = 'alpha(opacity=0)';
                }
            }
            this.executeEvents('onbeforeplay');
            this.isPlaying = true;
            frameTimer = setTimeout(nextFrame, this.timeout);
            this.executeEvents('onafterplay');
        };

        this.stop           = function()
        {
            this.executeEvents('onbeforestop');
            if (/*@cc_on!@*/false && this.props == 'opacity')
            {//IE
                if (endPoint == 100)
                {
                    this.obj.filter = '';
                }
                else if (endPoint == 0)
                {
                    this.obj.filter = '';
                    this.obj.visibility = 'hidden';
                }
            }

            curTime = curDuration;
            this.isPlaying = false;
            this.executeEvents('onafterstop');
        };

        this.continueTo     = function(point, duration)
        {
            if (this.isPlaying) startPoint = endPoint;
            else startPoint = getCurPoint();
            this.isPlaying = false;
            endPoint = point;
            curDuration = duration || curDuration;
            delta = getDelta();
            this.play();
        };

        var nextFrame = function()
        {
            var curTime = getTime();
            if ((curTime < curDuration) && this.isPlaying)
            {
                setPosition(curTime);
                frameTimer = setTimeout(nextFrame, 0);
            }
            else
            {
                setPosition(curDuration);
                this.stop();
                if (isSequence)
                {
                    this.continueTo(this.path[startPointIndex+2]);
                    if (startPointIndex+3 < this.path.length) startPointIndex++;
                    else isSequence = false;
                }
            }
        }.bind(this);

        var setPosition = function(curTime)
        {
            curPoint = getPosition(curTime);
            if (typeof(this.props) == 'string')
            {
                if (this.props == 'opacity')
                {
                    if (/*@cc_on!@*/false)
                    {//IE
                        this.obj.filter = 'alpha(opacity='+curPoint+')';
                        this.obj[this.props] = curPoint / 100;
                    }
                    else
                    {
                        this.obj[this.props] = curPoint / 100;
                    }
                }
                else
                {
                    this.obj[this.props] = Math.floor(curPoint) + this.suffixe;
                }
            }
            else
            {
                for (var i = this.props.length; i >= 0; i--)
                {
                    this.obj[this.props[i]] = Math.floor(curPoint[i]) + this.suffixe;
                }
            }
        }.bind(this);

        var getPosition = function(curTime)
        {
            var pos;
            if (typeof(this.props) == 'string')
            {
                pos = this.method(curTime, startPoint, delta, curDuration);
            }
            else
            {
                pos = [];
                for (var i = this.props.length; i >= 0; i--)
                {
                    pos[i] = this.method(curTime, startPoint[i], delta[i], curDuration);
                }
            }
            return pos;
        }.bind(this);

        var getTime = function() { return ($time(startTime)); }.bind(this);
    }
}

// global неймспейс //

/**
 * Сокращенный вариант document.getElementById
 *
 * @param {Object} elementId
 * @return {Object}
 */
function $(elementId)
{
    return (typeof elementId == 'string') ? document.getElementById(elementId) : elementId;
};

/**
 * Конструктор элементов HTML
 *
 * @param {Object} tag тэг
 * @param {Object} props свойства элемента
 * @param {Object} cssStyle стиль элемента
 * @return {Object}
 */
function $$$(tag, props, cssStyle)
{
    return PuskFramework.elem.construct(tag, props, cssStyle);
};

/**
 * Удаление элемента из DOM
 *
 * @param {Object} elem ссылка на элемент
 * @return {Object} возвращает сам элемент
 */
function $_(elem)
{
    return PuskFramework.elem.remove(elem);
};

/**
 * Возвращает тип объекта
 *
 * @param {Object} obj
 * @return {String}
 */
function $type(obj)
{
    if (!obj) return false;
    if (obj.tagName) return 'element';
    var type = typeof obj;
    if (type == 'object' && obj.nodeName) 
    {
        switch (obj.nodeType)
        {
            case 1:
                return 'element';
            case 3:
                return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
        }
    }
    if (type == 'object' || type == 'function') 
    {
        switch (obj.constructor)
        {
            case Array:
                return 'array';
            case RegExp:
                return 'regexp';
        }
        if (typeof obj.length == 'number') 
        {
            if (obj.item) return 'collection';
        }
    }
    return type;
};

/**
 * Возвращает текущее время в формате timestamp или разницу между
 * текущим временем и временем, заданным в качестве аргумента
 *
 * @param {Object} startTime время для сдвига
 * @return {Integer}
 */
function $time(startTime)
{
    var now = new Date().getTime();
    return startTime ? now - startTime : now;
};

/**
 * Функция предназначена для кроссбраузерной обработки любых событий
 * осуществляет выставление ключевых свойств
 *
 * @param {Object} evt объект типа event
 * @return {Object}
 */
function $event(evt)
{
    evt = PuskFramework.evt.e(evt);
    if (evt && !evt.target) 
    {
        evt.target = evt.srcElement;
        if (evt.type == 'mouseover') 
        {
            evt.relatedTarget = evt.fromElement;
        }
        else if (evt.type == 'mouseout') 
        {
            evt.relatedTarget = evt.toElement;
        }
        evt.stopPropagation = function()
        {
            this.cancelBubble = true
        };
        evt.preventDefault = function()
        {
            this.returnValue = false
        };
    }
    return evt;
};

/**
 * Аналог empty() в PHP, предназначена для проверки ненулевого значения элемента
 * 
 * @param {Object} smth элемент для проверки
 * @return {Bool}
 */
function $empty(smth)
{
    if (typeof(smth) != 'object') 
    { return !smth; }
    for (var i in smth) 
    { return false; }
    return true;
};

/**
 * Расширяет один объект всеми свойствами другого
 *
 * @param {Object} destination объект получатель
 * @param {Object} source объект донор
 * @return {Object}
 */
function $extend(destination, source)
{
    for (var property in source) 
        destination[property] = source[property];
    return destination;
};

// framework config //
var PuskFramework = {
    '_cfg': {
        'namespace': 'pf',
        'extendArray': true,
        'extendString': true,
        'extendDate': true
    }
};

// framework functions //

/** 
 * Развертывание функций обработчиков, если в качестве обработчика задана не функция, а объект
 *
 * @param {Object} _callback
 * @return {Function}
 */
PuskFramework._expandCallBack = function(_callback)
{
    switch (true)
    {
        default:
        case (typeof(_callback) == 'function' || !_callback):
            return _callback;
        case (typeof(callback) == 'object'):
        {
            var _scope = _callback.scope || window;
            var _func = _callback.func;
            var _args = _callback.args;
            var _temp = function()
            {
                if (_args) 
                {
                    for (var i = 0; i < _args.length; i++) 
                        arguments.push(_args[i]);
                }
                return _func.apply(_scope, arguments);
            };
            return _temp;
        }
    }
};

/**
 * Подмена контекста вызова функции
 *
 * @param {Object} obj контекст (обычно this)
 * @return {Function}
 */
Function.prototype.bind = function(obj)
{
    var method = this;
    var temp = function()
    {
        return method.apply(obj, arguments);
    };
    return temp;
};

/**
 * Возвращает случайное целое число в диапазоне от min до max
 *
 * @param {Object} min минимальное значение
 * @param {Object} max максимальное значение
 * @return {Integer}
 */
Math.rand = function(min, max)
{
    var res = Math.random();
    return (min == undefined || max == undefined) ? res : Math.floor(res * (max - min + 1) + min);
};

/**
 * Преобразует строку в шестнадцатиричном формате в целое число
 *
 * @param {Object} dec
 * @return {Integer}
 */
Math.dec2hex = function(dec)
{
    var hexChars = '0123456789abcdef';
    var a = dec % 16;
    return '' + hexChars.charAt((dec - a) >>> 4) + hexChars.charAt(a);
};

