/************************************************************
 *     Эффекты
 *
 *     Автор:                     Агроник А.Ю.
 *     Дата создания:             18.08.2007
 *
 *     TODO:  Дописать AnimateStyle так, чтоб 
 *            можно было анимировать несколько свойств
 *            SUGGEST: Задавать массив конструкций
 *                     вида {style, value, border}                        
 *
 ************************************************************/
 
 $effect = 
 {
    Info:new Array(), // Массив текущих анимаций
    GetInfo:function(obj) // Получает текущую анимацию по объекту, к которому она применятеся
    {
        var result = null;
        for(var i=0;i<this.Info.length;i++)
        {
            if(obj == this.Info[i].obj)
            {
                result = this.Info[i];
                break;
            }
        }
        return result;
    },
    ClearInfo:function(obj) // Удаляет анимацию привязаную в заданому объекту
    { 
        var arr = new Array();
        for(var i=0;i<this.Info.length;i++)
            if(obj != this.Info[i].obj)
                arr.push(this.Info[i]);
        this.Info = arr; 
    },
    Timer:null, // -- проверить необходимость -- не вижу
    AnimationTime:1500, // Длительность анимации (в милисекундах)
    AnimationInterval:10, // Интервал в милисекундах между итерациями анимационного процесса
    AnimationType:"sin", // Тип изменения скорости анимации. Возможные значения: "sin", "cos"
    Default:function() // Сброс параметров в значения по-умолчанию
    {
        this.Timer = null;
        this.AnimationTime = 1500;
        this.AnimationInterval = 10;
        this.AnimationType = "sin";
    }, 
    Time:function(_time) // Задает время Анимации и возвращает управляющий объект (для конструкций типа $effect.Time(1000).FadeIn(this))
    {
        this.AnimationTime = _time;
        return this;
    },
    Interval:function(_interval) // Задает интервал Анимации и возвращает управляющий объект (аналогично .Time().)
    {
        this.AnimationInterval = _interval;
        return this;
    },
    Type:function(_type) // Задает Тип Анимации и возвращает управляющий объект (аналогично .Time().)
    {
        this.AnimationType = _type;
        return this;
    },
    FadeIn:function(obj, end) // Постепенное проявление объекта (из прозрачности)
    {
        if(obj)
        {  
            this.AnimateOpacity (obj, 100, 0, end); 
        }
    },
    FadeOut:function(obj, end) // Постепенное исчезновение объекта (прозрачность)
    {
        if(obj)
        {
            this.AnimateOpacity(obj, -100, 100, end);
        }
    },
    AnimateOpacity:function(obj, disp, start, onEnd) // Анимирует изменение непрозрачности
    {
        var border  = disp < 0 ? 0 : 100;
        var x0 = start;
        var a = x0 + disp;
        var displace = border != null ? (disp > 0 ? a < border : a > border) ? disp : (x0 - border)*(-1) : disp;
        var time = Math.abs(Math.round(displace*$effect.AnimationTime/disp));
        var info = this.GetInfo(obj);
            if(info != null)
                info.Stop();
                
        var run = function(f)
        {
            $global.SetOpacity(obj, Math.round(f*displace + x0)/100);
            //console.log(obj.tagName + " : "+ Math.round(f*displace + x0)/100);
        }    
        var end = function()
        {
            $global.SetOpacity(obj, Math.round(displace + x0)/100);
            $effect.Default(); 
            if(onEnd)
                onEnd(obj);
        }
        this.Animate(obj, time, null, run, end);
    },
    AnimateStyle:function(obj, style, disp, bord, onEnd, onRun) // Анимирует измение параметра стиля заданого в style
    {
        var border = bord == null ? null : bord;
        var x0 = $global.GetStyle(obj, style, true);
        var a = x0 + disp;
        var displace = border != null ? (disp > 0 ? a < border : a > border) ? disp : (x0 - border)*(-1) : disp;
        var time = Math.abs(Math.round(displace*$effect.AnimationTime/disp));
        var info = this.GetInfo(obj);
            if(info != null)
                info.Stop();
                
        var run = function(f)
        {
            $global.SetStyle($global.NormCssStr(style), Math.round(f*displace + x0)+"px", obj);
            if(onRun)
				onRun();
        }		
        var end = function()
        {
            $global.SetStyle($global.NormCssStr(style), (x0 + displace) + "px", obj);
            $effect.Default(); 
            if(onEnd)
                onEnd(obj);
        }
        this.Animate(obj, time, null, run, end);
    },
    SlideH:function(obj, disp, bord, onEnd) // Перемещение объекта по горизонтали
    {
        this.AnimateStyle(obj, "left", disp, bord, onEnd);
    },
    SlideV:function(obj, disp, bord, onEnd) // Перемещение объекта по вертикали
    {
        this.AnimateStyle(obj, "top", disp, bord, onEnd);
    },
    SlideToPoint:function(obj, x, y)
    {
        var info = this.GetInfo(obj);
            if(info != null)
                info.Stop();
                
        var pos = $global.FindPosition(obj);
        var x0 = Number(pos[0]);
        var y0 = Number(pos[1]); 
        var dispX = x - x0;
        var dispY = y - y0;
        var start = function()
        {
        }   
        var run = function(f)
        {
            obj.style.top = Math.round(f*dispY + y0)+"px";
            obj.style.left = Math.round(f*dispX + x0)+"px";
        }
        var end = function()
        {
            obj.style.top = y + "px";
            obj.style.left = x + "px";
            $effect.Default(); 
        }
        this.Animate(obj, start, run, end);
    },
    // Необходим для изменения ускорения согласно выбранному типу
    // type - варианты "sin" : sin(value), "cos" : cos(value)
    TypedChange:function(value, type)
    {
        if(type == "sin")
            return Math.sin(value);
        else if(type == "cos")
            return Math.cos(value);
        else
            return value;
    },
    // Функция - движок, изменяет параметры в течение времени (плавно)
    //           obj - объект анимации
    //    uTotalTime - общее время анимации
    //     fnOnStart - функция выполняющаяся до начала анимации
    //       fnOnRun - функция выполняющаяся на каждом шаге анимации func(f), f - ускорение?!
    //       fnOnEnd - функция выполняющаяся после завершения анимации
    Animate:function(obj, uTotalTime, fnOnStart, fnOnRun, fnOnEnd)
    {
        if (fnOnStart) { fnOnStart(); }
        var freq = Math.PI / (2 * uTotalTime); // частота
        var startTime = new Date().getTime();
        var info = null;
        var type = this.AnimationType;
        var loop = function() 
        {
            var elapsedTime = new Date().getTime() - startTime;
            if (elapsedTime < uTotalTime) 
            {
                var f = Math.abs($effect.TypedChange(elapsedTime * freq, type)); 
                if (fnOnRun) { fnOnRun(f); }
            }
            else 
            {
                if (fnOnEnd) { info.Stop(); fnOnEnd(); }
            }
        }
        var tmr = setInterval(loop, $effect.AnimationInterval);
        info = new AnimInfo(tmr, obj);
        $effect.Info.push(info);
    },
    Reflection:
    {
        Add:function(obj, height, opacity)
        {
            if(obj)
            {
                var refHeight = Math.floor(obj.offsetHeight*height);
                var refWidth = obj.offsetWidth;
                var divHeight = Math.floor(obj.offsetHeight*(1+height));
                var wrap = document.createElement("div");
                
                wrap.className = obj.className;
                wrap.style.cssText = obj.style.cssText;
                
                $global.SetStyle("width", refWidth+"px", wrap);
                $global.SetStyle("height", divHeight+"px", wrap);
                $global.SetStyle($global.NormCssStr("vertical-align"), "bottom", wrap);
                $global.SetStyle($global.NormCssStr("overflow"), "hidden", wrap);
                
                var reflect = null;
                
                if(document.all && !window.opera)
                {
                    reflect = document.createElement("img");
                    reflect.src = obj.src;
                    $global.SetStyle("width", refWidth+"px", reflect);
                    $global.SetStyle($global.NormCssStr("margin-bottom"), "-"+(obj.offsetHeight-refHeight)+"px", reflect);
                    reflect.style.filter = 'flipv Alpha(opacity='+(opacity*100)+', style=1, finishOpacity=0, startx=0, starty=0, finishx=0, finishy='+(height*100)+')';
                }
                else
                {
                    reflect = document.createElement("canvas");
					var context = reflect.getContext("2d");
				
					$global.SetStyle("height", refHeight+"px", reflect); 
					$global.SetStyle("width", refWidth+"px", reflect); 
					
					reflect.width = refWidth;
					reflect.height = refHeight;
					
					// append ?
					context.save();
					
					context.translate(0, obj.offsetHeight-1);
					context.scale(1, -1);
					
					context.drawImage(obj, 0, 0, refWidth, obj.offsetHeight);
	
					context.restore();
					
					context.globalCompositeOperation = "destination-out";
					var gradient = context.createLinearGradient(0, 0, 0, refHeight);
					
					gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
					gradient.addColorStop(0, "rgba(255, 255, 255, "+(1-opacity)+")");
		
					context.fillStyle = gradient;
					if (navigator.appVersion.indexOf('WebKit') != -1) {
						context.fill();
					} else {
						context.fillRect(0, 0, refWidth, refHeight*2);
					}
                }
                
                obj.className = "reflected";
                obj.style.cssText = ""; 
                
                obj.parentNode.replaceChild(wrap, obj);
                wrap.appendChild(obj);
                wrap.appendChild(reflect);
            }
        },
        Remove:function(obj) // Несовершенен...
        {
		    if (obj.className == "reflected") 
		    {
			    obj.className = obj.parentNode.className;
			    obj.parentNode.style.height = obj.style.height;
			    obj.parentNode.style.width = obj.style.width;
			    obj.style.cssText = obj.parentNode.style.cssText;
			    obj.parentNode.parentNode.replaceChild(obj, obj.parentNode);
		    }
	    }
    }
 }; 

function AnimInfo(interval, obj)
{
    this.obj = obj;
    this.Interval = interval;
    this.Stop = function()
    {
        clearInterval(this.Interval);
        $effect.ClearInfo(this.obj);
    }
}