
/************************************************************************
 *  IcyEffects - Powered by Brokenice Interactive
 *  (c) 2008-2009 Bechetti Luca
 *
 *  IcyGallery is freely distributable under the terms of an GPL license.
 *  For details, see brokenice.it website http://www.brokenice.it
 *
 *  based on fantastic prototype framework
 *
 ************************************************************************/
/*
 * TERMS OF USE - EASING EQUATIONS
 * 
 * Open source under the BSD License. 
 * 
 ° Thanks very much to Robert Penner
 * Copyright © 2001 Robert Penner
 * All rights reserved.
*/
// t: current time, b: begInnIng value, c: change In value, d: duration
Transition =  {
  linear: function(x, t, b, c, d) {
	return c*t/d + b;
  },
  sinus: function( x, t, b, c, d) {
	return Math.sin(t/d*2*Math.PI) * c + b;
  },
  cosinus: function( x, t, b, c, d) {
	return Math.cos(t/d*2*Math.PI) * c + b;
  },
  easeInQuad: function (x, t, b, c, d) {
  	return c*(t/=d)*t + b;
  },
  easeOutQuad: function (x, t, b, c, d) {
  	return -c *(t/=d)*(t-2) + b;
  },
  easeInOutQuad: function (x, t, b, c, d) {
  	if ((t/=d/2) < 1) return c/2*t*t + b;
  	return -c/2 * ((--t)*(t-2) - 1) + b;
  },
  easeInCubic: function (x, t, b, c, d) {
  	return c*(t/=d)*t*t + b;
  },
  easeOutCubic: function (x, t, b, c, d) {
  	return c*((t=t/d-1)*t*t + 1) + b;
  },
  easeInOutCubic: function (x, 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;
  },
  easeInQuart: function (x, t, b, c, d) {
  	return c*(t/=d)*t*t*t + b;
  },
  easeOutQuart: function (x, t, b, c, d) {
  	return -c * ((t=t/d-1)*t*t*t - 1) + b;
  },
  easeInOutQuart: function (x, 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;
  },
  easeInQuint: function (x, t, b, c, d) {
  	return c*(t/=d)*t*t*t*t + b;
  },
  easeOutQuint: function (x, t, b, c, d) {
  	return c*((t=t/d-1)*t*t*t*t + 1) + b;
  },
  easeInOutQuint: function (x, 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;
  },
  easeInSine: function (x, t, b, c, d) {
  	return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
  },
  easeOutSine: function (x, t, b, c, d) {
  	return c * Math.sin(t/d * (Math.PI/2)) + b;
  },
  easeInOutSine: function (x, t, b, c, d) {
  	return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
  },
  easeInExpo: function (x, t, b, c, d) {
  	return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
  },
  easeOutExpo: function (x, t, b, c, d) {
  	return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
  },
  easeInOutExpo: function (x, 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;
  },
  easeInCirc: function (x, t, b, c, d) {
  	return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
  },
  easeOutCirc: function (x, t, b, c, d) {
  	return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
  },
  easeInOutCirc: function (x, 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;
  },
  easeInElastic: function (x, t, b, c, d) {
  	var s=1.70158;var p=0;var a=c;
  	if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
  	if (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;
  },
  easeOutElastic: function (x, t, b, c, d) {
  	var s=1.70158;var p=0;var a=c;
  	if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
  	if (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;
  },
  easeInOutElastic: function (x, t, b, c, d) {
  	var s=1.70158;var p=0;var a=c;
  	if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
  	if (a < Math.abs(c)) { 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;
  },
  easeInBack: function (x, t, b, c, d, s) {
  	if (s == undefined) s = 1.70158;
  	return c*(t/=d)*t*((s+1)*t - s) + b;
  },
  easeOutBack: function (x, t, b, c, d, s) {
  	if (s == undefined) s = 1.70158;
  	return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
  },
  easeInOutBack: function (x, t, b, c, d, s) {
  	if (s == undefined) 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;
  },
  easeInBounce: function (x, t, b, c, d) {
  	return c - Transition.easeOutBounce (x, d-t, 0, c, d) + b;
  },
  easeOutBounce: function (x, 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;
  	}
  },
  easeInOutBounce: function (x, t, b, c, d) {
  	if (t < d/2) return Transition.easeInBounce (x, t*2, 0, c, d) * .5 + b;
  	return Transition.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
  },
  swing:function(B,C,A,E,D){
  	return((-Math.cos(C/D*Math.PI)/2)+0.5)*E+A
  }
}

//Metodi che estendono gli oggetti Element di prototype
var IcyEffects = {
	/*
	 * Animate
	 * recupare l'elenco degli attributi da animare
	*/
	animate : function(element, attributes,time,options){
		//Creo l'oggetto da animare
		var defOpt = {
				duration: time, 
				transition: Transition.swing,
				Ended			: Prototype.emptyFunction,
    			Started			: Prototype.emptyFunction,
    			Beforestarder	: Prototype.emptyFunction,
    			play			: true
		};
		
		element.obj = new IcyObject(element).set(attributes);
		//Setto le opzioni
		options = options || {};
		Object.extend(defOpt, options);
			
		element.obj.setOptions(defOpt);
			
		
			//REGISTRO GLI EVENTI
			if(Object.isFunction(defOpt.onEnded)) element.obj.onEnded(defOpt.onEnded);
			if(Object.isFunction(defOpt.onBeforeStarted)) element.obj.onBeforeStarted(defOpt.onBeforeStarted);
			if(Object.isFunction(defOpt.onStarted)) element.obj.onStarted(defOpt.onStarted);
			if(Object.isFunction(defOpt.onStopped)) element.obj.onStopped(defOpt.onStopped);

		
		//Avvio l'animazione
		
		if(defOpt.play)	
			element.obj.play();
		return element.obj;
		
	},
	
	/*
	EFFETTI BASE ESTENSIONE DEGLI ELEMENTI
		fade, appear, higlight, blindUp, blindDown
	*/
	fade: function(element) {
	    if (!(element = $(element)) || ($(element).obj !== undefined)) return;
	    $(element).animate({opacity: 0}, 1000);
    },
  
	appear: function(element) {
	    if (!(element = $(element)) || ($(element).obj !== undefined) || $(element).visible() ) return;
	    $(element).setOpacity(0);
	    $(element).show();
	    $(element).animate({opacity: 1}, 1000);
	},

    highlight: function(element, options) {
	    if (!(element = $(element)) || ($(element).obj !== undefined) && (!element.visible() || $(element).obj.isPlaying()) ) return;
	    options = options || {};
	    var highlightColor = options.highlightColor || "#ffff99";
	    var originalColor = element.getStyle('background-color');
	    $(element).setStyle({backgroundColor: highlightColor});
	    $(element).animate({backgroundColor: originalColor}, 500);
    },
    
    blindDown: function(element) {
    	if (!(element = $(element)) || ($(element).obj !== undefined) || $(element).visible() ) return;
    	var height = element.getHeight();

    	$(element).animate({height: height + 'px'},1000,{
    	  	onBeforeStarted : function() {element.show(); element.style.height = '0px';}
      	});
  	},
  	
  	blindUp: function(element, options) {
    	var originalHeight;
    	if (!(element = $(element)) || ($(element).obj !== undefined) && (!element.visible() || $(element).obj.isPlaying()) ) return;
     	$(element).animate({height:0},1000,{
     	onBeforeStarted: function(){originalHeight = element.getHeight(); },
     		onEnded : function() {element.hide(); element.setStyle({'height' : originalHeight + 'px'});}
     	});
    }
}
//Estendo gli elementi di prototype con l'animazione
Element.addMethods(IcyEffects);


/**
 * Classe per estendere le classi al supporto eventi
 * thanks to Matthew Foster
 *
**/
var EventDispatcher = Class.create({});
Object.extend(EventDispatcher.prototype,{
						
	buildListenerChain : function(){
							
		if(!this.listenerChain)
			this.listenerChain = {};							
	
	},
	addEventListener : function(type, listener){
		
		this.buildListenerChain();
							
		if(!this.listenerChain[type])					
			this.listenerChain[type] = [listener];
		else
			this.listenerChain[type].push(listener);
		
	},
	hasEventListener : function(type){
							
		return (typeof this.listenerChain[type] != "undefined");
			
	},
	
	removeEventListener : function(type, listener){
		if(!this.hasEventListener(type))
			return false;
							
		for(var i = 0; i < this.listenerChain[type].length; i++)
			if(this.listenerChain[type][i] == listener)
				this.listenerChain[type].splice(i, 1);
						
	},
	
	dispatchEvent : function(type, args){
		this.buildListenerChain();
							
		if(!this.hasEventListener(type))
			return false;
					
		this.listenerChain[type].any(function(f){ return (f(args) == false ? true : false); });						
	},
	
	on : function(type, listener){
		this.addEventListener(type, listener);
	},
	
	fire : function(type, args){
		this.dispatchEvent(type, args);
	}
});


/*
 * TimeLine
 * classe per schedulare gli effetti
*/
TimeLine = Class.create({
		
	/*
	* Initialize
	* costruttore della classe
	*/
	initialize : function(){
		this.effects = new Array;	
		this.currentPlay = new Array;  
		this.isRun = false;
		this.allEff = new Array();
	},
	
	/*
	* add
	* funziona per aggiungere un effetto alla timeline
	*/
	add : function(obj, ritardo, posizione){
	
		//valori di default
		this.position = {
			after: null,
			before : null,
			started : null
		}
		posizione = posizione || {};
		Object.extend(this.position, posizione);
		//Se non ha vincoli lo inserisco in coda alla timeline
		if(this.position.after ==  null && this.position.before ==  null && this.position.started == null){
			this.e = new TimeLineItem(obj, ritardo);
			this.effects.push(this.e);
			this.registerEvent(this.e);
			this.allEff.push(this.e);
		//Trovo il punto in cui inserirlo	
		}else if (this.position.after != null){
			this.insObj(this.position.after, obj, ritardo, 'after');
		}else if (this.position.before != null)
			this.insObj(this.position.before, obj, ritardo, 'before');
		else if (this.position.started != null)
			this.insObj(this.position.started, obj, ritardo, 'started');
	
	},
	
	/*
	* remove
	* rimuove un oggetto dalla timeline
	*/
	remove : function(obj){
		this.allEff = this.allEff.reject(function(item) { 
		
			if(item.obj == obj){
				item.remove(this.allEff);
				return true;
			}else
				return false; 
		
		}.bind(this));
		this.effects = this.effects.reject(function(item) { return item.obj == obj; });
	},
	
	/*
	 * search
	 * cerca l'ogetto in ricorsione nelle code
	*/
	search : function(i, posObj){
		i.started.each(function(itemSt){
				if(itemSt.obj == posObj){
					this.found = true;
					this.item = itemSt;
					this.parentItem = i;
				}else
					this.search(itemSt, posObj);
		}.bind(this));
		i.ended.each(function(itemEn){
				if(itemEn.obj == posObj){
					this.found = true;
					this.item = itemEn;
					this.parentItem = i;
				}else
					this.search(itemEn, posObj);
		}.bind(this));
		i.beforeStarted.each(function(itemBf){
				if(itemBf.obj == posObj){
					this.found = true;
					this.item = itemBf;
					this.parentItem = i;
				}else
					this.search(itemBf, posObj)
		}.bind(this));
	},
	
	/*
	* insObj
	* funzione per inserire un oggetto nell'array apposito
	*/
	insObj : function(posObj, object, ritardo, pos){
		//Cerco l'oggetto in tutte le code
		this.found = false;
		this.parentItem = null;
		this.effects.find(function(item) {
			if(item.obj == posObj){
				this.found = true;
				this.item = item;
			}
		}.bind(this));
		if(!this.found)	this.effects.each(function(e){this.search(e, posObj)}.bind(this));
		
		//Se è stato trovato lo aggiungo al suo posto
		if(this.found){

				this.e = new TimeLineItem(object, ritardo);
				this.registerEvent(this.e);
				this.allEff.push(this.e);
				switch(pos){
					case "after":	//In coda
						this.item.insertEnded(this.e);
					break;
					case "before":	//In testa
						if(this.parentItem != null){
							this.parentItem.insertEnded(this.e);
							this.parentItem.ended = this.parentItem.ended.without(this.item);
						}else{
							this.effects = this.effects.without(this.item);
							this.effects.push(this.e);
						}
						this.e.insertEnded(this.item);
					break;
					case "started":	//Partenza insieme
						this.item.insertStarted(this.e);
					break;
				}
		}
	},
	
	/*
	* registerEvent
	* funzione per registrare gli eventi delle animazioni
	*/
	registerEvent : function(o){
			o.obj.addEventListener("anim:ended", function(){
				o.ended.each(function(e){
					
					if(!e.run){
						if(e.ritardo == 0){
							e.obj.play();
						}else
							window.setTimeout(e.obj.play.bind(e.obj),e.ritardo);
						//Delete current animation
						this.currentPlay.push(e.obj);
						e.play();
					}
				}.bind(this));
				this.currentPlay = this.currentPlay.without(o.obj);
				if(this.currentPlay.size() == 0)
					this.stop();
			}.bind(this));
			o.obj.addEventListener("anim:started", function(){
				o.started.each(function(e){
					if(!e.run){
						if(e.ritardo == 0){
							e.obj.play();
						}else
							window.setTimeout(e.obj.play.bind(e.obj),e.ritardo);
						//Delete current animation
						this.currentPlay.push(e.obj);
						e.play();
					}
				}.bind(this));
			}.bind(this));
			o.obj.addEventListener("anim:beforestarted", function(){
				o.beforeStarted.each(function(e){
					if(!e.run){
						if(e.ritardo == 0){
							e.obj.play();
						}else
							window.setTimeout(e.obj.play.bind(e.obj),e.ritardo);
						//Delete current animation
						this.currentPlay.push(e.obj);
						e.play();
					}
				}.bind(this));
			}.bind(this));
	},
	
	/*
	* run
	* funzione che da il via alle animazioni
	*/
	run : function(){

			//Avvio tutti gli effetti pronti
			this.effects.each(function(o){
				
				if(!o.run && o.ritardo == 0){
					o.obj.play();
					o.play();
					this.currentPlay.push(o.obj);
				}else if(!o.run){
					window.setTimeout(o.obj.play.bind(o.obj),o.ritardo);
					o.play();
					this.currentPlay.push(o.obj);
				}
				
			}.bind(this));

	},
	
	/*
	* stop
	* ferma la timeline corrente
	*/
	stop : function(){
		this.isRun = false;
	},
	
	/*
	* pause
	* ferma momentaneamente tutti gli effetti al punto in cui si trovano
	*/
	pause : function(){
		this.allEff.reverse(false).each(function(e){
			e.stop();
		});
	},
	
	/*
	 * play
	 * fa ripartire la timeline
	*/
	play : function(){
		this.currentPlay.each(function(e){
			e.play();
		});
	},
	
	/*
	* rewind
	* riavvolge la timeline
	*/
	rewind : function(obj) {
		
		this.allEff.reverse(false).each(function(e){
		
			e.stop();
			e.obj.rewind();
		});
		this.stop();
  }
		
});

/*
* Class TimeLineItem
* Ogggetto delle timeline
*/

TimeLineItem = Class.create({

	/*
	* initialize
	* costruttore della classe
	*/
	initialize : function(elem, ritardo){
		this.obj = elem;
		this.ended = new Array;
		this.started = new Array;
		this.beforeStarted = new Array;
		this.ritardo = ritardo;
		this.run = false;
	},
	
	/*
	* initialize
	* inserisce l'animazione in coda a se stessa
	*/
	insertEnded : function(obj){
		this.ended.push(obj);
	},
	
	/*
	* initialize
	* fa partire l'animazione prima di se stessa
	*/
	insertBeforeStarted : function(obj){
		this.started.push(obj);
		
	},
	
	/*
	* insertStarted
	* inserisce l'animazione in testa a se stessa
	*/
	insertStarted : function(obj){
		this.beforeStarted.push(obj);
	},
	
	/*
	* stop
	* marca l'oggetto come eseguito
	*/
	stop : function(){
		this.run = false;
	},

	/*
	* paly
	* marca l'oggetto come in esecuzione
	*/
	play : function(){
		this.run = true;
	},

	remove : function(allEff){
		this.ended.each(function(f){allEff = allEff.without(f)});
		this.started.each(function(f){allEff = allEff.without(f)});
		this.beforeStarted.each(function(f){allEff = allEff.without(f)});
		this.ended.clear();
		this.started.clear();
		this.beforeStarted.clear();
		
	}
});


/*
 * CLASSE BASE PER L'OGGETTO DA ANIMARE
 *
*/
IcyBase = Class.create(EventDispatcher, (function() {

  /** 
   *  initialize
   *  costruttore della classe
   **/
  function initialize(options) {
  	//DEFAULT PARAMETRI
    this.defaultDuration		= 500,
	this.defaultTransition 		= Transition.linear,
    this.options				= Object.extend(Object.clone({duration: this.defaultDuration, transition: this.defaultTransition}), options);
    //PARAMETRI DELL'ANIMAZIONE
    this.animationIteration		= null;		//tempo attuale animazione
    this.playing				= false;	//oggetto in animazione o no
    this.cycle					= false;	//animazione ciclica
    //EVENTI SOLLEVATI DALLE ANIMAZIONI
    this.on	= {
    	Ended			: Prototype.emptyFunction, 	//Sollevato ad animazione conclusa
    	Started			: Prototype.emptyFunction,	//Sollevato ad animazione iniziata
    	Beforestarder	: Prototype.emptyFunction	//Sollevata prima che inizi l'animazione
    };
    //Setto le opzioni
    this.setOptions(options);
 
  }
 
  
  /** 
   * setOptions
   * Setta le opzioni passate come argomenti 
   **/
  function setOptions(options) {
    Object.extend(this.options, options || {});  
    return this; 
  }
  
  /** 
   * getDuration
   * restituisce la durata dell'animazione
   **/
  function getDuration() {
    return this.options.duration;
  }
  
  /** 
   * isPlaying
   * Mi dice se l'oggetto è animato
   **/
  function isPlaying() {
    return this.playing;
  }
  
  /** 
   *  setAnimationType
   *  setta il tipo di animazione
   *	loop
   **/
  function setAnimationType(type, count) {
    this.cycle = type == 'none' ? false : {type: type, count: count || 1, current: 1}
    return this;
  }
  
  /** 
   *  play
   *  funzione che inizializza l'animazione
   *
   **/
  function play() {
  	//Se l'oggetto è già animato ritorno
    if (this.playing) return;
    this.element.obj = this;
    //Sollevo l'evento ed avvio l'animazione
    this.fireCall('beforeStarted');
    this.playing = true;

    //Resetto i controlli per la nuova animazione
    if (this.animationIteration == null) {
      this.animationIteration = 0;
      this.initializeAnimation();
    }
    //Aggiungo l'oggetto al timer che schedula le animazioni
    IcyTimer.register(this);
	//Sollevo l'evento di avvio
    this.fireCall('started');
    return this;
  }
  
  /** 
   *  stop
   *  fermo l'animazione alla posizione attuale
   **/
  function stop() {
  	//Sollevo l'evento
    this.fireCall('stopped');
    //Rimuovo l'oggetto dal timer
    IcyTimer.unregister(this);
    this.playing = false;
    this.element.obj = undefined;
    return this;
  }
  
  /** 
   *  rewind
   *  riavvolge l'animazione, simile al flash player
   **/
  function rewind() {
    //ferma l'animazione
    this.stop();
    //Riparto da 0
    this.repaint(0);
    this.animationIteration = null;
    this.element.obj = undefined;
    //Se è un animazione ciclica la risetto a 1
    if (this.cycle) this.cycle.current = 1;
    return this;
  }

  /** 
   *  timerUpdate
   *  viene periodicamente chiamata dal timer
   **/
  function timerUpdate(dx) {
   
    //Aggiorno il tempo corrente dell'animazione
    this.animationIteration += dx;
        
    //Controllo se l'animazione sia finita o meno
    if (this.animationIteration > this.getDuration() || this.animationIteration < 0) {
      //setto l'animazione all'ultima posizione
      this.repaint(this.animationIteration < 0 ? 0 : 1);
      //se è un animazione ciclica che non è ancora finita
      if (this.cycle && this.cycle.current < this.cycle.count) {
      	//se è un loop incremento e poi riparto
        if (this.cycle.type == 'loop') {
          this.cycle.current++;
          this.repaint(0);
          this.animationIteration =0;
        }
      } else {	
      	//Animazione ciclica finita
        this.stopAnimation();
        this.fireCall('ended');
		//Elimino l'oggetto dal timer
        IcyTimer.unregister(this);
        this.element.obj = undefined;
      	//Azzero le variabili
        this.animationIteration = null;
        this.playing   = false;
        if (this.cycle) {
          this.cycle.current = 0;
        }
      }
    }
    //L'animazione è semlice
    else {
      //Ottengo i dati dalla transitions scelta ed effettuo il repaint del componente
      var pos = this.options.transition(this.animationIteration / this.getDuration(), this.animationIteration, 0, 1, this.getDuration());
      this.repaint(pos);
    }
  }
  
  /*
  * onEnded
  * Setta la funziona da richiamare ad animazione finita
  */
  function onEnded(callback) {
    this.on.onEnded = callback;
    return this;
  }
  
  /*
  * onStarted
  * Setta la funziona da richiamare ad animazione iniziata
  */
  function onStarted(callback) {
    this.on.onStarted = callback;
    return this;
  }
  
  /*
  * onBeforeStarted
  * Setta la funziona da richiamare prima che inizi l'animazione
  */
  function onBeforeStarted(callback) {
    this.on.onBeforestarted = callback;
    return this;
  }
 
  /*
  * onStopped
  * Setta la funziona da richiamare quando l'animazione viene stoppata
  */
  function onStopped(callback) {
    this.on.onStopped = callback;
    return this;
  }
  
  /*
  * fireCall
  * Richiama le funzioni passate come argomenti
  */
  function fireCall(eventName) {
    var callback;
    switch(eventName.capitalize()){
    	case "Ended":
    		this.fire("anim:ended");
    		if(Object.isFunction(this.on.onEnded))this.on.onEnded(this);
  		break;
  		case "Started":
  			this.fire("anim:started");
    		if(Object.isFunction(this.on.onStarted))this.on.onStarted(this);
  		break;
  		case "Beforestarted":
  			this.fire("anim:beforestarted");
    		if(Object.isFunction(this.on.onBeforestarted))this.on.onBeforestarted(this);
  		break;
  		case "Stopped":
    		if(Object.isFunction(this.on.onStopped))this.on.onStopped(this);
  		break;
  	}
  }

  /*
   * repaint
   * definita nella classe superiore
  */
  function repaint(pos) {
   
  }
 
  return {           
    initialize:      initialize,
    setOptions:      setOptions,
    setAnimationType:        setAnimationType,
    getDuration:     getDuration,
    play:            play,
    stop:            stop,
    rewind:          rewind,
    isPlaying:       isPlaying,
    timerUpdate: timerUpdate,
    initializeAnimation:  Prototype.emptyFunction,
    stopAnimation:   Prototype.emptyFunction,
    repaint: repaint,
    fireCall:            fireCall,
    onStarted:       onStarted,
    onEnded:         onEnded,
    onBeforeStarted: onBeforeStarted,
    onStopped:		 onStopped
  }
})());
/** 
 *  class IcyAttribute
 *  classe per definire gli attributi dell'oggetto
 **/
 IcyAttribute = Class.create((function() {
 
   /** 
    * initialize
    * costruttore della classe
    *  @PRAMA key - Il nome dell'attributo
    *  @PARAM from - (String | Number): colore RGB o valore
   **/
  function initialize(key, start, end) {
    this.key    = key;
    this.from   = start;
    this.to     = end;
    this.type   = isColorOrNumber(this.from);
    this.unit   = cUnit(this.from);
    this.startVal = calculateStartValue(this.from, this.isColor());
    this.toVal   = calculateEndValue(this);

  }
  
  /** 
   *  convert
   *  converte il valore o RGB
   **/
  function convert(pos) {
    if (this.isNumber()) 
      return numberToValue(this.startVal, this.toVal, pos) + this.unit;
    // Else it's a color
    else {
      var r = parseInt(numberToValue(this.startVal[0], this.toVal[0], pos));
      var g = parseInt(numberToValue(this.startVal[1], this.toVal[1], pos));
      var b = parseInt(numberToValue(this.startVal[2], this.toVal[2], pos));
      return "rgb(" + r + ", " + g + ", " + b + ")";
    }
  }

  /** 
   *  reset
   *  
   **/
  function reset(from) {
    if (from && this.relative)
      this.from = from;
    this.startVal = calculateStartValue(this.from, this.isColor());
    this.relative = false;
    this.toVal   = calculateEndValue(this);
  }
  
  /** 
   *  isColor
   *  ritorna true se il tipo dell'attributo è un colore
   **/
  function isColor() {
    return this.type == 'Color'
  }
  
  /** 
   *  isNumber
   *  ritorna true se il tipo dell'attributo è un numero
   **/
  function isNumber() {
    return this.type == 'Number'
  }
  
  /*
   * isColorOrNumber
   * restituisce se il valore passato è un colore o un numero
  */
  function isColorOrNumber(value) {
    if (Object.isString(value) && value.isColor())
      return 'Color'
    else
      return 'Number'
  }
  
  /** 
   *  cUnit
   *  
   **/
  function cUnit(from) {
    var match;
    if (Object.isString(from) && (match = from.match(/(\d+)([\w\%]*)/)))
      return match[2];
    else
      return '';
  }
  
  /*
  * calculateStartValue
  * calcola il valore iniziale per gli attributi
  */
  function calculateStartValue(from, color) {
    if (color) 
      return from.getRGB();
    else if (Object.isString(from))
      return parseFloat(from);
    else
      return from;
  }
  
  /*
  * calculateEndValue
  * calcola il valore reale a secondo del tipo di attributo
  */
  function calculateEndValue(attribute) {	
  	//se è un colore ritorno l'RGB
    if (attribute.isColor())
      return attribute.to.getRGB();
    //Se è una stringa controllo la presenza degli operatori
    else if (Object.isString(attribute.to)) {
      if (isOperator(attribute.to))
        return parseOp(attribute);
      else
        return parseFloat(attribute.to);
    }
    else
      return attribute.to;
  }
  
  /*
  * numberToValue
  * 
  */
  function numberToValue(from, to, pos) {
    return from + (to - from) * pos;
  }
  
  /*
  * isOperator
  * contralle se la stringa passata è un operatore
  */
  function isOperator(string) {
    return string.match(/^([\+\-\*\/]=)(\d*)$/);
  }
  
  /*
   * parseOp
   * trova l'operatore passato a aggiusta il valore di arrivo dell'attributo
  */
  function parseOp(attribute) {
    attribute.relative = true;
    var match = isOperator(attribute.to);

    if ((match[1] == '-=') || (!match[1] == '+='))
      return attribute.startVal - parseFloat(match[2]);
    else if ((match[1] == '+=') || (!match[1] == '-='))
      return attribute.startVal + parseFloat(match[2]);
    else if ((match[1] == '*=') || (!match[1] == '/='))
      return attribute.startVal * parseFloat(match[2]);
    else if ((match[1] == '/=') || (!match[1] == '*='))
      return attribute.startVal / parseFloat(match[2]);
  }
  
  return {
    initialize: initialize,
    convert:    convert,
    reset:      reset,
    isNumber:   isNumber,
    isColor:    isColor
  };
})());
/** 
 *  class IcyTimer
 *  il timer che animare gli oggetti
 **/
IcyTimer = (function() {
  var timer     = null, 
      freq 		= 900/60,
      lastCall  = null,
      objects   = new Array();
  
  /** 
   * register
   * registro un oggetto da animare al timer
   *
   **/
  function register(object) {
    if (!isObjectReg(object)) {
      objects.push(object);
      play();
    }
  }
  
  /** 
   *  unregister
   *  Rimuovo l'oggetto dal timer 
   **/
  function unregister(object) {
    objects = objects.reject(function(item) { return item == object; });
    //Se non ci sono più oggetti fermo il timer, le animazioni sono finite
    if (objects.length == 0)
      stop();
  }
  
  /** 
   *  isObjectReg 
   *  controllo se un oggetto è già registrato nel timer
   **/
  function isObjectReg(object) {
    return objects.find(function(item) { return item == object; });
  }

  /** 
   *  play 
   *  avvia il timer
   **/
  function play() {
  	//Se non c'è già un timer
    if (timer == null) {
      //avvio un nuovo timer
      lastCall = new Date().getTime();
      timer    = setInterval(timerCall, freq);
      timerCall();
    }
  }
  
  /** 
   *  stop 
   *  ferma il timer 
   **/
  function stop() {
    if (timer) {
      clearInterval(timer);
      timer    = null;
      lastCall = 0;
    }
  }
  
   /** 
   *  timerCall 
   *  chiamata periodicamente va ed effettuare il repaint dei vari oggetti 
   **/
  function timerCall() {
    var now   = new Date().getTime();
    var dx = now - lastCall;
    lastCall  = now;
    //Chiamo il repaint degli oggetti registrati
    objects.invoke('timerUpdate', dx);
  }
  
  return {
    register:     register,
    unregister:   unregister,
    isObjectReg: isObjectReg
  }
})();


Object.extend(String.prototype, {
  //thanks to predator
  isColor: function() {return this.match(/^\#/) || this.match(/^rgb\(/);},
  getRGB: function() {
    function toDec(string) {
      if (string.length == 1)
        string += string;
      return parseInt(string, 16);
    }
  
    if (this.isColor()) {
      var match;
      // controllo rgb(r,g,b)
      if (match = this.match(/(^rgb\()(\d+), (\d+), (\d+)(\))/i)) 
        return [parseInt(match[2]), parseInt(match[3]), parseInt(match[4])];
      // controllo #xxx
      if (match = this.match(/^\#([0-9a-f]{1})([0-9a-f]{1})([0-9a-f]{1})$/i)) {
        return match.slice(1).collect(toDec);
      }
      // controllo #xxxxxx 
      if (match = this.match(/^\#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)) 
        return match.slice(1).collect(toDec);
    }    
    return null;
  }
});

/** 
 *  class IcyObject
 *  classe per gli oggetti da animare
 **/
IcyObject = Class.create(IcyBase, (function() {

  /** 
   *  initialize
   *  costruttore della classe, crea l'oggetto da animare
   **/
  function initialize($super, element, options) {
    $super(options);
    this.element = $(element);
  }
  
  /*
   * set
   * setta gli attributi per l'oggetto
  */
  function set(attributes) {
    this.originalAttributes = attributes;    
    return this;
  }
    
  /*
   * createAllAttributes
   * crea un ogetto IcyAttribute per ogni elemento passato
  */
  function initializeAnimation() {
  	//Se non ci sono gli attributi li creo
    this.attributes = this.attributes || createAllAttributes(this.originalAttributes, this.element);
	//Restto il valore degli attributi
    this.attributes.each(function(attribute) {
      attribute.reset(this.element.getStyle(attribute.key));
    }, this);
  } 
  /*
   * createAllAttributes
   * crea un ogetto IcyAttribute per ogni elemento passato
  */
  function createAllAttributes(attributes, element) {
    var array = [];
    $H(attributes).each(function(item) {
      array.push(new IcyAttribute(item.key, (element.getStyle(item.key) == null) ? '0px' : element.getStyle(item.key), item.value));
    });
    return array;
  }
  
    
  /*
   * repaint
   * Setta il nuovo valore per ogni attributo
  */
  function repaint(pos) {
    var style = {};
    if(this.attributes!==undefined){
    this.attributes.each(function(attribute) {
      style[attribute.key] = attribute.convert(pos);
    }, this);
    this.element.setStyle(style);
    }
  }
   
  
  
  return {
    initialize:      initialize,
    set:         set,
    initializeAnimation:  initializeAnimation,
    repaint: repaint
  }
})());

//THANKS TO JQUERY.FX, PROTOTYPE, PROtoVal, SCRIPT.ACULO.US AN MORE ANIMATION FRAMEWORKS FOR IDEAS//
