// -------------------------------------------------------
//
// Tween library for dL
//
// -------------------------------------------------------

// To do: I want to remove the "hover"-event from this class, it messes up logic of regular "animation" tweens;


dL.Tween = (function namespace() {

function Tween (parameters) {   
    
    this.properties = {};                                                       // doesn't need to be an object, could be an array?
    this.beginValues = {};
    this.difference = {}
    this.eventListeners = [];    
    this.finishFirst = false;                                                   
    this.reverseOnFinish = false;    
    this.linearColors = true;
    this.linearOpacity = true;
    this.isRunningBackward = false;
    this.isRunningForward = false;
        
    this.isInStartPosition = true;  
    
    this.duration = 1000;
    this.delay = 0;
        
    for (var property in parameters) {
        if (dL.isCSSProperty(property)) {
            this.properties[property] = parameters[property];            // keeps "endvalues"            
        } else
            this[property] = parameters[property];            
    }
    
    // retrieve current values for target. This done throughh a separate method that can be called outside of the constructor
      
    this.setValues();
    
    // set easing
        
    if (this.easing)    
        this.easing = easing[this.easing];
    if (typeof this.easing !== "function")
        this.easing = easing.linear;       
    
    
    if (typeof this.onfinish === "function")                    // in case of typos
        this.addEventListener("finish", this.onfinish);
    if (typeof this.finish === "function")
        this.addEventListener("finish", this.finish);
    
    
    if (typeof this.onreversefinish === "function")
        this.addEventListener("reversefinish", this.onreversefinish);
    
    // create CallBack function; eventHandlers can't be anonymous functions or they cannot be removed later on.
    // Therefore, callback functions need to be named. Also they reference the 'this'-variable because of the way callbacks are invoked
    
    var self = this;  
    
    if (this.hover)
        this.callback = function (event) {
        if (self.setValues) {
            self.setValues();
            self.setValues = null;            
        }
        
        if (event.type === "mouseover" || event === "start") 
            self.start();        
        else if ((event.type === "mouseout" || event === "reverse" ) && !self.isInStartPosition) 
            self.reverse();                
        }; 
    else this.callback = function () {
            if (self.setValues)  {
                self.setValues();
                self.setValues = null;
                self.start();            
                }                           
        };
}

Tween.prototype.reverseAndFinish = function () {
    if (!this.isInStartPosition && !this.isRunningBackward)
        this.reverse();
    else
        reverseFinish(this);       
}

Tween.prototype.setValues = function() {  
    for (var property in this.properties) {        
        
        // get beginvalues and difference values
        
        if (property === "color" || property === "backgroundColor") {                         
            if (this.target.style[property])
                this.beginValues[property] = dL.cssToRGBColor(this.target.style[property]);      
            else
                this.beginValues[property] = dL.cssToRGBColor("#000000");
            this.difference[property] = dL.cssToRGBColor(this.properties[property]);  // difference = final color;
        } else {
            if  (property === "opacity") {                                          
                if (dL.isIE7) 
                    this.beginValues[property] = this.target.style.filter.match(/\d+/) / 100;                 
                else
                    this.beginValues[property] = Number(this.target.style[property]); 
            } else {                    
                this.beginValues[property] = Number(this.target.style[property].match(/-?\d+/));                                        
            }
            
            if (dL.isRelativeValue(this.properties[property]))
                this.difference[property] = dL.convertRelativeValue(this.properties[property]);
            else
                this.difference[property] = this.properties[property] - this.beginValues[property];
        }
    }    
}

Tween.prototype.toString = function () {
    return "Tween";
}

Tween.prototype.start = function() {                                   // start/resume;    
    
    if (this.isRunningForward && this.finishFirst) {
        this.reverseOnFinish = false;
        return;                                                             // prevent a 'reset' when finishFirst is set to true;
    }    
    if (this.isRunningBackward && this.finishFirst) {        
        this.reverseOnFinish = true; 
        return;
    } 
    
    if (this.delay && this.startEvent !== "hover") {
        var self = this;
        setTimeout(function () {self.start()}, this.delay);
        this.delay = undefined;
        return;
    }
    
    var now = new Date().getTime();    
    if (!this.isRunningBackward || ((now - this.reverseTime) > this.duration))
        this.startTime = now;    
    else 
        this.startTime = now - this.duration + (now - this.reverseTime);    
    this.isRunningBackward = false;
    this.isRunningForward = true;        
    this.isInStartPosition = false;
    forward(this);       
};

Tween.prototype.reverse = function() {     
    if (this.isRunningBackward && this.finishFirst) {
        this.reverseOnFinish = false;
        return;                                                             // prevent a 'reset' when finishFirst is set to true;
    }
    if (this.isRunningForward && this.finishFirst) {        
        this.reverseOnFinish = true; 
        return;
    }    
    var now = new Date().getTime();      
    if (!this.isRunningForward || ((now - this.startTime) > this.duration))
        this.reverseTime = now;
    else 
        this.reverseTime = now - this.duration + (now - this.startTime);    
    this.isRunningForward = false;
    this.isRunningBackward = true;  
    backward(this);     
};

Tween.prototype.addEventListener = function (event, listener) {    
    if (!this.eventListeners)
        this.eventListeners = [];
    this.eventListeners.push({"event": event, "listener": listener});
};

Tween.prototype.removeEventListener = function (event, listener) {
    if (!this.eventListeners)
        return;
    for (var i = 0; i < this.eventListeners.length; i++)
        if (this.eventListeners[i].event === event && this.eventListeners[i].listener === listener)
                this.eventListeners.splice(i, 1);                     // removes 1 array entry at i;
};

Tween.prototype.removeAllEventListeners = function () {
    this.eventListeners = null;
}

// private helper functions

function broadcastEvent (tween, event) {
    if (!tween.eventListeners)
        return;
    for (var i = 0; i < tween.eventListeners.length; i++)
        if (tween.eventListeners[i].event === event)
            tween.eventListeners[i].listener();    
}

function forward(tween) {        
    var timeElapsed = new Date().getTime() - tween.startTime;    
    calculateValues(tween, timeElapsed);   
    if (timeElapsed < tween.duration && tween.isRunningForward)  {   // tween hasn't finished yet                
            setTimeout(function () {forward(tween);} , 0); 
    } else 
        tween.isRunningForward = false;       
    if (!tween.isRunningForward && !tween.isRunningBackward)
        finish(tween);   
};

function backward(tween) {        
    var timeElapsed = tween.duration - (new Date().getTime() - tween.reverseTime);   
    calculateValues(tween, timeElapsed);    
    if (timeElapsed > 0 && tween.isRunningBackward)  {                
            setTimeout(function () {backward(tween);} , 0);              
    } else 
        tween.isRunningBackward = false;                  
    if (!tween.isRunningForward && !tween.isRunningBackward)
        reverseFinish(tween);       
};

function finish(tween) {    
    if (tween.reverseOnFinish) {            
        tween.reverseOnFinish = false;
        tween.reverse();
        return;
    }
    
    broadcastEvent(tween, "finish");
};

function reverseFinish(tween) {        
    if (tween.reverseOnFinish) {            
        tween.reverseOnFinish = false;
        tween.start();
        return;
    } 

    tween.isInStartPosition = true;       
    broadcastEvent(tween, "reversefinish");        
};

function calculateValues (tween, timeElapsed) {   
    //if (this.startEvent === "hover")
      //  console.debug("calculateValues called on the hover tween")
    for (var property in tween.properties) {      
        
        if ((timeElapsed > 0) && (timeElapsed < tween.duration)) {    
            
           // tween is running
           
           if (property === "color" || property === "backgroundColor") {        // colors are tweened differently, using RGB values;
                var rgb = {};                
                for (var component in tween.beginValues[property]) {
                    //console.debug(tween.beginValues[property]);
                    if (tween.linearColors)                                     
                        rgb[component] = Math.round(tween.beginValues[property][component] + (tween.difference[property][component] 
                            - tween.beginValues[property][component] ) * timeElapsed / tween.duration);
                    else
                        rgb[component] = Math.round(tween.beginValues[property][component] + tween.easing(tween.difference[property][component], 
                                        tween.duration, timeElapsed));
                    //console.debug(rgb[component]);
                    if (rgb[component] > 255)
                        rgb[component] = 255;
                    if (rgb[component] < 0)    
                        rgb[component] = 0;
                }
                //console.debug(rgb);
                tween.target.style[property] = dL.cssToHexColor(rgb);                
            }             
            else if (property === "opacity") {                                  // opacity                           
               if (tween.linearOpacity) 
                    dL.cssSetOpacity(tween.target, tween.beginValues.opacity + tween.difference.opacity 
                        * timeElapsed / tween.duration);               
               else 
                    dL.cssSetOpacity(tween.target, tween.beginValues.opacity + tween.easing(tween.difference.opacity,                        
                         tween.duration, timeElapsed)); 
            }                                                                   // other properties;
            else if (dL.isCSSProperty(property))             
                tween.target.style[property] = 
                    Math.round(tween.beginValues[property] + tween.easing(tween.difference[property], tween.duration, timeElapsed))
                    + dL.cssSuffix(property);
        } else if (timeElapsed >= tween.duration) {      
            
              // set values to EndValues and stop running forward   
            
            if (property === "color" || property === "backgroundColor")        
                tween.target.style[property] = dL.cssToHexColor(tween.difference[property]);
            else if (property === "opacity")
                dL.cssSetOpacity(tween.target, tween.beginValues.opacity + tween.difference.opacity);
            else
                tween.target.style[property] = tween.beginValues[property] + tween.difference[property] + dL.cssSuffix(property);   
        } else if (timeElapsed <= 0) {       
            
            // else, set values to Beignvalues and stop running backward                     
            
            if (property === "color" || property === "backgroundColor")        
                tween.target.style[property] = dL.cssToHexColor(tween.beginValues[property]);
            else if (property === "opacity") 
                dL.cssSetOpacity(tween.target, tween.beginValues.opacity);
            else
                tween.target.style[property] = tween.beginValues[property] + dL.cssSuffix(property);        
        }  
    }
};

// Robert Penner equations
var easing = {

"linear" : function (difference, duration, timeElapsed) {
    var ratio = timeElapsed / duration;           
    return difference * ratio;
},

"squaredEaseIn": function (difference, duration, timeElapsed) {
    var ratio = timeElapsed / duration;
    return difference * ratio * ratio;
}, 

"squared": function (difference, duration, timeElapsed) {
    var ratio = 1 - timeElapsed / duration;
    return difference * ( 1 - ratio * ratio);
},

"cubicEaseIn" : function (difference, duration, timeElapsed) {
    var ratio = timeElapsed / duration;   
    return ( difference * ratio * ratio * ratio);
},
                   
"cubic": function (difference, duration, timeElapsed) {
    var ratio = 1 - timeElapsed / duration;
    return difference * (1 - ratio * ratio *ratio);
},

"overshootEaseIn": function (difference, duration, timeElapsed) {
    var ratio = timeElapsed / duration;
    var s = 1.70154 // results in 10% overshoot
    return difference * ( (s + 1) * ratio * ratio * ratio - s * ratio * ratio);
}, 

"overshoot": function (difference, duration, timeElapsed) {
    var ratio = 1 - timeElapsed / duration;
    var s = 1.70154 // results in 10% overshoot
    return difference * ( 1 - ( (s + 1) * ratio * ratio * ratio - s * ratio * ratio));
}, 

"elastic": function (difference, duration, timeElapsed) {
    var a = 30;
    var ratio = timeElapsed / duration;
    return difference * (Math.pow(2, -10 * ratio) * Math.sin ( (ratio - Math.PI / ( 2 * a)) * a ) + 1);
},  

"bounce": function (difference, duration, timeElapsed) {
    var ratio = timeElapsed / duration; 
    if (ratio < 0.364 )
        return difference * ( 7.5625 * ratio * ratio);
    if (ratio < 0.727) {
        ratio -= 0.545;
        return difference * ( 7.5625 * ratio * ratio + 0.75);
    }
    if (ratio < 0.909) {
        ratio -= 0.818;
        return difference * ( 7.5625 * ratio * ratio + 0.9375);                                
    }
    ratio -= 0.955; 
    return difference * (7.5625 * ratio * ratio + 0.984375);
}
 
};

// ----------------------
//
// augmenting the dL prototype.
//
// ----------------------

dL.prototype.tween = function () {
    
    var args = Array.prototype.slice.call(arguments);               // convert Arguments object to a real array.    
    var parameters = {};    
    var self = this; 
    var thisTween, startEvent;  
    
    if (!this.tweenArray)
        this.tweenArray = [];
        
    // loop through all the possible nodes referred to by this instance of dL;
    
    for (var j = 0; j < this.nodes.length; j++) {
    
        // to allow for flexibility, parameters can be passed in more than 
        // one object. first concatenate a single parameter object                

        for (var i = 0; i < args.length; i++) {
            if (typeof args[i] === "object") {
               concat(parameters, args[i]);
            }
            else if (dL.isEvent(args[i])) {                             
                startEvent = args[i];                
            }            
        }
   
        parameters.target = this.nodes[j];        
        parameters.startEvent = startEvent;        
        thisTween = new dL.Tween(parameters);                   
        
        
        thisTween.chain = true; //(args.indexOf("nochain") === -1);
        thisTween.removeOnComplete = true;
        
        // set autoremoval of lastTween (external) eventhandlers and remove it from tweenArray;
        
        if (thisTween.removeOnComplete) {
            thisTween.addEventListener("finish", function () {                  
                if (thisTween.startEvent)  
                    self.removeEventListener(thisTween.startEvent, thisTween.callback);                     
                self.removeTweenFromArray(thisTween);                
            });
        }        

        if (this.tweenArray.length > 0 && thisTween.chain) {                    // chain the tween;            
            var lastTween = this.tweenArray.last();
            //console.debug(lastTween);
            if (thisTween.startEvent) {                
                lastTween.addEventListener("finish", function () { 
                    self.addEventListener(thisTween.startEvent, thisTween.callback);                                                                       
                    });
            } else {
                lastTween.addEventListener("finish", function () {thisTween.callback()});                
                
            }                   
        } else
            if (thisTween.startEvent) {                     
                this.addEventListener(thisTween.startEvent, thisTween.callback);                                
            } else {
                thisTween.start();                      
            }
            
       this.tweenArray.push(thisTween);
    }        
    return this; // method chaining;
}

dL.prototype.removeTweenFromArray = function (tween) {              // can I remove a tween this way? without knowing its position in 
    if (this.tweenArray) {                                          // the array?
        for (var i = 0; i < this.tweenArray.length; i++)
            if (this.tweenArray[i] === tween) {
                this.tweenArray.splice(i, 1);  
                if (this.tweenArray.length < 1)
                    this.broadcastInternalEvent("finish");
                return true;
            }
    }
    return false;
}

dL.prototype.onHover = function () {
    var args = Array.prototype.slice.call(arguments);               // convert Arguments object to a real array.    
    var parameters = {};    
    var self = this; 
    var thisHover, lastHover;
    
    if (args[0] === null) {
        // remove and shutdown onHovers;
        
        var allTweensInStartPosition = true;
        var activeTween;
        
        if (this.hoverArray) {
            for (var i = this.hoverArray.length - 1; i > -1; i--) {                
                    allTweensInStartPosition &= this.hoverArray[i].isInStartPosition;
                    if(!allTweensInStartPosition) {
                        
                        var makeListener = function (self) {
                            return function () {
                                //console.debug("wookie!");
                                self.reverse();
                            };                            
                        };

                        for (var j = 0; j <= i; j++) {
                            this.hoverArray[j].removeAllEventListeners();
                            if (j > 0) {
                                this.hoverArray[j].addEventListener("reversefinish", 
                                    makeListener(this.hoverArray[j-1]));
                                    //console.debug("adding a listner")
                            }
                        }
                        
                        if (!this.hoverArray[i].isRunningBackward)
                            this.hoverArray[i].reverse();
                        this.removeAllEventListeners("hover");                                                                   
                        break;
                    }
                }
            if (allTweensInStartPosition) {
                this.removeAllEventListeners("hover");
                this.hoverArray = null;
            }
            else
                this.hoverArray[0].addEventListener("reversefinish", function () { 
                    self.removeAllEventListeners("hover");
                    self.hoverArray = null});         
        }
        
        return;
    }

    if (!this.hoverArray)
        this.hoverArray = [];
    else
        lastHover = this.hoverArray.last();
        
    // loop through all the possible nodes referred to by this instance of dL;
    
    for (var j = 0; j < this.nodes.length; j++) {
           
        for (var i = 0; i < args.length; i++) {
            if (typeof args[i] === "object") 
               concat(parameters, args[i]);
        }
        
        parameters.target = this.nodes[j];        
        parameters.hover = true;
        thisHover = new Tween(parameters);        
        this.hoverArray.push(thisHover);
        
        
        if (lastHover) {                       
            lastHover.addEventListener ("finish", function () {
               self.removeEventListener("hover", lastHover.callback);
               self.addEventListener("hover", thisHover.callback);
               thisHover.callback("start");
            });
            thisHover.addEventListener("reversefinish", function () {
               self.removeEventListener("hover", thisHover.callback) ;
               self.addEventListener("hover", lastHover.callback);                             
               lastHover.callback("reverse");
            });
            
        } else // no previous hoverTween;
            if (this.tweenArray && this.tweenArray.length > 0) {
                this.addInternalEventListener("finish", function () {                 
                    self.addEventListener("hover", thisHover.callback);                
                });
        } else
            this.addEventListener("hover", thisHover.callback);
        
    }
    
    return this;
        
}


dL.prototype.setOnPress = function () {
    
}

return Tween;

}()); // namespace

