 /**
   *  (c) 2005-2007 Richard Cowin (http://openrico.org)
   *
   *  Rico is licensed under the Apache License, Version 2.0 (the "License"); you may not use this
   *  file except in compliance with the License. You may obtain a copy of the License at
   *   http://www.apache.org/licenses/LICENSE-2.0
   **/

Rico.animate = function(effect){
  new Rico.Effect.Animator().play(effect, arguments[1]);
}

Rico.Effect = {}
Rico.Effect.easeIn = function(step){
  return Math.sqrt(step)
}
Rico.Effect.easeOut = function(step){
  return step*step
}
Rico.Stepping = {}
Rico.Stepping.easeIn = Rico.Effect.easeIn;
Rico.Stepping.easeOut = Rico.Effect.easeOut;

Rico.Effect.Animator = Class.create();
Rico.Effect.Animator.prototype = {
  initialize : function(effect) {
    this.animateMethod = this.animate.bind(this);
    this.options = arguments[1] || {};
    this.stepsLeft = 0;
    if (!effect) return;
    this.reset(effect, arguments[1]);
  },
  reset: function(effect){
    this.effect = effect;
    if (arguments[1]) this.setOptions(arguments[1]);
    this.stepsLeft = this.options.steps;
    this.duration = this.options.duration;
  },
  setOptions: function(options){
    this.options = Object.extend({
      steps: 200,
      duration: 500,
      rate: function(steps){ return steps;}
    }, options|| {});
  },
  play: function(effect) {
    this.setOptions(arguments[1])
    if (effect)
      if (effect.step)
        this.reset(effect, arguments[1]);
      else{
        $H(effect).keys().each((function(e){
          var effectClass = {fadeOut:Rico.Effect.FadeOut}[e];
          this.reset(new effectClass(effect[e]));
        }).bind(this))
      }
    this.animate();
  },
  stop: function() {
    if (this.timer) clearTimeout(this.timer);
    this.stepsLeft = 0;
    if (this.effect && this.effect.finish) this.effect.finish();
    if (this.options.onFinish) this.options.onFinish();
  },
  pause: function() {
    this.interupt = true;
  },
  resume: function() {
    this.interupt = false;
    if (this.stepsLeft >0)
      this.animate();
  },
  animate: function() {
    if (this.interupt)
      return;
    if (this.stepsLeft <=0) {
      this.stop();
      return;
    }
    if (this.timer) clearTimeout(this.timer);
    this.effect.step(this.options.rate(this.stepsLeft));
    this.startNextStep();
  },
  startNextStep: function() {
    var stepDuration = Math.round(this.duration/this.stepsLeft) ;
    this.duration -= stepDuration;
    this.stepsLeft--;
    this.timer = setTimeout(this.animateMethod, stepDuration);
  },
  isPlaying: function(){
    return this.stepsLeft != 0 && !this.interupt;
  }
}

Rico.Effect.Group = Class.create();
Rico.Effect.Group.prototype = {
  initialize: function(effects){
    this.effects = effects;
  },
  step: function(stepsToGo){
    this.effects.each(function(e){e.step(stepsToGo)});
  },
  finish: function(){
    this.effects.each(function(e){if (e.finish) e.finish()});
  }
}

Rico.Effect.SizeAndPosition = Class.create();
Rico.Effect.SizeAndPosition.prototype = {
  initialize: function(element, x, y, w, h) {
    Object.extend(this, new Rico.Effect.SizeAndPositionFade(element, x, y, w, h));
  }
}

Rico.Effect.SizeAndPositionFade = Class.create();
Rico.Effect.SizeAndPositionFade.prototype = {
  initialize: function(element, x, y, w, h, value) {
    this.element = $(element);
    this.x = typeof(x)=='number' ? x : this.element.offsetLeft;
    this.y = typeof(y)=='number' ? y : this.element.offsetTop;
    if (!Prototype.Browser.IE || (document.compatMode && document.compatMode.indexOf("CSS")!=-1)) {
      this.pw = RicoUtil.nan2zero(Element.getStyle(this.element,'padding-left'))+RicoUtil.nan2zero(Element.getStyle(this.element,'padding-right'));
      this.pw += RicoUtil.nan2zero(Element.getStyle(this.element,'border-left-width'))+RicoUtil.nan2zero(Element.getStyle(this.element,'border-right-width'));
      this.ph = RicoUtil.nan2zero(Element.getStyle(this.element,'padding-top'))+RicoUtil.nan2zero(Element.getStyle(this.element,'padding-bottom'));
      this.ph += RicoUtil.nan2zero(Element.getStyle(this.element,'border-top-width'))+RicoUtil.nan2zero(Element.getStyle(this.element,'border-bottom-width'));
    } else {
      this.pw=0;
      this.ph=0;
    }
    this.w = typeof(w)=='number' ? w : this.element.offsetWidth;
    this.h = typeof(h)=='number' ? h : this.element.offsetHeight;
    this.opacity = Element.getStyle(this.element, 'opacity') || 1.0;
    this.target = arguments.length > 5 ? Math.min(value, 1.0) : this.opacity;
  },
  step: function(stepsToGo) {
    var left = this.element.offsetLeft + ((this.x - this.element.offsetLeft)/stepsToGo);
    var top = this.element.offsetTop + ((this.y - this.element.offsetTop)/stepsToGo);
    var width = this.element.offsetWidth + ((this.w - this.element.offsetWidth)/stepsToGo) - this.pw;
    var height = this.element.offsetHeight + ((this.h - this.element.offsetHeight)/stepsToGo) - this.ph;
    var style = this.element.style;
    var curOpacity = Element.getStyle(this.element, 'opacity');
    var newOpacity = curOpacity + (this.target - curOpacity) / stepsToGo;
    Rico.Effect.setOpacity(this.element, Math.min(Math.max(0,newOpacity),1.0));
    style.left = left + "px";
    style.top = top + "px";
    style.width = width + "px";
    style.height = height + "px";
  }
}

Rico.AccordionEffect = Class.create();
Rico.AccordionEffect.prototype = {
  initialize: function(toClose, toOpen, height) {
    this.toClose   = toClose;
    this.toOpen    = toOpen;
    toOpen.style.height = "0px";
    Element.show(toOpen);
    Element.makeClipping(toOpen);
    Element.makeClipping(toClose);
    Rico.Controls.disableNativeControls(toClose);
    this.endHeight = height;
  },
  step: function(framesLeft) {
    var cHeight = Math.max(1,this.toClose.offsetHeight - parseInt((parseInt(this.toClose.offsetHeight))/framesLeft));
    var closeHeight = cHeight + "px";
    var openHeight = (this.endHeight - cHeight) + "px"
    this.toClose.style.height = closeHeight;
    this.toOpen.style.height = openHeight;
  },
  finish: function(){
    Element.hide(this.toClose)
    this.toOpen.style.height = this.endHeight + "px";
    this.toClose.style.height = "0px";
    Element.undoClipping(this.toOpen);
    Element.undoClipping(this.toClose);
    Rico.Controls.enableNativeControls(this.toOpen);
  }
};

Rico.Effect.SizeFromBottom = Class.create()
Rico.Effect.SizeFromBottom.prototype = {
  initialize: function(element, y, h) {
    this.element = $(element);
    this.y = typeof(y)=='number' ? y : this.element.offsetTop;
    this.h = typeof(h)=='number' ? h : this.element.offsetHeight;
    this.options  = arguments[3] || {};
  },
  step: function(framesToGo) {
    var top = this.element.offsetTop + ((this.y - this.element.offsetTop)/framesToGo) + "px"
    var height = this.element.offsetHeight + ((this.h - this.element.offsetHeight)/framesToGo) + "px"
    var style = this.element.style;
    style.height = height;
    style.top = top;
  }
}

Rico.Effect.Position = Class.create();
Rico.Effect.Position.prototype = {
  initialize: function(element, x, y) {
    this.element = $(element);
    this.x = typeof(x)=='number' ? x : this.element.offsetLeft;
    this.destTop = typeof(y)=='number' ? y : this.element.offsetTop;
  },
  step: function(stepsToGo) {
    var left = this.element.offsetLeft + ((this.x - this.element.offsetLeft)/stepsToGo) + "px"
    var top = this.element.offsetTop + ((this.destTop - this.element.offsetTop)/stepsToGo) + "px"
    var style = this.element.style;
    style.left = left;
    style.top = top;
  }
}

Rico.Effect.FadeTo = Class.create()
Rico.Effect.FadeTo.prototype = {
  initialize: function(element, value){
    this.element = element;
    this.opacity = Element.getStyle(this.element, 'opacity') || 1.0;
    this.target = Math.min(value, 1.0);
  },
  step: function(framesLeft) {
    var curOpacity = Element.getStyle(this.element, 'opacity');
    var newOpacity = curOpacity + (this.target - curOpacity)/framesLeft
    Rico.Effect.setOpacity(this.element, Math.min(Math.max(0,newOpacity),1.0));
  }
}

Rico.Effect.FadeOut = Class.create()
Rico.Effect.FadeOut.prototype = {
  initialize: function(element){
    this.effect = new Rico.Effect.FadeTo(element, 0.0)
  },
  step: function(framesLeft) {
    this.effect.step(framesLeft);
  }
}

Rico.Effect.FadeIn = Class.create()
Rico.Effect.FadeIn.prototype = {
  initialize: function(element){
    var options = arguments[1] || {}
    var startValue = options.startValue || 0
    Rico.Effect.setOpacity(element, startValue);
    this.effect = new Rico.Effect.FadeTo(element, 1.0)
  },
  step: function(framesLeft) {
    this.effect.step(framesLeft);
  }
}

Rico.Effect.setOpacity= function(element, value) {
  if (element.setOpacity) {
     element.setOpacity(value);  // use prototype function
  } else {
     element.style.filter = "alpha(opacity="+Math.round(value*100)+")";
     element.style.opacity = value;
  }
}

Rico.Effect.SizeFromTop = Class.create()
Rico.Effect.SizeFromTop.prototype = {
  initialize: function(element, scrollElement, y, h) {
     this.element = $(element);
     this.h = typeof(h)=='number' ? h : this.element.offsetHeight;
  //   element.style.top = y;
     this.scrollElement = scrollElement;
     this.options  = arguments[4] || {};
     this.baseHeight = this.options.baseHeight ||  Math.max(this.h, this.element.offsetHeight)
  },
  step: function(framesToGo) {
    var rawHeight = this.element.offsetHeight + ((this.h - this.element.offsetHeight)/framesToGo);
    var height = rawHeight + "px"
    var scroll = (rawHeight - this.baseHeight) + "px";
    this.scrollElement.style.top = scroll;
    this.element.style.height = height;
  }
}


Rico.Effect.Height = Class.create()
Rico.Effect.Height.prototype = {
  initialize: function(element, endHeight) {
    this.element = element
    this.endHeight = endHeight
  },
  step: function(stepsLeft) {
    if (this.element.constructor != Array){
      var height = this.element.offsetHeight + ((this.endHeight - this.element.offsetHeight)/stepsLeft) + "px"
      this.element.style.height = height;
    } else {
      var height = this.element[0].offsetHeight + ((this.endHeight - this.element[0].offsetHeight)/stepsLeft) + "px"
      this.element.each(function(e){e.style.height = height})
    }
  }
}

Rico.Effect.SizeWidth = Class.create();
Rico.Effect.SizeWidth.prototype = {
    initialize: function(element, endWidth) {
      this.element = element
      this.endWidth = endWidth
    },
    step: function(stepsLeft) {
       delta = Math.abs(this.endWidth - parseInt(this.element.offsetWidth))/(stepsLeft);
       this.element.style.width = (this.element.offsetWidth - delta) + "px";
    }
}

//these are to support non Safari browsers and keep controls from bleeding through on absolute positioned element.
Rico.Controls = {
  editors: [],
  scrollSelectors: [],

  disableNativeControls: function(element) {
    Rico.Controls.defaultDisabler.disableNative(element);
  },
  enableNativeControls: function(element){
    Rico.Controls.defaultDisabler.enableNative(element);
  },
  prepareForSizing: function(element){
    Element.makeClipping(element)
    Rico.Controls.disableNativeControls(element)
  },
  resetSizing: function(element){
    Element.undoClipping(element)
    Rico.Controls.enableNativeControls(element)
  },
  registerScrollSelectors: function(selectorSet) {
    selectorSet.each(function(s){Rico.Controls.scrollSelectors.push(Rico.selector(s))});
  }
}

Rico.Controls.Disabler = Class.create();
Rico.Controls.Disabler.prototype = {
  initialize: function(){
    this.options = Object.extend({
      excludeSet: [],
      hidables: Rico.Controls.editors
    }, arguments[0] || {});
  },
  disableNative: function(element) {
    if (!(/Konqueror|Safari|KHTML/.test(navigator.userAgent))){
      if (!navigator.appVersion.match(/\bMSIE\b/))
        this.blockControls(element).each(function(e){Element.makeClipping(e)});
      else
        this.hidableControls(element).each(function(e){e.disable()});
    }
  },
  enableNative: function(element){
    if (!(/Konqueror|Safari|KHTML/.test(navigator.userAgent))){
      if (!navigator.appVersion.match(/\bMSIE\b/))
        this.blockControls(element).each(function(e){Element.undoClipping(e)});
      else
        this.hidableControls(element).each(function(e){e.enable()});
    }
  },
  blockControls: function(element){
    try{
    var includes = [];
    if (this.options.includeSet)
      includes = this.options.includeSet;
    else{
      var selectors = this.options.includeSelectors || Rico.Controls.scrollSelectors;
      includes = selectors.map(function(s){return s.findAll(element)}).flatten();
    }
    return includes.select(function(e){return (Element.getStyle(e, 'display') != 'none') && !this.options.excludeSet.include(e)}.bind(this));
  }catch(e) { return []}
  },
  hidableControls: function(element){
    if (element)
      return this.options.hidables.select(function(e){return Element.childOf(e, element)});
    else
      return this.options.hidables;
  }
}

Rico.Controls.defaultDisabler = new Rico.Controls.Disabler();
Rico.Controls.blankDisabler = new Rico.Controls.Disabler({includeSet:[],hidables:[]});

Rico.Controls.HidableInput = Class.create();
Rico.Controls.HidableInput.prototype = {
  initialize: function(field, view){
    this.field = field;
    this.view = view;
    this.enable();
    Rico.Controls.editors.push(this);
  },
  enable: function(){
    Element.hide(this.view);
    Element.show(this.field);
  },
  disable: function(){
    this.view.value = $F(this.field);
    if (this.field.offsetWidth > 1) {
      this.view.style.width =  parseInt(this.field.offsetWidth)  + "px";
      Element.hide(this.field);
      Element.show(this.view);
    }
  }
}



Element.forceRefresh = function(item) {
  try {
    var n = document.createTextNode(' ')
    item.appendChild(n); item.removeChild(n);
  } catch(e) { }
};

Rico.includeLoaded('ricoEffects.js');
