/*
	SUPERSCROLLORAMA - The jQuery plugin for doing scroll animations
	by John Polacek (@johnpolacek)

	Powered by the Greensock Tweening Platform
	http://www.greensock.com
	Greensock License info at http://www.greensock.com/licensing/

	Dual licensed under MIT and GPL.

	Thanks to Jan Paepke (@janpaepke) for making many nice improvements
*/

(function($) {

	$.superscrollorama = function(options) {

		var superscrollorama = this;
		var defaults = {
			isVertical:true,		// are we scrolling vertically or horizontally?
			triggerAtCenter: true,	// the animation triggers when the respective Element's origin is in the center of the scrollarea. This can be changed here to be at the edge (-> false)
			playoutAnimations: true,	// when scrolling past the animation should they be played out (true) or just be jumped to the respective last frame (false)? Does not affect animations where duration = 0
			reverse: true			// make reverse configurable so you don't have to pass it in for every tween to reverse globally
		};
		superscrollorama.settings = $.extend({}, defaults, options);
        var $window = $(window);

		// PRIVATE VARS

		var animObjects = [],
			pinnedObjects = [],
			scrollContainerOffset = {x: 0, y: 0},
			doUpdateOnNextTick = false,
			targetOffset,
			i;

		// PRIVATE FUNCTIONS

		function init() {
			// set event handlers
			$window.scroll(function() {
				doUpdateOnNextTick = true;
			});
			TweenLite.ticker.addEventListener("tick", tickHandler);
		}

		function cssNumericPosition ($elem) { // return 0 when value is auto
			var obj = {
				top: parseFloat($elem.css("top")),
				left: parseFloat($elem.css("left"))
			};
			if (isNaN(obj.top)) {
				obj.top = 0;
			}
			if (isNaN(obj.left)) {
				obj.left = 0;
			}
			return obj;
		}

		function tickHandler() {
			if (doUpdateOnNextTick) {
				checkScrollAnim();
				doUpdateOnNextTick = false;
			}
		}

		// reset a pin Object
		function resetPinObj (pinObj) {
			pinObj.el.css('position', pinObj.origPositioning.pos);
			pinObj.el.css('top', pinObj.origPositioning.top);
			pinObj.el.css('left', pinObj.origPositioning.left);
		}
		// set a Tween Progress (use totalProgress for TweenMax and TimelineMax to include repeats)
		function setTweenProgress(tween, progress) {
			if (tween) {
				if (tween.totalProgress) {
					tween.totalProgress(progress).pause();
				} else {
					tween.progress(progress).pause();
				}
			}
		}

		function checkScrollAnim() {

			var currScrollPoint = superscrollorama.settings.isVertical ? $window.scrollTop() + scrollContainerOffset.y :  $window.scrollLeft() + scrollContainerOffset.x;
			var offsetAdjust = superscrollorama.settings.triggerAtCenter ? (superscrollorama.settings.isVertical ? - $window.height()/2 : - $window.width()/2) : 0;
			var i, startPoint, endPoint;

			// check all animObjects
			var numAnim = animObjects.length;
			for (i=0; i<numAnim; i++) {
				var animObj = animObjects[i],
					target = animObj.target,
					offset = animObj.offset;

				if (typeof(target) === 'string') {
                    targetOffset = $(target).offset();
					startPoint = superscrollorama.settings.isVertical ? targetOffset.top + scrollContainerOffset.y : targetOffset.left + scrollContainerOffset.x;
					offset += offsetAdjust;
				} else if (typeof(target) === 'number')	{
					startPoint = target;
				} else if ($.isFunction(target)) {
					startPoint = target.call(this);
				} else {
                    targetOffset = target.offset();
                    startPoint = superscrollorama.settings.isVertical ? targetOffset.top + scrollContainerOffset.y : targetOffset.left + scrollContainerOffset.x;
					offset += offsetAdjust;
				}

				startPoint += offset;
				endPoint = startPoint + animObj.dur; // if the duration is 0 the animation should autoplay (forward going from BEFORE to AFTER and reverse going from AFTER to BEFORE)

				if ((currScrollPoint > startPoint && currScrollPoint < endPoint) && animObj.state !== 'TWEENING') {
					// if it should be TWEENING and isn't..
					animObj.state = 'TWEENING';
					animObj.start = startPoint;
					animObj.end = endPoint;
				}
				if (currScrollPoint < startPoint && animObj.state !== 'BEFORE' && animObj.reverse) {
					// if it should be at the BEFORE tween state and isn't..
					if (superscrollorama.settings.playoutAnimations || animObj.dur === 0) {
						animObj.tween.reverse();
					} else {
						setTweenProgress(animObj.tween, 0);
					}
					animObj.state = 'BEFORE';
				} else if (currScrollPoint > endPoint && animObj.state !== 'AFTER') {
					// if it should be at the AFTER tween state and isn't..
					if (superscrollorama.settings.playoutAnimations || animObj.dur === 0) {
						animObj.tween.play();
					} else {
						setTweenProgress(animObj.tween, 1);
					}
					animObj.state = 'AFTER';
				} else if (animObj.state === 'TWEENING') {
					// if it is TWEENING..
					var repeatIndefinitely = false;
					if (animObj.tween.repeat) {
						// does the tween have the repeat option (TweenMax / TimelineMax)
						repeatIndefinitely = (animObj.tween.repeat() === -1);
					}

					if (repeatIndefinitely) { // if the animation loops indefinitely it will just play for the time of the duration
						var playheadPosition = animObj.tween.totalProgress(); // there is no "isPlaying" value so we need to save the playhead to determine whether the animation is running
						if (animObj.playeadLastPosition === null || playheadPosition === animObj.playeadLastPosition) {
							if (playheadPosition === 1) {
								if (animObj.tween.yoyo()) {
									// reverse Playback with infinitely looped tweens only works with yoyo true
									animObj.tween.reverse();
								} else {
									animObj.tween.totalProgress(0).play();
								}
							} else {
								animObj.tween.play();
							}
						}
						animObj.playeadLastPosition = playheadPosition;
					} else {
						setTweenProgress(animObj.tween, (currScrollPoint - animObj.start)/(animObj.end - animObj.start));
					}
				}
			}

			// check all pinned elements
			var numPinned = pinnedObjects.length;
			for (i=0; i<numPinned; i++) {
				var pinObj = pinnedObjects[i];
				var el = pinObj.el;

				// should object be pinned (or updated)?
				if (pinObj.state !== 'PINNED') {

                    var pinObjSpacerOffset = pinObj.spacer.offset();

					if (pinObj.state === 'UPDATE') {
						resetPinObj(pinObj); // revert to original Position so startPoint and endPoint will be calculated to the correct values
					}

					startPoint = superscrollorama.settings.isVertical ? pinObjSpacerOffset.top + scrollContainerOffset.y : pinObjSpacerOffset.left + scrollContainerOffset.x;
					startPoint += pinObj.offset;
					endPoint = startPoint + pinObj.dur;

					var jumpedPast = ((currScrollPoint > endPoint && pinObj.state === 'BEFORE') || (currScrollPoint < startPoint && pinObj.state === 'AFTER')); // if we jumped past a pinarea (i.e. when refreshing or using a function) we need to temporarily pin the element so it gets positioned to start or end respectively
					var inPinAra = (currScrollPoint > startPoint && currScrollPoint < endPoint);
					if (inPinAra || jumpedPast) {
						// set original position values for unpinning
						if (pinObj.pushFollowers && el.css('position') === "static") { // this can't be. If we want to pass following elements we need to at least allow relative positioning
							el.css('position', "relative");
						}
						// save original positioning
						pinObj.origPositioning = {
							pos: el.css('position'),
							top: pinObj.spacer.css('top'),
							left: pinObj.spacer.css('left')
						};
						// change to fixed position
						pinObj.fixedPositioning = {
							top: superscrollorama.settings.isVertical ? -pinObj.offset : pinObjSpacerOffset.top,
							left: superscrollorama.settings.isVertical ? pinObjSpacerOffset.left : -pinObj.offset
						};
						el.css('position','fixed');
						el.css('top', pinObj.fixedPositioning.top);
						el.css('left', pinObj.fixedPositioning.left);

						// save values
						pinObj.pinStart = startPoint;
						pinObj.pinEnd = endPoint;

						// If we want to push down following Items we need a spacer to do it, while and after our element is fixed.
						if (pinObj.pushFollowers) {
							if (superscrollorama.settings.isVertical) {
									pinObj.spacer.height(pinObj.dur + el.outerHeight());
							} else {
									pinObj.spacer.width(pinObj.dur + el.outerWidth());
							}
						} else {
							if (pinObj.origPositioning.pos === "absolute") { // no spacer
								pinObj.spacer.width(0);
								pinObj.spacer.height(0);
							} else { // spacer needs to reserve the elements space, while pinned
								if (superscrollorama.settings.isVertical) {
									pinObj.spacer.height(el.outerHeight());
								} else {
									pinObj.spacer.width(el.outerWidth());
								}
							}
						}


						if (pinObj.state === "UPDATE") {
							if (pinObj.anim) {
								setTweenProgress(pinObj.anim, 0); // reset the progress, otherwise the animation won't be updated to the new position
							}
						} else if (pinObj.onPin) {
							pinObj.onPin(pinObj.state === "AFTER");
						}

						// pin it!
						pinObj.state = 'PINNED';
					}
				}
				// If state changed to pinned (or already was) we need to position the element
				if (pinObj.state === 'PINNED') {
					// Check to see if object should be unpinned
					if (currScrollPoint < pinObj.pinStart || currScrollPoint > pinObj.pinEnd) {
						// unpin it
						var before = currScrollPoint < pinObj.pinStart;
						pinObj.state = before ? 'BEFORE' : 'AFTER';
						// set Animation to end or beginning
						setTweenProgress(pinObj.anim, before ? 0 : 1);

						var spacerSize = before ? 0 : pinObj.dur;

						if (superscrollorama.settings.isVertical) {
							pinObj.spacer.height(pinObj.pushFollowers ? spacerSize : 0);
						} else {
							pinObj.spacer.width(pinObj.pushFollowers ? spacerSize : 0);
						}

						// correct values if pin Object was moved (animated) during PIN (pinObj.el.css values will never be auto as they are set by the class)
						var deltay = pinObj.fixedPositioning.top - cssNumericPosition(pinObj.el).top;
						var deltax = pinObj.fixedPositioning.left - cssNumericPosition(pinObj.el).left;

						// first revert to start values
						resetPinObj(pinObj);

						// position element correctly
						if (!pinObj.pushFollowers || pinObj.origPositioning.pos === "absolute") {
							var pinOffset;

							if (pinObj.origPositioning.pos === "relative") { // position relative and pushFollowers = false
								pinOffset = superscrollorama.settings.isVertical ?
											parseFloat(pinObj.origPositioning.top) :
											parseFloat(pinObj.origPositioning.left);
								if (isNaN(pinOffset)) { // if Position was "auto" parseFloat will result in NaN
									pinOffset = 0;
								}
							} else {
								pinOffset = superscrollorama.settings.isVertical ?
											pinObj.spacer.position().top :
											pinObj.spacer.position().left;
							}

							var direction = superscrollorama.settings.isVertical ?
											"top" :
											"left";

							pinObj.el.css(direction, pinOffset + spacerSize);
						} // if position relative and pushFollowers is true the element remains untouched.

						// now correct values if they have been changed during pin
						if (deltay !== 0) {
							pinObj.el.css("top", cssNumericPosition(pinObj.el).top - deltay);
						}
						if (deltax !== 0) {
							pinObj.el.css("left", cssNumericPosition(pinObj.el).left - deltax);
						}
						if (pinObj.onUnpin) {
							pinObj.onUnpin(!before);
						}
					} else if (pinObj.anim) {
						// do animation
						setTweenProgress(pinObj.anim, (currScrollPoint - pinObj.pinStart)/(pinObj.pinEnd - pinObj.pinStart));
					}
				}
			}
		}

		// PUBLIC FUNCTIONS
		superscrollorama.addTween = function(target, tween, dur, offset, reverse) {

			tween.pause();

			animObjects.push({
				target:target,
				tween: tween,
				offset: offset || 0,
				dur: dur || 0,
				reverse: (typeof reverse !== "undefined") ? reverse : superscrollorama.settings.reverse, // determine if reverse animation has been disabled
				state:'BEFORE'
			});

			return superscrollorama;
		};

		superscrollorama.pin = function(el, dur, vars) {
			if (typeof(el) === 'string') {
				el = $(el);
			}
			var defaults = {
				offset: 0,
				pushFollowers: true		// if true following elements will be "pushed" down, if false the pinned element will just scroll past them
			};
			vars = $.extend({}, defaults, vars);
			if (vars.anim) {
				vars.anim.pause();
			}

			var spacer = $('<div class="superscrollorama-pin-spacer"></div>');
			spacer.css("position", "relative");
			spacer.css("top", el.css("top"));
			spacer.css("left", el.css("left"));
			el.before(spacer);

			pinnedObjects.push({
				el:el,
				state:'BEFORE',
				dur:dur,
				offset: vars.offset,
				anim:vars.anim,
				pushFollowers:vars.pushFollowers,
				spacer:spacer,
				onPin:vars.onPin,
				onUnpin:vars.onUnpin
			});
			return superscrollorama;
		};

		superscrollorama.updatePin = function (el, dur, vars) { // Update a Pinned object. dur and vars are optional to only change vars and keep dur just pass NULL for dur
			if (typeof(el) === 'string') {
				el = $(el);
			}
			if (vars.anim) {
				vars.anim.pause();
			}

			var numPinned = pinnedObjects.length;

			for (i=0; i<numPinned; i++) {
				var pinObj = pinnedObjects[i];
				if (el.get(0) === pinObj.el.get(0)) {

					if (dur) {
						pinObj.dur = dur;
					}
					if (vars.anim) {
						pinObj.anim = vars.anim;
					}
					if (vars.offset) {
						pinObj.offset = vars.offset;
					}
					if (typeof vars.pushFollowers !== "undefined") {
						pinObj.pushFollowers = vars.pushFollowers;
					}
					if (vars.onPin) {
						pinObj.onPin = vars.onPin;
					}
					if (vars.onUnpin) {
						pinObj.onUnpin = vars.onUnpin;
					}
					if ((dur || vars.anim || vars.offset) && pinObj.state === 'PINNED') { // this calls for an immediate update!
						pinObj.state = 'UPDATE';
						checkScrollAnim();
					}
				}
			}
			return superscrollorama;
		};

		superscrollorama.removeTween = function (target, tween, reset) {
			var count = animObjects.length;
			if (typeof reset === "undefined") {
				reset = true;
			}
			for (var index = 0; index < count; index++) {
				var value = animObjects[index];
				if (value.target === target &&
					(!tween || value.tween === tween)) { // tween is optional. if not set just remove element
					animObjects.splice(index,1);
					if (reset) {
						setTweenProgress(value.tween, 0);
					}
					count--;
					index--;
				}
			}
			return superscrollorama;
		};

		superscrollorama.removePin = function (el, reset) {
			if (typeof(el) === 'string') {
				el = $(el);
			}
			if (typeof reset === "undefined") {
				reset = true;
			}
			var count = pinnedObjects.length;
			for (var index = 0; index < count; index++) {
				var value = pinnedObjects[index];
				if (value.el.is(el)) {
					pinnedObjects.splice(index,1);
					if (reset) {
						value.spacer.remove();
						resetPinObj(value);
						if (value.anim) {
							setTweenProgress(value.anim, 0);
						}
					}
					count--;
					index--;
				}
			}
			return superscrollorama;
		};

		superscrollorama.setScrollContainerOffset = function (x, y) {
			scrollContainerOffset.x = x;
			scrollContainerOffset.y = y;
			return superscrollorama;
		};

		superscrollorama.triggerCheckAnim = function (immediately) { // if immedeately is true it will be updated right now, if false it will wait until next tweenmax tick. default is false
			if (immediately) {
				checkScrollAnim();
			} else {
				doUpdateOnNextTick = true;
			}
			return superscrollorama;
		};


		// INIT
		init();

		return superscrollorama;
	};

})(jQuery);