//
// Copyright (c) 2008 Paul Duncan (paul@pablotron.org)
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

/**
 * Easer: namespace for Easier class and methods.
 * @namespace
 */
Easing = (function () {
  // return namespace
  var E = {};

  // import math functions to speed up ease callbacks
  var abs     = Math.abs,
      asin    = Math.asin,
      cos     = Math.cos, 
      pow     = Math.pow,
      sin     = Math.sin, 
      sqrt    = Math.sqrt,
      PI      = Math.PI,
      HALF_PI = Math.PI / 2;

  E = {
    /**
     * Version of Easing library.
     * @static
     */
    VERSION: '0.1.0',

    /**
     * Default options for Easer.
     * @static
     */
    DEFAULTS: {
      type: 'linear',
      side: 'none'
    },

    /**
     * Hash of valid types and sides.
     * @static
     */
    VALID: {
      type: { 
        linear:     true, 
        bounce:     true,
        circular:   true,
        cubic:      true,
        elastic:    true,
        exp:        true,
        quadratic:  true,
        quartic:    true,
        quintic:    true,
        sine:       true 
      },

      side: { 
        none: true, 
        'in': true, 
        out:  true, 
        both: true 
      }
    } 
  };

  /**
   * Easing.Easer: Easing class.
   * @class Easing.Easer.
   * @constructor
   * 
   * @param {Hash}  o   Hash of options.  Valid keys are "type" and "side".
   * 
   * Example:
   * 
   *   // create a new quadratic easer
   *   e = new Easing.Easer({
   *     type: 'quadratic',
   *     side: 'both'
   *   });
   * 
   */
  E.Easer = function(o) {
    var key;

    // set defaults
    for (key in E.DEFAULTS)
      this[key] = E.DEFAULTS[key];

    this.reset(o);
  };

  /**
   * Reset an Easer with new values.
   * 
   * @param {Hash}  o   Hash of options.  Valid keys are "type" and "side".
   * 
   * Example:
   * 
   *   // reset easer to quintic easing
   *   e = e.reset({
   *     type: 'quintic',
   *     side: 'end'
   *   });
   * 
   */
  E.Easer.prototype.reset = function(o) {
    var key, name, type, side, err;
    for (key in o)
      this[key] = o[key];

    // get/check type
    type = (this.side != 'none') ? this.type : 'linear';
    if (!E.VALID.type[type])
      throw new Error("unknown type: " + this.type);

    // get/check side
    side = (type != 'linear') ? this.side : 'none';
    if (!E.VALID.side[side])
      throw new Error("unknown side: " + this.side);

    // build callback name
    name = ['ease', side].join('_');
    this.fn = E[type] && E[type][name];

    // make sure callback exists
    if (!this.fn) {
      err = "type = " + this.type + ", side = " + this.side;
      throw new Error("unknown ease: " + err);
    }
  };

  /**
   * Get the ease for a particular time offset.
   * 
   * @param {Number}    time_now     Current time offset (in the range of 0-time_dur).
   * @param {Number}    begin_val    Beginning value.
   * @param {Number}    change_val   End offset value.
   * @param {Number}    time_dur     Duration of time.
   * 
   * @returns Eased value.
   * @type Number
   * 
   * Example:
   * 
   *   // calculate ease at 50 time units for transition from 10 to 300
   *   var x = e.ease(50, 10, 290, 100);
   * 
   */
  E.Easer.prototype.ease = function(time_now, begin_val, change_val, time_dur) {
    return this.fn.apply(this, arguments);
  };

  /**
   * linear easing
   * @namespace
   */
  E.linear = {};

  E.linear.ease_none = function(t, b, c, d) {
    return c * t / d + b;
  };

  /**
   * back easing
   * @namespace
   */
  E.back = {};

  var BACK_DEFAULT_S = 1.70158;

  E.back.ease_in = function(t, b, c, d, s) {
    if (s == undefined) s = BACK_DEFAULT_S;
    return c*(t/=d)*t*((s+1)*t - s) + b;
  };

  E.back.ease_out = function(t, b, c, d, s) {
		if (s == undefined) s = BACK_DEFAULT_S;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	};

  E.back.ease_both = function(t, b, c, d, s) {
		if (s == undefined) s = BACK_DEFAULT_S; 
		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;
	};

  /**
   * bounce easing
   * @namespace
   */
  E.bounce = {};

  var bounce_ratios = [
    1 / 2.75,
    2 / 2.75,
    2.5 / 2.75
  ];

  var bounce_factors = [
    null,
    1.5 / 2.75,
    2.25 / 2.75,
    2.625 / 2.75
  ];

  E.bounce.ease_out = function(t, b, c, d) {
    if ((t/=d) < (bounce_ratios[0])) {
      return c*(7.5625*t*t) + b;
    } else if (t < (bounce_ratios[1])) {
      return c*(7.5625*(t-=(bounce_factors[1]))*t + .75) + b;
    } else if (t < (bounce_ratios[2])) {
      return c*(7.5625*(t-=(bounce_factors[2]))*t + .9375) + b;
    } else {
      return c*(7.5625*(t-=(bounce_factors[3]))*t + .984375) + b;
    }
  };

  E.bounce.ease_in = function(t, b, c, d) {
    return c - E.bounce.ease_out(d-t, 0, c, d) + b;
  };

  E.bounce.ease_both = function(t, b, c, d) {
    if (t < d/2) return E.bounce.ease_in(t*2, 0, c, d) * .5 + b;
    else return E.bounce.ease_out(t*2-d, 0, c, d) * .5 + c*.5 + b;
  };

  /**
   * circular easing
   * @namespace
   */
  E.circular = {};

  E.circular.ease_in = function(t, b, c, d) {
    return -c * (sqrt(1 - (t/=d)*t) - 1) + b;
  };

  E.circular.ease_out = function(t, b, c, d) {
    return c * sqrt(1 - (t=t/d-1)*t) + b;
  };

  E.circular.ease_both = function(t, b, c, d) {
    if ((t/=d/2) < 1) return -c/2 * (sqrt(1 - t*t) - 1) + b;
    return c/2 * (sqrt(1 - (t-=2)*t) + 1) + b;
  };

  /**
   * cubic easing
   * @namespace
   */
  E.cubic = {};

  E.cubic.ease_in = function(t, b, c, d) {
    return c*(t/=d)*t*t + b;
  };

  E.cubic.ease_out = function(t, b, c, d) {
    return c*((t=t/d-1)*t*t + 1) + b;
  };

  E.cubic.ease_both = 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;
  };

  /**
   * elastic easing
   * @namespace
   */
  E.elastic = {};

  E.elastic.ease_in = function(t, b, c, d, a, p) {
    if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
    if (!a || a < abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*PI) * asin(c/a);
    return -(a*pow(2,10*(t-=1)) * sin( (t*d-s)*(2*PI)/p )) + b;
  };

  E.elastic.ease_out = function(t, b, c, d, a, p) {
    if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
    if (!a || a < abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*PI) * asin(c/a);
    return (a*pow(2,-10*t) * sin( (t*d-s)*(2*PI)/p ) + c + b);
  };

  E.elastic.ease_both = function(t, b, c, d, a, p) {
    if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
    if (!a || a < abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*PI) * asin (c/a);
    if (t < 1) return -.5*(a*pow(2,10*(t-=1)) * sin( (t*d-s)*(2*PI)/p )) + b;
    return a*pow(2,-10*(t-=1)) * sin( (t*d-s)*(2*PI)/p )*.5 + c + b;
  };

  /**
   * exponential easing
   * @namespace
   */
  E.exp = {};

  E.exp.ease_in = function(t, b, c, d) {
    return (t==0) ? b : c * pow(2, 10 * (t/d - 1)) + b;
  };

  E.exp.ease_out = function(t, b, c, d) {
    return (t==d) ? b+c : c * (-pow(2, -10 * t/d) + 1) + b;
  };

  E.exp.ease_both = function(t, b, c, d) {
    if (t==0) return b;
    if (t==d) return b+c;
    if ((t/=d/2) < 1) return c/2 * pow(2, 10 * (t - 1)) + b;
    return c/2 * (-pow(2, -10 * --t) + 2) + b;
  };

  /**
   * quadratic easing
   */
  E.quadratic = {};

  E.quadratic.ease_in = function(t, b, c, d) {
    return c*(t/=d)*t + b;
  };

  E.quadratic.ease_out = function(t, b, c, d) {
    return -c *(t/=d)*(t-2) + b;
  };

  E.quadratic.ease_both = function(t, b, c, d) {
    if ((t/=d/2) < 1) return c/2*t*t + b;
    return -c/2 * ((--t)*(t-2) - 1) + b;
  };

  /**
   * quartic easing
   * @namespace
   */
  E.quartic = {};

  E.quartic.ease_in = function(t, b, c, d) {
    return c*(t/=d)*t*t*t + b;
  };

  E.quartic.ease_out = function(t, b, c, d) {
    return -c * ((t=t/d-1)*t*t*t - 1) + b;
  };

  E.quartic.ease_both = 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;
  };

  /**
   * quintic easing
   * @namespace
   */
  E.quintic = {};

  E.quintic.ease_in = function(t, b, c, d) {
    return c*(t/=d)*t*t*t*t + b;
  };

  E.quintic.ease_out = function(t, b, c, d) {
    return c*((t=t/d-1)*t*t*t*t + 1) + b;
  };

  E.quintic.ease_both = 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;
  };

  /**
   * sinusoidal easing
   * @namespace
   */
  E.sine = {};

  E.sine.ease_in = function(t, b, c, d) {
    return -c * cos(t/d * (HALF_PI)) + c + b;
  };

  E.sine.ease_out = function(t, b, c, d) {
    return c * sin(t/d * (HALF_PI)) + b;
  };

  E.sine.ease_both = function(t, b, c, d) {
    return -c/2 * (cos(PI*t/d) - 1) + b;
  };

  // return scope
  return E;
})();
