// Copyright (c) 2013-2015 Marco Biasini
//
// 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.

define(
  [
    '../gl-matrix', 
    '../utils', 
    '../geom'
  ], 
  function(
    glMatrix, 
    utils, 
    geom) {
"use strict";

var vec3 = glMatrix.vec3;
var quat = glMatrix.quat;
var mat3 = glMatrix.mat3;

// base for all animations, e.g. position transitions, slerping etc.
function Animation(from, to, duration) {
  this._from = from;
  this._to = to;
  this._duration = duration;
  this._left = duration;
  this._start = Date.now();
  this._looping = false;
  this._finished = false;
}


Animation.prototype = {
  setLooping : function(looping) {
    this._looping = looping;
  },
  step : function(cam) {
    var now = Date.now();
    var elapsed = now - this._start;
    var t;
    if (this._duration === 0) {
      t = 1.0;
    } else {
      if (this._looping) {
        var times = Math.floor(elapsed/this._duration);
        t = (elapsed - times * this._duration)/this._duration;
      } else {
        elapsed = Math.min(this._duration, elapsed);
        t = elapsed/this._duration;
        this._finished = t === 1.0;
      }
    }
    this.apply(cam, t);
    return this._finished;
  },

  apply : function(cam, t) {
    var smoothInterval = (1 - Math.cos(t * Math.PI ) ) / 2;
    this._current = this._from * (1-smoothInterval) + this._to * smoothInterval;
    cam.setZoom(this._current);
  },

  finished : function() {
    return this._finished;
  }
};



function Move(from, to, duration) {
  Animation.call(this, vec3.clone(from), vec3.clone(to), duration);
  this._current = vec3.clone(from);
}

utils.derive(Move, Animation, {
  apply : function(cam, t) {
    var smoothInterval = (1 - Math.cos(t * Math.PI ) ) / 2;
    vec3.lerp(this._current, this._from, this._to, smoothInterval);
    cam.setCenter(this._current);
  }
});

function Rotate(initialRotation, destinationRotation, duration) {
  var initial = mat3.create();
  var to = mat3.create();
  mat3.fromMat4(initial, initialRotation);
  mat3.fromMat4(to, destinationRotation);
  var initialQuat = quat.create();
  var toQuat = quat.create();
  quat.fromMat3(initialQuat, initial);
  quat.fromMat3(toQuat, to);
  this._current = mat3.create();
  Animation.call(this, initialQuat, toQuat, duration);
}

utils.derive(Rotate, Animation, {
  apply : (function() {
    var quatRot = quat.create();
    
    return function(cam, t) {
      quat.slerp(quatRot, this._from, this._to, t);
      mat3.fromQuat(this._current, quatRot);
      cam.setRotation(this._current);
    };
  })()
});

function RockAndRoll(axis, duration) {
  Animation.call(this, null, null, duration);
  this._axis = vec3.clone(axis);
  this.setLooping(true);
  this._previousAngle = 0.0;
}

utils.derive(RockAndRoll, Animation, {
  apply : (function() {
    var axisRot = mat3.create();
    var rotation = mat3.create();
    return function(cam, t) {
      mat3.fromMat4(rotation, cam.rotation());
      var angle = 0.2 * Math.sin(2 * t * Math.PI);
      var deltaAngle = angle - this._previousAngle;
      this._previousAngle = angle;
      geom.axisRotation(axisRot, this._axis, deltaAngle);
      mat3.mul(rotation, axisRot, rotation);
      cam.setRotation(rotation);
    };
  })()
});

function Spin(axis, speed) {
  var duration = 1000 * (2 * Math.PI / speed);
  Animation.call(this, null, null, duration);
  this._axis = vec3.clone(axis);
  this.setLooping(true);
  this._speed = speed;
  this._previousT = 0.0;
}

utils.derive(Spin, Animation, {
  apply : (function() {
    var axisRot = mat3.create();
    var rotation = mat3.create();
    return function(cam, t) {
      mat3.fromMat4(rotation, cam.rotation());
      var angle = Math.PI * 2 * (t - this._previousT);
      this._previousT = t;
      geom.axisRotation(axisRot, this._axis, angle);
      mat3.mul(rotation, axisRot, rotation);
      cam.setRotation(rotation);
    };
  })(),
  setSpeed : function(speed) {
    this._speed = speed;
  },
  setAxis : function(axis) {
    this._axis = axis;
  }
});


function AnimationControl() {
  this._animations = [];
}

AnimationControl.prototype = {
  // apply all currently active animations to the camera 
  // returns true if there are pending animations.
  run : function(camera) {
    var time = Date.now();
    this._animations = this._animations.filter(function(anim) {
      return !anim.step(camera, time);
    });
    return this._animations.length > 0;
  },
  add : function(animation) {
    this._animations.push(animation);
  },
  remove : function(animation) {
    this._animations = this._animations.filter(function(a) {
      return a !== animation;
    });
  }
};


function move(from, to, duration) {
  return new Move(from, to, duration);
}

function rotate(from, to, duration) {
  return new Rotate(from, to, duration);
}

function zoom(from, to, duration) {
  return new Animation(from, to, duration);
}


function spin(axis, speed) {
  return new Spin(axis, speed);
}

function rockAndRoll() {
  return new RockAndRoll([0, 1, 0], 2000);
}

return {
  AnimationControl : AnimationControl,
  move : move,
  rotate : rotate,
  zoom : zoom,
  rockAndRoll : rockAndRoll,
  spin : spin
};

});

@@ -4963,24 +4963,75 @@
 ed = speed;%0A
+    this._duration = 1000 * (2 * Math.PI / speed);%0A
   %7D,%0A  setAx

fix bug in spinning implementation