// com.tts.initial.html5.movingpicture
define(["jquery"], function (jq) {

	"use strict";

    var MovingPictureElementFactory = function( context, metadata ){

			var testId = "ttsMovingPictureElement",
				commonCss = {
					"position": "relative",
					"background-color": "transparent",
					"border": "0px",
					"backgroundPosition": "0px 0px",
					"cursor": metadata.parameters.get("cursor", "pointer"),
					"width": metadata.getWidth() + "px",
					"height": metadata.getHeight() + "px"
				};

			function createPicture( imageFilename ) {
				return jq(document.createElement("div"))
					.css(commonCss)
					.css({
						"backgroundRepeat" : "no-repeat",
						"backgroundSize" : "100% 100%",
						"backgroundImage" : "url(\"" + context.stage.getSharedPath() + imageFilename + "\")"
					})
					.addClass(testId);
			}

			function createPlaceholder() {
				return jq(document.createElement("div"))
					.css( commonCss )
					.css({
						"opacity" : "0.15",
						"text-align" : "center",
						"line-height" : metadata.getHeight() + "px",
						"font-size" : "36px",
						"color" : "#000000",
						"backgroundImage" : "url(\"" + context.getPath() + "invis.png" + "\")"
					})
					.addClass(testId);
			}

			return {
				create : function(){
					var backgroundImage = metadata.parameters.get("picture", null);

					if (backgroundImage) {
						return createPicture( backgroundImage.fileName );
					}

					return createPlaceholder();
				}
			};
		},

		PictureAnimator = function( options, context ) {

            var jqPictureElement = jq(options.pictureElement),
                animationIntervalId,
	            initialOffset,
                moveOffsetCache;

			function startAnimationInterval( hotspotInfo ) {

				stopAnimationInterval();

				var startOffset = hotspotInfo.initialOffset,
					endOffset = getTargetOffset( hotspotInfo );

				moveImage( startOffset, endOffset, options.speed );

				animationIntervalId = setInterval(function () {
					moveImage( startOffset, endOffset, options.speed );
				}, 25);
			}

			function getTargetOffset(hotspotInfo) {

				var movingForce = {
					"x": options.forceX / context.stage.getZoom(),
					"y": options.forceY / context.stage.getZoom()
				};

				return {
					left: hotspotInfo.initialOffset.left + ((hotspotInfo.interactionHotspot.width / 2 - hotspotInfo.pointerInfo.x) * movingForce.x),
					top: hotspotInfo.initialOffset.top + ((hotspotInfo.interactionHotspot.height / 2 - hotspotInfo.pointerInfo.y) * movingForce.y)
				};
			}

			function startResetAnimationInterval( hotspotInfo ) {

				stopAnimationInterval();

				var startOffset = moveOffsetCache,
					endOffset = hotspotInfo.initialOffset,
					speed = options.speed < 0.15 ? 0.15 : options.speed;

				animationIntervalId = setInterval(function () {
					moveImage( startOffset, endOffset, speed );
				}, 25);
			}

			function moveImage( startOffset, endOffset, speed ) {

				var moveOffset = getMoveOffset( startOffset, endOffset, speed );

				jqPictureElement.css({
					"left": (moveOffset.left - initialOffset.left) + "px",
					"top": (moveOffset.top - initialOffset.top) + "px"
				});

				if( offsetEquals( moveOffset, endOffset ) ){
					stopAnimationInterval();
				}
			}

			function getMoveOffset( startOffset, endOffset, speed) {

				// In order to beeing able to calculate the position that we want to move to within this
				// animation cycle, we first have to find out the distance percentage, that we have already been moved based on
				// our current position and the current target position (target can constantly be changing, because pointer
				// might be moving while animation is running.
				var originOffsetToTarget = {
						left: endOffset.left - startOffset.left,
						top: endOffset.top - startOffset.top
					},
					currentOffsetToTarget = {
						left: endOffset.left - moveOffsetCache.left,
						top: endOffset.top - moveOffsetCache.top
					},

					distanceOriginToTarget = {
						left: Math.abs(Math.max(endOffset.left, originOffsetToTarget.left) - Math.min(endOffset.left, originOffsetToTarget.left)),
						top: Math.abs(Math.max(endOffset.top, originOffsetToTarget.top) - Math.min(endOffset.top, originOffsetToTarget.top))
					},
					distanceCurrentToTarget = {
						left: Math.abs(Math.max(endOffset.left, moveOffsetCache.left) - Math.min(endOffset.left, moveOffsetCache.left)),
						top: Math.abs(Math.max(endOffset.top, moveOffsetCache.top) - Math.min(endOffset.top, moveOffsetCache.top))
					},

					// Having the percentage, we have to feed our easing function with it, in order to get the correct percentage,
					// before the easing function was applied within last cycle.
					percentMovedWithEasing = {
						left: Math.abs(((Math.min(distanceOriginToTarget.left, distanceCurrentToTarget.left) / Math.max(distanceOriginToTarget.left, distanceCurrentToTarget.left)) / 100) - 1) % 1,
						top: Math.abs(((Math.min(distanceOriginToTarget.top, distanceCurrentToTarget.top) / Math.max(distanceOriginToTarget.top, distanceCurrentToTarget.top)) / 100) - 1) % 1
					},

					percentMovedWithoutEasing = {
						left: (Math.acos((percentMovedWithEasing.left * 2) - 1) / Math.PI),
						top: (Math.acos((percentMovedWithEasing.top * 2) - 1) / Math.PI)
					},

					newProgressInPercent = {
						left: Math.min(percentMovedWithoutEasing.left + speed, 1),
						top: Math.min(percentMovedWithoutEasing.top + speed, 1)
					},

					newWeightedProgressInPercent = {
						left: Math.abs(((Math.cos(Math.PI * newProgressInPercent.left) + 1) / 2) - 1),
						top: Math.abs(((Math.cos(Math.PI * newProgressInPercent.top) + 1) / 2) - 1)
					},

					moveOffset = {
						left: moveOffsetCache.left + (currentOffsetToTarget.left * newWeightedProgressInPercent.left),
						top: moveOffsetCache.top + (currentOffsetToTarget.top * newWeightedProgressInPercent.top)
					};

				// for each cycle update current position (it's like a caching mechanism, so we do not have
				// to retrieve the current position through jquerys offset() method, which might be slow)
				moveOffsetCache = {
					left: moveOffset.left,
					top: moveOffset.top
				};

				return moveOffsetCache;
			}

			function offsetEquals( offset1, offset2 ) {
				var diff = (offset1.left - offset2.left) + (offset1.top - offset2.top);
				return precisionRound( diff ) === 0;
			}

			function precisionRound(number) {
				var factor = Math.pow(10, 1);
				return Math.round(number * factor) / factor;
			}

            function stopAnimationInterval(){
	            if( animationIntervalId ) {
		            clearInterval(animationIntervalId);
		            animationIntervalId = null;
	            }
            }

			return {
				initializeAnimation: function( hotspotInfo ) {
					initialOffset = hotspotInfo.initialOffset;
					moveOffsetCache = initialOffset;
				},
				startAnimation: startAnimationInterval,
                resetAnimation: startResetAnimationInterval
            };
        },

		Hotspot = function( options, context ) {

			var jqElement = jq( options.hotspotElement ),
				interactionElement = jq( document.body ).get(0),
				initialOffset = jqElement.offset(),
				pointerHasEnteredInteractionArea = false,
				pointerInfo = {
					x : -1,
					y : -1
				};

			if( typeof(options.callbacks.onInitialize) === "function" ) {
				options.callbacks.onInitialize(getHotspotInfo());
			}

			function getZoomedInteractionHotspot() {

				var stageOffset = context.stage.getOffset(),
					stageZoom = context.stage.getZoom();

				return {
					left : (options.interactionBox.x * stageZoom) + stageOffset.left,
					top : (options.interactionBox.y * stageZoom) + stageOffset.top,
					width : options.interactionBox.width * stageZoom,
					height : options.interactionBox.height * stageZoom
				};
			}

			function checkTouchState( event ){

				if( getPointerType( event ) !== "mouse" ){
					return;
				}

				pointerInfo = getPointerInfoFromEvent(event);

				if ( pointerIsOverInteractionArea() ) {

					// if interaction area had previously NOT been entered, pointer just went inside the box, so activate everything
					if (!pointerHasEnteredInteractionArea) {
						pointerHasEnteredInteractionArea = true;
						context.page.event.trigger( options.instance, "onMouseEnter");
					}

					if( typeof(options.callbacks.onEnter) === "function" ) {
						options.callbacks.onEnter( getHotspotInfo() );
					}

					return;
				}

				// if interaction area had previously been entered, pointer just went outside the box, so deactivate everything
				if (pointerHasEnteredInteractionArea) {
					pointerHasEnteredInteractionArea = false;

					if( typeof(options.callbacks.onLeave) === "function" ) {
						options.callbacks.onLeave( getHotspotInfo() );
					}

					context.page.event.trigger( options.instance, "onMouseOut");
				}
			}

			/**
			 * @private
			 * @param {jQuery.Event} jqEvent
			 * @returns {"pen" | "touch" | "mouse"}
			 */
			function getPointerType(jqEvent) {
				if (jqEvent.type && jq.inArray(jqEvent.type, ["touchstart", "touchmove", "touchend"]) !== -1) {
					return "touch";
				}
				if (jqEvent.type && jq.inArray(jqEvent.type, ["pointerdown", "pointermove", "pointerup"]) !== -1 && jqEvent.originalEvent && jqEvent.originalEvent.pointerType) {
					return jqEvent.originalEvent.pointerType;
				}
				return "mouse";
			}

			function pointerIsOverInteractionArea() {
				var hotspot = getZoomedInteractionHotspot();
				return pointerInfo.x > 0
					&& pointerInfo.x < hotspot.width
					&& pointerInfo.y > 0
					&& pointerInfo.y < hotspot.height;
			}

			function getHotspotInfo() {
				return {
					"initialOffset": initialOffset,
					"pointerInfo": pointerInfo,
					"interactionHotspot": getZoomedInteractionHotspot()
				};
			}

			function onPointerMove(event) {
                checkTouchState( event );
			}

			function getPointerInfoFromEvent(event) {

				var hotspot = getZoomedInteractionHotspot(),
					touch,
					pointerX,
					pointerY;

				if (event.originalEvent && event.originalEvent.touches) {
					touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0];
					pointerX = touch.pageX;
					pointerY = touch.pageY;
				}
				else {
					pointerX = event.pageX;
					pointerY = event.pageY;
				}

				// calculate pointer relative to interaction area and clamp values smaller zero or bigger interaction box
				return {
					x: Math.min(Math.max(0, pointerX - hotspot.left), hotspot.width),
					y: Math.min(Math.max(0, pointerY - hotspot.top), hotspot.height)
				};
			}

			function onPointerLeave() {
				pointerHasEnteredInteractionArea = false;
				options.callbacks.onLeave( getHotspotInfo() );
			}

			return {

                // note, that the result depends on the current zoom factor.
                getInitialOffset: function () {
                    return initialOffset;
                },

                // note, that the result depends on the current zoom factor.
                getInteractionHotspot: function () {
                    return getZoomedInteractionHotspot();
                },

                bindEvents: function () {
                    context.device.attachFirstSupportedEvent( interactionElement, "pointermove mousemove mouseenter", onPointerMove);
                    context.device.attachFirstSupportedEvent( interactionElement, "pointerleave mouseleave", onPointerLeave);
                    context.device.attachFirstSupportedEvent( interactionElement, "pointerout mouseout", onPointerLeave);
	            },

                releaseEvents: function () {
                    context.device.removeFirstSupportedEvent( interactionElement, "pointermove mousemove mouseenter", onPointerMove);
                    context.device.removeFirstSupportedEvent( interactionElement, "pointerleave mouseleave", onPointerLeave);
                    context.device.removeFirstSupportedEvent( interactionElement, "pointerout mouseout", onPointerLeave);
                }
            };
		};

	return function( context, metadata ) {

		var screenHotspot;

        function createMovingPicture( instance, shellElement ) {

        	var movingPictureElement = new MovingPictureElementFactory( context, metadata ).create(),
		        useReleativeInteractionArea =  metadata.parameters.asBool("interactionBox"),
                pictureAnimator;

            jq(shellElement).append( movingPictureElement );

            pictureAnimator = new PictureAnimator({
					"pictureElement": movingPictureElement,
					"speed": metadata.parameters.asNumber("speed", 0.01 ),
					"forceX": metadata.parameters.asNumber("forceX") / 100,
					"forceY": metadata.parameters.asNumber("forceY") / 100
            	},
				context
			);

            screenHotspot = new Hotspot({
					"instance" : instance,
	                "scId" : metadata.getId(),
                	"hotspotElement" : movingPictureElement,
		            "interactionBox" : {
                        "x": useReleativeInteractionArea ? metadata.parameters.asNumber("interactionBoxX", 100) : 0,
                        "y": useReleativeInteractionArea ? metadata.parameters.asNumber("interactionBoxY", 100) : 0,
                        "width": useReleativeInteractionArea ? metadata.parameters.asNumber("interactionBoxWidth", 500) : metadata.stage.getWidth(),
                        "height": useReleativeInteractionArea ? metadata.parameters.asNumber("interactionBoxHeight", 281) : metadata.stage.getHeight()
                    },
					"callbacks" : {
						"onInitialize" : pictureAnimator.initializeAnimation,
						"onEnter" : pictureAnimator.startAnimation,
                        "onLeave" : pictureAnimator.resetAnimation
					}
				},
				context
			);
        }

		function createInteractionAreaPreview( hotspot ){

			var interactionPreview = jq(document.createElement("div")),
				shellElement = jq( context.getElement() ),
				initialOffset = hotspot.getInitialOffset(),
				interactionHotspot = hotspot.getInteractionHotspot();

			interactionPreview.css({
				"width": interactionHotspot.width + "px",
				"height": interactionHotspot.height + "px",
				"left": (interactionHotspot.left - initialOffset.left) + "px",
				"top": (interactionHotspot.top - initialOffset.top) + "px",
				"line-height": interactionHotspot.height + "px",
				"text-align" : "center",
				"font-size" : "36",
				"overflow" : "hidden",
				"position": "absolute",
				"border" : "1px dashed #ff0000",
				"color" : "#ff0000",
				"z-index" : "1000"
			});

			shellElement.prepend(interactionPreview);
		}

		// revealing public API
		return {

			onLoading : function() {

				context.page.event.bindOnEnter( this );
				context.page.event.bindOnExit( this );

                createMovingPicture( this, context.getElement() );

				var useInteractionBox = metadata.parameters.asBool("interactionBox");
				if( context.stage.isInAuthoringMode() && useInteractionBox ){
					createInteractionAreaPreview( screenHotspot );
				}
			},

			onEnterPage : function() {
				if( !context.stage.isInAuthoringMode() ) {
                    screenHotspot.bindEvents();
				}
			},

			onExitPage : function() {
				if( !context.stage.isInAuthoringMode() ) {
					screenHotspot.releaseEvents();
				}
			}
		};
	};
});
