(function(ionic) {

  var bezierCoord = function (x,y) {
    if(!x) x=0;
    if(!y) y=0;
    return {x: x, y: y};
  };

  function B1(t) { return t*t*t; }
  function B2(t) { return 3*t*t*(1-t); }
  function B3(t) { return 3*t*(1-t)*(1-t); }
  function B4(t) { return (1-t)*(1-t)*(1-t); }

  ionic.Animator = {
    // Quadratic bezier solver
    getQuadraticBezier: function(percent,C1,C2,C3,C4) {
      var pos = new bezierCoord();
      pos.x = C1.x*B1(percent) + C2.x*B2(percent) + C3.x*B3(percent) + C4.x*B4(percent);
      pos.y = C1.y*B1(percent) + C2.y*B2(percent) + C3.y*B3(percent) + C4.y*B4(percent);
      return pos;
    },

    // Cubic bezier solver from https://github.com/arian/cubic-bezier (MIT)
    getCubicBezier: function(x1, y1, x2, y2, duration) {
      // Precision
      epsilon = (1000 / 60 / duration) / 4;

      var curveX = function(t){
        var v = 1 - t;
        return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
      };

      var curveY = function(t){
        var v = 1 - t;
        return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
      };

      var derivativeCurveX = function(t){
        var v = 1 - t;
        return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;
      };

      return function(t) {

        var x = t, t0, t1, t2, x2, d2, i;

        // First try a few iterations of Newton's method -- normally very fast.
        for (t2 = x, i = 0; i < 8; i++){
          x2 = curveX(t2) - x;
          if (Math.abs(x2) < epsilon) return curveY(t2);
          d2 = derivativeCurveX(t2);
          if (Math.abs(d2) < 1e-6) break;
          t2 = t2 - x2 / d2;
        }

        t0 = 0, t1 = 1, t2 = x;

        if (t2 < t0) return curveY(t0);
        if (t2 > t1) return curveY(t1);

        // Fallback to the bisection method for reliability.
        while (t0 < t1){
          x2 = curveX(t2);
          if (Math.abs(x2 - x) < epsilon) return curveY(t2);
          if (x > x2) t0 = t2;
          else t1 = t2;
          t2 = (t1 - t0) * 0.5 + t0;
        }

        // Failure
        return curveY(t2);
      };
    },

    animate: function(element, className, fn) {
      return {
        leave: function() {
          var endFunc = function() {

            element.classList.remove('leave');
            element.classList.remove('leave-active');

            element.removeEventListener('webkitTransitionEnd', endFunc);
            element.removeEventListener('transitionEnd', endFunc);
          };
          element.addEventListener('webkitTransitionEnd', endFunc);
          element.addEventListener('transitionEnd', endFunc);

          element.classList.add('leave');
          element.classList.add('leave-active');
          return this;
        },
        enter: function() {
          var endFunc = function() {

            element.classList.remove('enter');
            element.classList.remove('enter-active');

            element.removeEventListener('webkitTransitionEnd', endFunc);
            element.removeEventListener('transitionEnd', endFunc);
          };
          element.addEventListener('webkitTransitionEnd', endFunc);
          element.addEventListener('transitionEnd', endFunc);

          element.classList.add('enter');
          element.classList.add('enter-active');

          return this;
        }
      };
    }
  };
})(ionic);
