
function SortedSet(comparator){
	this.comparator = comparator;
	this.back = new Array();
	this.length = 0;
}


SortedSet.prototype.putIfAbsent = function(obj){
	var idx = this.back.binarySearch(obj,this.comparator);
	if(idx >= 0){
		return this.back[idx];
	}
	this.back.splice((Math.abs(idx) - 1),0,obj);
	
	this.length++;
	return null;
}

SortedSet.prototype.contains = function(obj){
	return (this.back.binarySearch(obj,this.comparator) >= 0);
}

SortedSet.prototype.indexOf = function(obj){
	return this.back.binarySearch(obj,this.comparator);
}

SortedSet.prototype.get = function(idx){
	return this.back[idx];	
}
SortedSet.prototype.remove = function(idx,cnt){
	if(!cnt){
		cnt=1;
	}
	if(idx>=0 && idx <this.length){
		var item
		this.back.splice(idx,cnt);
		this.length = this.length - cnt;
	}
}

function TimedEventComparator(){}

TimedEventComparator.prototype.compare = function(obj1,obj2){
	if(obj1 == null){
		if(obj2==null)
			return 0;
		else
			return 1;
	}
	else if(obj2==null){
		return -1;
	}
	if(obj1.time == obj2.time)
		return 0;
	if(obj1.time < obj2.time)
		return -1;
	if(obj1.time > obj2.time)
		return 1;
}

function TimedEvent(time){
	this.time = time;
	this.sequences = new Array();
}

function Animation(){
	this.events = new SortedSet(new TimedEventComparator());
	this.frameinterval = 10;
	this.eventPointer=0;
	this.startTime;
	this.activeSequences = new Array();
	this.callbacks = new Array();
	this.timer =  null;
	this.running=false;
	this.stepper = makeDoFrame(this);
}

Animation.prototype.clone = function(){
	var anim = new Animation();
	anim.events = this.events;
	anim.frameinterval = this.frameinterval;
	return anim;
}

Animation.prototype.cancel = function(doCallback){
	if(this.running){
		this.running=false;
		if(this.timer !=null){
			window.clearTimeout(this.timer);
			this.timer=null;
		}
		
		var activelen = this.activeSequences.length;
		var seq;
		for(var i=0;i<activelen;i++){
			seq = this.activeSequences[i];
			if(!isEmpty(seq.callback)){
				seq.callback();
			}
			seq['lastFrameVal'] = null;
		}
		this.activeSequences = new Array();
		var evt;
		for(var i=this.eventPointer;i<this.events.length;i++){
			evt = this.events.get(i);
			for(var j=0;j<evt.sequences.length;j++){
				seq = evt.sequences[j];
				if(!isEmpty(seq.callback)){
					seq.callback();
				}
				seq['lastFrameVal'] = null;
			}
		}
		if(doCallback){
			var callback;
			while((callback = this.callbacks.pop())!=null){
				callback();
			}
		}
		else{
			this.callbacks = new Array();
		}
	}
}

Animation.prototype.reset = function(){
	this.eventPointer = 0;
}

Animation.prototype.addCallback = function(callback){
	this.callbacks.push(callback);
}

Animation.prototype.run = function(){
	this.running=true;
	this.startTime = new Date().getTime();
	this.doFrame(this.startTime);
}

Animation.prototype.addSequence = function(millis,sequence){
	var ev = new TimedEvent(millis);
	var pe = this.events.putIfAbsent(ev);
	if(pe!=null){
		ev = pe;
	}
	ev.sequences.push(sequence);
}

Animation.prototype.doFrame = function(time){
	if(this.running){
	
		if(!time){
		 time = new Date().getTime();
		}
		var offset = time - this.startTime;
		var event;
		var seq;
		var max = this.events.length;
		while((this.eventPointer != max)){
			event = this.events.get(this.eventPointer);
			if(event.time > offset){
				break;
			}
			var len = event.sequences.length;
			for(var i=0;i<len;i++){
				seq = event.sequences[i];
				seq['startTime']=event.time;
				if(!isEmpty(seq.init)){
					seq.init();
				}
				this.activeSequences.push(seq);
				
			}
			this.eventPointer++;
		}
		
		var activelen = this.activeSequences.length;
		var start, val, pct, rval;
		for(var i=0;i<activelen;i++){
			seq = this.activeSequences[i];
			start = seq['startTime'];
			if((start+seq.duration) <= offset){
				
				if(!isEmpty(seq.handler)){
					seq.handler(seq.end);
				}
				if(!isEmpty(seq.callback)){
					seq.callback();
				}
				seq['lastFrameVal'] = null;
				this.activeSequences.splice(i,1);
				activelen--;
				i--;
				continue;
			}
			
			if(!isEmpty(seq.handler)){
				if(!isEmpty(seq.ease)){
					val = seq.ease((offset-start),seq.start,(seq.end-seq.start),seq.duration)
				}
				else{
					pct = (offset-start)/seq.duration;
					val = ((seq.end-seq.start)*pct) + seq.start;
				}
				if(isEmpty(seq['lastFrameVal']) || isEmpty(seq.min) || (Math.abs(val - seq['lastFrameVal'])>seq.min)){
					rval = seq.handler(val);
					if(typeof rval == 'number'){
						val=rval;
					}
					seq['lastFrameVal']=val;
				}
			}
		}
		
		
		if((this.eventPointer == this.events.length) && (activelen==0)){
			this.running = false;
			this.timer=null;
			var callback;
			while((callback = this.callbacks.pop())!=null){
				callback();
			}
			return false;
		}
		else{
			var interval = this.frameinterval;
			if(activelen == 0){
				
				interval = this.events.get(this.eventPointer).time-offset;
			}
			this.timer = this.setFrameTimeout(interval);
			return true;
		}
	}
}

Animation.prototype.setFrameTimeout = function(interval){
	if(this.running){
		this.timer = window.setTimeout(this.stepper,interval);
	}
}



function makeDoFrame(animation){
	var animation = animation;
	return function(){
		animation.doFrame();
	}
}

function Sequence(){
	this.start=0;
	this.end=1;
	this.duration;
	
	this.handler;
	
	this.init;
	
	this.callback;
	
	this.ease;
	
	this.min;
}

Sequence.prototype.clone = function(){
	var seq = new Sequence();
	seq.start = this.start;
	seq.end = this.end;
	seq.duration = this.duration;
	seq.handler = this.handler;
	return seq;
}


function makeFadeHandler(elementProducer){
	var elementProducer = elementProducer;
	return function(val){
		var el = elementProducer();
		if(el){
			setOpacity(el,val);
			if(el.style.display=="none" || el.style.display==''){
				el.style.display="block";
			}
		}
	}
}


function AnimationController(){
	this.running=false;
	this.events = new SortedSet(new TimedEventComparator());
	this.frameinterval = 5;
	this.lastFrame=0;
	this.currentInterval=0;
	this.stepper = makeDoFrame(this);
	this.timer=null;
}

AnimationController.prototype.animate = function(anim){
	anim.running=true;
	anim.startTime = new Date().getTime();
	var controller = this;
	var an = anim;
	window.setTimeout(function(){controller.push(an,an.startTime);an=null;},1);
}

AnimationController.prototype.cancel = function(anim){
	var len = this.events.length;
	var looking=true;
	for(var i=0;i<len && looking;i++){
		var seqs = this.events.get(i).sequences;
		var slen = seqs.length;
		for(var j=0;j<slen;j++){
			if(seqs[j]==anim){
				seqs.splice(j,1);
				looking=false;
				anim.cancel(true);
				break;
			}
		}
	}
}

AnimationController.prototype.push = function(anim,when){
	if(anim && anim!=null){
		if(!when){
			when = new Date().getTime();
		}
		var ev = new TimedEvent(when);
		var pe = this.events.putIfAbsent(ev);
		if(pe!=null){
			ev = pe;
		}
		ev.sequences.push(anim);
	}
	if(!this.running || (this.currentInterval > this.frameinterval)){
		if(this.timer !=null){
			window.clearTimeout(this.timer);
		}
		this.running=true;
		this.currentInterval=this.frameInterval;
		this.timer=window.setTimeout(this.stepper,this.frameinterval);//this.doFrame();
	}
}


AnimationController.prototype.doFrame = function(){
	if(this.running){
		this.timer=null;
		this.currentInterval=0;
		this.lastFrame = new Date().getTime();
		var event, anims, len, anim;
		var x = 0;
		var max = this.events.length
		while((max > x) && ((event = this.events.get(x)).time <= this.lastFrame)){
			anims = event.sequences;
			len = anims.length;
			for(i=0;i<len;i++){
				anim = anims[i];
				if(anim!=null){
					var offset=-1;
					anim.setFrameTimeout = function(interval){
						if(anim.running){
							offset=interval;
						}
					}
					var cur = new Date().getTime();
					anim.doFrame(cur);
					if(offset>=0){
						this.push(anim,cur+offset);
					}
					anim=null;
				}
				anims[i]=null;
			}
			x++;
			
		}
		if(x>0){
			this.events.remove(0,x);
		}
		if(this.events.length > 0){
			var cur = new Date().getTime();
			var offset = cur-this.lastFrame;
			var interval = this.frameinterval-offset;
			if(interval < 1){
				interval = 1;
			}
			var next = this.events.get(0).time;
			interval = Math.max(interval,next-cur);
			this.currentInterval = interval;
			this.timer=window.setTimeout(this.stepper,interval);
		}
		else{
			this.running=false;
			this.timer=null;
		}
		
	}
}

masterAnimationController = new AnimationController();