/*!
 * ScrollMagic v2.0.5 (2015-04-29)
 * The javascript library for magical scroll interactions.
 * (c) 2015 Jan Paepke (@janpaepke)
 * Project Website: http://scrollmagic.io
 * 
 * @version 2.0.5
 * @license Dual licensed under MIT license and GPL.
 * @author Jan Paepke - e-mail@janpaepke.de
 *
 * @file ScrollMagic Velocity Animation Plugin.
 *
 * requires: velocity ~1.2
 * Powered by VelocityJS: http://VelocityJS.org
 * Velocity is published under MIT license.
 */
/**
 * This plugin is meant to be used in conjunction with the Velocity animation framework.  
 * It offers an easy API to __trigger__ Velocity animations.
 *
 * With the current version of Velocity scrollbound animations (scenes with duration) are not supported.  
 * This feature will be added as soon as Velocity provides the appropriate API.
 * 
 * To have access to this extension, please include `plugins/animation.velocity.js`.
 * @requires {@link http://julian.com/research/velocity/|Velocity ~1.2.0}
 * @mixin animation.Velocity
 */
(function (root, factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(['ScrollMagic', 'velocity'], factory);
	} else if (typeof exports === 'object') {
		// CommonJS
		factory(require('scrollmagic'), require('velocity'));
	} else {
		// Browser globals
		factory(root.ScrollMagic || (root.jQuery && root.jQuery.ScrollMagic), root.Velocity || (root.jQuery && root.jQuery.Velocity));
	}
}(this, function (ScrollMagic, velocity) {
	"use strict";
	var NAMESPACE = "animation.velocity";

	var
	console = window.console || {},
		err = Function.prototype.bind.call(console.error || console.log ||
		function () {}, console);
	if (!ScrollMagic) {
		err("(" + NAMESPACE + ") -> ERROR: The ScrollMagic main module could not be found. Please make sure it's loaded before this plugin or use an asynchronous loader like requirejs.");
	}
	if (!velocity) {
		err("(" + NAMESPACE + ") -> ERROR: Velocity could not be found. Please make sure it's loaded before ScrollMagic or use an asynchronous loader like requirejs.");
	}

	var autoindex = 0;

	ScrollMagic.Scene.extend(function () {
		var
		Scene = this,
			_util = ScrollMagic._util,
			_currentProgress = 0,
			_elems, _properties, _options, _dataID; // used to identify element data related to this scene, will be defined everytime a new velocity animation is added
		var log = function () {
			if (Scene._log) { // not available, when main source minified
				Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ")", "->");
				Scene._log.apply(this, arguments);
			}
		};

		// set listeners
		Scene.on("progress.plugin_velocity", function () {
			updateAnimationProgress();
		});
		Scene.on("destroy.plugin_velocity", function (e) {
			Scene.off("*.plugin_velocity");
			Scene.removeVelocity(e.reset);
		});

		var animate = function (elem, properties, options) {
			if (_util.type.Array(elem)) {
				elem.forEach(function (elem) {
					animate(elem, properties, options);
				});
			} else {
				// set reverse values
				if (!velocity.Utilities.data(elem, _dataID)) {
					velocity.Utilities.data(elem, _dataID, {
						reverseProps: _util.css(elem, Object.keys(_properties))
					});
				}
				// animate
				velocity(elem, properties, options);
				if (options.queue !== undefined) {
					velocity.Utilities.dequeue(elem, options.queue);
				}
			}
		};
		var reverse = function (elem, options) {
			if (_util.type.Array(elem)) {
				elem.forEach(function (elem) {
					reverse(elem, options);
				});
			} else {
				var data = velocity.Utilities.data(elem, _dataID);
				if (data && data.reverseProps) {
					velocity(elem, data.reverseProps, options);
					if (options.queue !== undefined) {
						velocity.Utilities.dequeue(elem, options.queue);
					}
				}
			}
		};

		/**
		 * Update the tween progress to current position.
		 * @private
		 */
		var updateAnimationProgress = function () {
			if (_elems) {
				var progress = Scene.progress();
				if (progress != _currentProgress) { // do we even need to update the progress?
					if (Scene.duration() === 0) {
						// play the animation
						if (progress > 0) { // play forward
							animate(_elems, _properties, _options);
						} else { // play reverse
							reverse(_elems, _options);
							// velocity(_elems, _propertiesReverse, _options);
							// velocity("reverse");
						}
					} else {
						// TODO: Scrollbound animations not supported yet...
					}
					_currentProgress = progress;
				}
			}
		};

		/**
		 * Add a Velocity animation to the scene.  
		 * The method accepts the same parameters as Velocity, with the first parameter being the target element.
		 *
		 * To gain better understanding, check out the [Velocity example](../examples/basic/simple_velocity.html).
		 * @memberof! animation.Velocity#
		 *
		 * @example
		 * // trigger a Velocity animation
		 * scene.setVelocity("#myElement", {opacity: 0.5}, {duration: 1000, easing: "linear"});
		 *
		 * @param {(object|string)} elems - One or more Dom Elements or a Selector that should be used as the target of the animation.
		 * @param {object} properties - The CSS properties that should be animated.
		 * @param {object} options - Options for the animation, like duration or easing.
		 * @returns {Scene} Parent object for chaining.
		 */
		Scene.setVelocity = function (elems, properties, options) {
			if (_elems) { // kill old ani?
				Scene.removeVelocity();
			}

			_elems = _util.get.elements(elems);
			_properties = properties || {};
			_options = options || {};
			_dataID = "ScrollMagic." + NAMESPACE + "[" + (autoindex++) + "]";

			if (_options.queue !== undefined) {
				// we'll use the queue to identify the animation. When defined it will always stop the previously running one.
				// if undefined the animation will always fully run, as is expected.
				// defining anything other than 'false' as the que doesn't make much sense, because ScrollMagic takes control over the trigger.
				// thus it is also overwritten.
				_options.queue = _dataID + "_queue";
			}

			var checkDuration = function () {
				if (Scene.duration() !== 0) {
					log(1, "ERROR: The Velocity animation plugin does not support scrollbound animations (scenes with duration) yet.");
				}
			};
			Scene.on("change.plugin_velocity", function (e) {
				if (e.what == 'duration') {
					checkDuration();
				}
			});
			checkDuration();
			log(3, "added animation");

			updateAnimationProgress();
			return Scene;
		};
		/**
		 * Remove the animation from the scene.  
		 * This will stop the scene from triggering the animation.
		 *
		 * Using the reset option you can decide if the animation should remain in the current state or be rewound to set the target elements back to the state they were in before the animation was added to the scene.
		 * @memberof! animation.Velocity#
		 *
		 * @example
		 * // remove the animation from the scene without resetting it
		 * scene.removeVelocity();
		 *
		 * // remove the animation from the scene and reset the elements to initial state
		 * scene.removeVelocity(true);
		 *
		 * @param {boolean} [reset=false] - If `true` the animation will rewound.
		 * @returns {Scene} Parent object for chaining.
		 */
		Scene.removeVelocity = function (reset) {
			if (_elems) {
				// stop running animations
				if (_options.queue !== undefined) {
					velocity(_elems, "stop", _options.queue);
				}
				if (reset) {
					reverse(_elems, {
						duration: 0
					});
				}

				_elems.forEach(function (elem) {
					velocity.Utilities.removeData(elem, _dataID);
				});
				_elems = _properties = _options = _dataID = undefined;
			}
			return Scene;
		};
	});
}));