var Timing = {
	easeIn: function easeIn(pos) {
		return Math.pow(pos, 3);
	},
	easeOut: function easeOut(pos) {
		return Math.pow(pos - 1, 3) + 1;
	},
	easeInOut: function easeInOut(pos) {
		if ((pos /= 0.5) < 1) {
			return 0.5 * Math.pow(pos, 3);
		} else {
			return 0.5 * (Math.pow(pos - 2, 3) + 2);
		}
	},
	linear: function linear(pos) {
		return pos;
	}
};

function Animation(opts) {
	this.isStop = false;
	opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration;
	opts.timing = opts.timing || 'linear';
	var delay = 17;

	function createAnimationFrame() {
		if (typeof setTimeout !== 'undefined') {
			return function(step, delay) {
				setTimeout(function() {
					var timeStamp = +new Date();
					step(timeStamp);
				}, delay);
			};
		} else if (typeof requestAnimationFrame !== 'undefined') {
			return requestAnimationFrame;
		} else {
			return function(step) {
				step(null);
			};
		}
	};
	var animationFrame = createAnimationFrame();
	var startTimeStamp = null;
	var _step = function step(timestamp) {
		if (timestamp === null || this.isStop === true) {
			opts.onProcess && opts.onProcess(1);
			opts.onAnimationFinish && opts.onAnimationFinish();
			return;
		}
		if (startTimeStamp === null) {
			startTimeStamp = timestamp;
		}
		if (timestamp - startTimeStamp < opts.duration) {
			var process = (timestamp - startTimeStamp) / opts.duration;
			var timingFunction = Timing[opts.timing];
			process = timingFunction(process);

			opts.onProcess && opts.onProcess(process);
			animationFrame(_step, delay);
		} else {
			opts.onProcess && opts.onProcess(1);
			opts.onAnimationFinish && opts.onAnimationFinish();
		}
	};
	_step = _step.bind(this);
	animationFrame(_step, delay);
}

// stop animation immediately
// and tigger onAnimationFinish
Animation.prototype.stop = function() {
	this.isStop = true;
};

if (typeof module === "object" && typeof module.exports === "object") {
	module.exports = Animation;
}
