// com.tts.initial.html5.quiz.ddsource.autoform
define([
    "jquery",
    "./source.element.builder",
    "./offset.service",
    "./source.store",
    "./target.registry",
    "./source.state",
    "./quiz.state"
],
function( jq,
          SourceElementBuilder,
          OffsetService,
          DropSourceStore,
          TargetRegistry,
          SourceState,
          QuizState ) {

	"use strict";

	/**
	 * @constructor
	 */
	return function ( context, metadata ) {

		// declare private members
		var self = null,
            parameters = metadata.parameters,
            isStandardPlayer = context.getPlayerName() === "standard",

            offsetService = new OffsetService( context ),
            dropSourceStore = new DropSourceStore( metadata.page.getId(), metadata.getId(), context ),
            targetRegistry = new TargetRegistry( dropSourceStore, context, metadata ),
            sourceState = new SourceState( dropSourceStore ),
            quizState = new QuizState( dropSourceStore ),
            sourceObject,
            destinationTargetIds = getDestinationTargetIds( "target_", 20 ),

            dragOverTarget = null,
            dropTarget = null,

            dragging = false,

            mousePosition = {
                "left": -1,
                "top": -1 },
            elementCentration = {
                "left" : metadata.getWidth()/2,
                "top" : metadata.getHeight()/2
	    	};

		function log( message ) {
            if (console) {
                console.log( "ddsource::" +  message );
            }
        }

		function getDestinationTargetIds( id, max ) {

			var result = [],
				i;

			for( i=1; i <= max; i++ ) {
				if (parameters.get(id + i) === "true") {
					result.push( i.toString() );
				}
			}

			return result;
		}

		function createComponent() {
			self = this;
			setComponentBehaviour();
            sourceObject = new SourceElementBuilder( context, metadata, offsetService ).build();
		}

		function setComponentBehaviour() {
			context.page.quiz.add( self, "1", true );
			context.page.event.bindOnEnter( self );
			context.page.event.bindOnExit( self );
		}

		function resetStates() {

			dragOverTarget = null;
			dropTarget = null;
			dragging = false;

			sourceObject.initialize();

            resetPlacementState();

            if( context.stage.isInPreviewMode() ){
                dropSourceStore.clear();
			    targetRegistry.initialize();
            }
        }

		function hasDestinationTarget() {
            return destinationTargetIds.length > 0;
        }

        function resetPlacementState(){
            sourceState.reset( hasDestinationTarget() );
            notifyStartPlacementStateToPlayer();
        }

        function notifyStartPlacementStateToPlayer() {
            //If a source has no target assigned, then the start position counts as the correct target position.
            context.page.quiz.setSelected( self, !hasDestinationTarget() );
        }

		function isEvaluated(){
            return dropSourceStore.isEvaluated();
		}

        function moveToStartPosition( skipAnimation ){
            sourceObject.moveToStartOffset( skipAnimation );
            notifyStartPlacementStateToPlayer();
        }

        function moveToTargetPosition( offset, index ) {

            if( !isEvaluated() ) {
                sourceState.setTargetIndex( index );
                sourceState.store();
            }

            sourceObject.moveToOffset( offset );
        }

        function getEventNames( eventTarget ){

		    var upevents = context.device.getUpEvents( eventTarget.nodeName );

		    if( upevents.indexOf( "touchend" ) === -1 ) {
		        upevents = upevents + " touchend";
            }

            if( context.stage.isInPreviewMode() ){
                return {
                    up: "mouseup",
                    down: "mousedown",
                    move: "mousemove"
                };
            }

            return {
                up: upevents,
                down: context.device.getDownEvents( eventTarget.nodeName ),
                move: context.device.getMoveEvents( eventTarget.nodeName )
            };
        }

        function bindEvents() {

			if( context.stage.isInAuthoringMode() ) {
				return;
			}

			removeEvents();

            var eventTarget = sourceObject.getDragDiv().get(0),
                eventNames = getEventNames(eventTarget);

            context.device.attachFirstSupportedEvent( eventTarget, eventNames.down, dragStart );
            context.device.attachFirstSupportedEvent( eventTarget, eventNames.move, preventIOS8VerticalMove );
            context.device.attachFirstSupportedEvent( eventTarget, eventNames.up, drop );

			jq(document)
                .on( eventNames.down + " " + eventNames.move, getGlobalMousePosition )
                .on( eventNames.up, drop );
		}

        function getGlobalMousePosition (event) {
            mousePosition = offsetService.getPointerPositionFromEvent( event );
            if( dragging ) {
                drag();
            }
        }

        function removeEvents() {

            var eventTarget = sourceObject.getDragDiv().get(0),
                eventNames = getEventNames(eventTarget);

            context.device.removeFirstSupportedEvent( eventTarget, eventNames.down, dragStart );
            context.device.removeFirstSupportedEvent( eventTarget, eventNames.move, preventIOS8VerticalMove );
            context.device.removeFirstSupportedEvent( eventTarget, eventNames.up, drop );

			jq(document)
                .off( eventNames.down + " " + eventNames.move, getGlobalMousePosition )
                .off( eventNames.up, drop );
		}

		function preventIOS8VerticalMove(e) {
			e.preventDefault ? e.preventDefault() : true;
		}

		function dragStart() {

			if( sourceObject.isInAnimation() ){
				return;
			}

			lockNavigation();

			dragging = true;

			sourceObject.setDragStyle();

			checkAndNotifyTargetOnDragStart();
			checkAndNotifyTargetOnDrag();
		}

		function lockNavigation() {
			if( context.stage.trigger && typeof( context.stage.trigger.lock ) === "function" ) {
				context.stage.trigger.lock(metadata.getId());
			}
		}

		function drag() {
            sourceObject.setOffset( {
                left: mousePosition.left - elementCentration.left,
                top: mousePosition.top - elementCentration.top
            });
            sourceObject.clearCursorStyle();
			checkAndNotifyTargetOnDrag();
		}

		function checkAndNotifyTargetOnDragStart() {
			if( dropTarget ){
				dropTarget.onDragStart( self );
				dropTarget = null;
			}
		}

		function checkAndNotifyTargetOnDrag() {

			var currentDragOverTarget = targetRegistry.getTargetByPosition( mousePosition );
			if( dragOverTarget && dragOverTarget !== currentDragOverTarget ) {
				dragOverTarget.onDragOut( self );
				dragOverTarget = null;
			}

			if (currentDragOverTarget && currentDragOverTarget !== dragOverTarget) {
				currentDragOverTarget.onDragOver(self);
				dragOverTarget = currentDragOverTarget;
			}
		}

		function drop() {

			if( !dragging ) {
				return;
			}

			dragging = false;
			unlockNavigation();
			checkAndNotifyTargetOnDrop();
            sourceObject.setDroppedStyle( sourceState.hasValidTargetId() );
		}

		function unlockNavigation() {
			if( context.stage.trigger && typeof( context.stage.trigger.unlock ) === "function" ) {
				context.stage.trigger.unlock(metadata.getId());
			}
		}

		function checkAndNotifyTargetOnDrop() {

			var target = targetRegistry.getTargetByPosition( mousePosition );
			if( target && target.onDrop( self )) {

                if( isCorrectAssignment( target ) ){
                    sourceState.droppedOnCorrectTarget( target );
                    context.page.quiz.setSelected( self, true );
				}
				else {
                    sourceState.droppedOnWrongTarget( target );
                    context.page.quiz.setSelected( self, false );
				}

				dropTarget = target;
				dragOverTarget = null;
			}
			else {
                sourceState.droppedOnInvalidTarget( hasDestinationTarget() );
                moveToStartPosition();
			}

            sourceState.store();
        }

		function onUserSolution() {

            quizState.changeToUserSolution();
			removeEvents();
			setEvaluated();
            sourceObject.setUserSolutionStyle( sourceState.isDroppedCorrectly() );

            setTimeout( function () {
                context.page.event.trigger( self, "tg_us" );
            }, 10 );

            if ( sourceState.isDroppedCorrectly() ) {
                setTimeout( function () {
                    context.page.event.trigger( self, "tg_us_correct" );
                }, 10 );
            } else {
                setTimeout( function () {
                    context.page.event.trigger( self, "tg_us_wrong" );
                }, 10 );
            }

            if( quizState.wasChangingFromTestRun() ) {
                return;
            }

            // use timeout to let the target react to the async event "OnUserSolution" and to restore the users order.
            setTimeout( reassignSourceToUserSelectedTarget, getTimeoutByTargetIndex() );
		}

        function setEvaluated(){
            dropSourceStore.setEvaluated();
        }

        function getTimeoutByTargetIndex(){

   		    if( sourceState.getTargetIndex() >= 0 ){
   		        return (sourceState.getTargetIndex() + 1) * 10;
            }
            return 10;
        }

        function reassignSourceToUserSelectedTarget() {

            var target;

            if( sourceState.hasValidTargetId() ) {
                target = targetRegistry.getTargetById( sourceState.getTargetId() );
                if( target ) {
                    target.setAsUserSolution( self );
                }
                else {
                	console.error("Target with id " +  sourceState.getTargetId() + "not found!");
				}
                return;
            }

            moveToStartPosition();
        }

  		function onBestPracticeSolution() {
            quizState.changeToBestPracticeSolution();
			removeEvents();
            sourceObject.setBestPracticeSolutionStyle();

            setTimeout( function () {
                context.page.event.trigger( self, "tg_bps" );
            }, 10 );

            assignSourceToBestPracticeTarget();
		}

        function assignSourceToBestPracticeTarget() {

            // use timeouts to let the target react to the async event "OnBestPracticeSolution"
            // and place the targets in the right order

            if( placeSourceOnUserAssignedTarget() ) {
                return;
            }

            // use timeout to place not correct assigned source after the correct assigned
            setTimeout( placeSourceOnFirstAvailableTarget, getTimeoutByTargetIndex() + 100 );
        }

        function placeSourceOnUserAssignedTarget(){

            var target;
            if(sourceState.hasValidTargetId() && sourceState.isDroppedCorrectly() ) {
                target = targetRegistry.getTargetById( sourceState.getTargetId() );
                if( target ) {
                    setTimeout( function(){ target.setAsBestPracticeSolution(self); }, getTimeoutByTargetIndex() );
                }
                return true;
            }

            return false;
        }

        function placeSourceOnFirstAvailableTarget(){

		    var placed = false,
                target;

		    jq.each( destinationTargetIds, function( index, targetId ){

                target = targetRegistry.getTargetById( targetId  );
                if( target && isCorrectAssignment( target ) && target.setAsBestPracticeSolution( self ) ){
                    placed = true;
                    return false; // jquery.each break
                }

                return true; // jquery.each continue
            });

            if( !placed ){
                moveToStartPosition();
            }
        }

		function isCorrectAssignment( target ) {

			var correctAssigned = false;
			jq.each( destinationTargetIds, function( index, id ){
				if( id === target.getTargetId() ) {
					correctAssigned = true;
					return false; // jquery.each break
				}

                return true; // jquery.each continue
			});

			return correctAssigned;
		}

        function initializeTestRun() {
            sourceObject.resetStyle();
            bindEvents();
            quizState.changeToTestRun();
   		}

        function initializeStandardPlayer(){
            initializeTestRun();
        }

        function cleanupStandardPlayer(){
            moveToStartPosition(true);
            resetStates();
        }

        function initializeHTML5Player(){

            sourceState.restore();
            quizState.restore();

            if( isEvaluated() ){
                return;
            }

            if( context.page.quiz.hasQuestion() ) {
                initializeTestRun();
                checkAndNotifyTargetOnDrop();
            }
        }

        return {
			// declare public members
            onLoading : createComponent,
			onEnterPage : function() {
                if( isStandardPlayer ) {
                    resetStates();
                }
            },
			onAllComponentsReady : function(){
				targetRegistry.initialize();
                isStandardPlayer ? initializeStandardPlayer() : initializeHTML5Player();
			},
			onExitPage : function() {
				removeEvents();
                if( isStandardPlayer ) {
                    cleanupStandardPlayer();
                }
			},
			isCorrectAssignment : isCorrectAssignment,
			onPositionOnTargetChanged : moveToTargetPosition,
			onUserSolution : onUserSolution,
			onBestPracticeSolution : onBestPracticeSolution,
			getId : function() {
				return metadata.getId();
			},
			getHeight : function() {
				return metadata.getHeight();
			}
		};
	};
});
