/*
 * Jany JavaScript Library for Tween animation
 *
 * Copyright (c) 2010 Jimbo
 * Dual licensed under the MIT and GPL licenses.
 *
 * 
 */

  function chill(o,opts){
    var context = this;
    var sets = this.sets = {};
    this.extend(sets,this.def);
    this.tag = o;
    this.history = []; //want to be implemented
    this.paralle = [];
    this.age = 0;
    this.queue = [];
    this.going = null;
    this.enqueue(opts);
    this.go();

    //protected methods
    //public methods
  }
  chill.prototype = {
    def:{
      //set:[{prefix:null,property:null,postfix:null,from:null,to:null}], //property over control set
      callback:null,
      time:"normal",
      agility:10,
      type:"linear",
      queue:true,
      prefix:"",
      property:"",
      postfix:"",
      from:null,
      to:null
    },
    extend:function (o,ox){
      if(!o) throw new Error("It can't extend an null object.");
      for(var p in ox){
        if (typeof(ox[p])=="object"){
          o[p] = {};
          this.extend(o[p],ox[p]);
        }else{
          o[p]=ox[p];
        }
      }
      return this;
    },
    tween:{
      linear:function(t,b,c,d){return b+c/d*t;},
      easeInQuad: function(t, b, c, d) {return c*(t/=d)*t + b;},    
      easeOutQuad: function(t, b, c, d) {return -c *(t/=d)*(t-2) + b;},    
      easeInOutQuad: function(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(t, b, c, d) {return c*(t/=d)*t*t + b;},    
      easeOutCubic: function(t, b, c, d) {return c*((t=t/d-1)*t*t + 1) + b;},    
      easeInOutCubic: 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;
      },    
      easeOutInCubic: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutCubic(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInCubic((t*2)-d, b+c/2, c/2, d);
      },    
      easeInQuart: function(t, b, c, d) {return c*(t/=d)*t*t*t + b;},    
      easeOutQuart: function(t, b, c, d) {return -c *((t=t/d-1)*t*t*t - 1) + b;},    
      easeInOutQuart: 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;
      },    
      easeOutInQuart: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutQuart(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInQuart((t*2)-d, b+c/2, c/2, d);
      },    
      easeInQuint: function(t, b, c, d) {return c*(t/=d)*t*t*t*t + b;},    
      easeOutQuint: function(t, b, c, d) {return c*((t=t/d-1)*t*t*t*t + 1) + b;},    
      easeInOutQuint: 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;
      },    
      easeOutInQuint: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutQuint(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInQuint((t*2)-d, b+c/2, c/2, d);
      },    
      easeInSine: function(t, b, c, d) {return -c * Math.cos(t/d *(Math.PI/2)) + c + b;},    
      easeOutSine: function(t, b, c, d) {return c * Math.sin(t/d *(Math.PI/2)) + b;},    
      easeInOutSine: function(t, b, c, d) {return -c/2 *(Math.cos(Math.PI*t/d) - 1) + b;},    
      easeOutInSine: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutSine(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInSine((t*2)-d, b+c/2, c/2, d);
      },    
      easeInExpo: function(t, b, c, d) {
          return(t==0) ? b : c * Math.pow(2, 10 *(t/d - 1)) + b - c * 0.001;
      },    
      easeOutExpo: function(t, b, c, d) {
          return(t==d) ? b+c : c * 1.001 *(-Math.pow(2, -10 * t/d) + 1) + b;
      },    
      easeInOutExpo: 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 - c * 0.0005;
          return c/2 * 1.0005 *(-Math.pow(2, -10 * --t) + 2) + b;
      },    
      easeOutInExpo: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutExpo(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInExpo((t*2)-d, b+c/2, c/2, d);
      },    
      easeInCirc: function(t, b, c, d) {return -c *(Math.sqrt(1 -(t/=d)*t) - 1) + b;},    
      easeOutCirc: function(t, b, c, d) {return c * Math.sqrt(1 -(t=t/d-1)*t) + b;},    
      easeInOutCirc: 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;
      },    
      easeOutInCirc: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutCirc(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInCirc((t*2)-d, b+c/2, c/2, d);
      },    
      easeInElastic: function(t, b, c, d, a, p) {
          var s;
          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; s=p/4; } else 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(t, b, c, d, a, p) {
          var s;
          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; s=p/4; } else 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(t, b, c, d, a, p) {
          var s;
          if(t==0) return b;  if((t/=d/2)==2) return b+c;  if(!p) p=d*(.3*1.5);
          if(!a || a < Math.abs(c)) { a=c; s=p/4; }       else 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;
      },    
      easeOutInElastic: function(t, b, c, d, a, p) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutElastic(t*2, b, c/2, d, a, p);
          return JSTweener.easingFunctions.easeInElastic((t*2)-d, b+c/2, c/2, d, a, p);
      },    
      easeInBack: function(t, b, c, d, s) {
          if(s == undefined) s = 1.70158;
          return c*(t/=d)*t*((s+1)*t - s) + b;
      },    
      easeOutBack: function(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(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;
      },    
      easeOutInBack: function(t, b, c, d, s) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutBack(t*2, b, c/2, d, s);
          return JSTweener.easingFunctions.easeInBack((t*2)-d, b+c/2, c/2, d, s);
      },    
      easeInBounce: function(t, b, c, d) {
          return c - JSTweener.easingFunctions.easeOutBounce(d-t, 0, c, d) + b;
      },    
      easeOutBounce: 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;
          }
      },    
      easeInOutBounce: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeInBounce(t*2, 0, c, d) * .5 + b;
          else return JSTweener.easingFunctions.easeOutBounce(t*2-d, 0, c, d) * .5 + c*.5 + b;
      },    
      easeOutInBounce: function(t, b, c, d) {
          if(t < d/2) return JSTweener.easingFunctions.easeOutBounce(t*2, b, c/2, d);
          return JSTweener.easingFunctions.easeInBounce((t*2)-d, b+c/2, c/2, d);
      }
    },
    finish:function(){
      //alert("test "+typeof(this.paralle));
      for(var i=0,j;j=this.paralle[i];i++) if (j.queue!=null) return false;
      return true;
    },
    addTask:function(opts){
      var task = new chill(this.tag,opts);
      task.parent = this;
      this.paralle.push(task);
      return this;
    },
    enqueue:function(opts){
      //this method remain problems.
      //please think about how to deal with queue.
      var sentinal = this.queue==null;
      this.queue = this.queue||[];
      if(opts.queue==null||opts.queue){
        this.queue.push(opts);//enqueue
      }else{
        //alert("imediatly");
        //this.paralle.push(new chill(this.tag,opts));
        this.addTask(opts);
      }
      //document.getElementById("queue").innerHTML = "queue length:"+this.queue.length+" age:"+this.age;
      if(sentinal){
        //alert("continue");
        this.go();
      }
    },
    dequeue:function(){
      var o=this.queue.shift();
      //document.getElementById("queue").innerHTML = "queue length:"+this.queue.length+" age:"+this.age+" final:"+this.finish();
      return o;
    },
    init:function (){
      var opts = this.dequeue();
      if(opts){
        var sentinal = false;
        var o = this.tag;
        var sets = this.sets;
        this.extend(sets,opts);
        sets.agility=sets.agility==0?50:sets.agility;
        //alert("opts.property="+o[opts.property]+(opts.form!=null));
        sets.from=!isNaN(opts.from)?opts.from:parseInt(/\d+/.exec(o[opts.property]));
        //alert("sets.from="+sets.from);
        sets.from=isNaN(sets.from)?0:sets.from;

        //alert("opts.to="+opts.to);
        var sets2 = {};
        this.extend(sets2,this.def).extend(sets2,opts);
        var vw = document.documentElement.clientWidth-parseInt(this.tag.width,10);
        var vh = document.documentElement.clientHeight-parseInt(this.tag.height,10);
        //try{
          var fromLeft=!isNaN(opts.from)?opts.from:parseInt(/\d+/.exec(o.left));
          var fromTop=!isNaN(opts.from)?opts.from:parseInt(/\d+/.exec(o.top));
        //}catch(ex){}
        switch (sets.to){
        case "n":
        case "s":
        case "e":
        case "w":
        case "ne":
        case "nw":
        case "se":
        case "sw":
        case "c":
          sets.property = "left";sets.postfix = "px";sets.from=fromLeft;
          sets2.property = "top";sets2.postfix = "px";sets2.from=fromTop;
          if(sets.to=="n"){sets.to = vw/2;sets2.to = 0;}
          if(sets.to=="s"){sets.to = vw/2;sets2.to = vh;}
          if(sets.to=="w"){sets.to = 0;sets2.to = vh/2}
          if(sets.to=="e"){sets.to = vw;sets2.to = vh/2}
          if(sets.to=="ne"){sets.to = vw;sets2.to = 0;}
          if(sets.to=="nw"){sets.to = 0;sets2.to = 0;}
          if(sets.to=="se"){sets.to = vw;sets2.to = vh;}
          if(sets.to=="sw"){sets.to = 0;sets2.to = vh;}
          if(sets.to=="c"){sets.to = vw/2;sets2.to = vh/2;}
          //sets2.generation = this.generation;//+this.queue.length;
          //this.paralle.push(sets2);
          sentinal = true;
          break;
        }

        //alert("opts.to="+opts.to+"|"+sets.to+"|"+sets2.to);

        switch (opts.time){
          case null: document.title = "time is null"; break;
          //case "normal": break;
          case "slow":
            sets.time=Math.abs(sets.from-sets.to)*2;
            sets2.time=Math.abs(sets2.from-sets2.to)*2;
            break;
          case "fast":
            sets.time=Math.abs(sets.from-sets.to)/2;
            sets2.time=Math.abs(sets2.from-sets2.to)/2;
            break;
          default :
            sets.time=(isNaN(opts.time)?Math.abs(sets.from-sets.to):opts.time);
            sets2.time=(isNaN(opts.time)?Math.abs(sets2.from-sets2.to):opts.time);
            break;
        }
        var time = sets.time;
        if(!isNaN(sets2.time)) time = time>sets2.time?time:sets2.time;
        sets.time = sets2.time = time;
        this.age += time;

        if(sentinal){
          //alert("put paralle");
          //this.paralle.push(new chill(this.tag,sets2));
          this.addTask(sets2);
        }
        //alert(opts.from+" sets.from="+sets.from+"|"+fromTop+"|"+fromLeft+" set.time="+sets.time);
        return true; //ok there valid queue
      }
      return false; //no queue are empty
    },
    next:function(opts){
      //this.init(o,opts);
      this.enqueue(opts);
      return this;
    },
    go:function(){
      //this.init(o,opts);
      delete this.going;//clear going for the task 
      this.going = null;
      if(this.init()){
        var context = this;
        var o = this.tag, sets = this.sets;
        var tick,clock;
        var tween = this.tween[sets.type];
        var sentinal = typeof(context.tag)=="function";

        var s = sets.from, e = sets.to, c=e-s, t=0, b=s,d=sets.time;
        //var d = (e-s)/sets.time;
        //if(s==e) return ;
        if(s==e) t = sets.time;
        (function proxy(){
          if(t>=sets.time){
            //document.title = sets.prefix+sets.to+sets.postfix;
            o[sets.property] = sets.prefix+sets.to+sets.postfix;
            if(sentinal) context.tag(sets.to);
            if(context.finish()){
              //alert("continue...");
              context.go();//try to dequeue
              //if parent exist and call it's going to continue
              try{context.parent.going();}catch(ex){}
            }else{
              context.going = context.go;//set going for paralle task
            }
            try{sets.callback.call(o);}catch(ex){}
          }else{
            v = (tween(t,b,c,d));
            //document.title = sets.prefix+(v)+sets.postfix;
            if(sentinal) context.tag(v);
            o[sets.property] = sets.prefix+parseInt(v)+sets.postfix;
            //document.title=("FINAL:"+context.finish()+'proxy '+(d)+"|"+c+"|"+/\d\d:\d\d:\d\d/.exec(new Date()));
            clock = setTimeout(proxy,sets.agility);
            t += sets.agility;
          }
        })();
      }else{
        //alert("set queue null to indicate task complete.");
        delete this.queue;
        this.queue = null;
      }
    },
    to:function(p,args){//n,e,w,s, ne, nw, se, sw, c
      opts = {};
      this.extend(opts,this.def).extend(opts,args);
      //alert("args.from="+opts.from);
      switch(p){
      case "n": 
      case "e": 
      case "w": 
      case "s": 
      case "ne": 
      case "nw": 
      case "se": 
      case "sw": 
      case "c":
        opts.to = p;
        this.enqueue(opts);
        break;
      default: throw new Error("unvalid destination of to.");
      }
      return this;
    }
  }

