/**
 * @author Patrick Kopp
 */

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CIRCLE ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyCircle.prototype = new CircleBasedStrategy;
StrategyCircle.prototype.STRATEGYNAME = "Circle - Strategy";

function StrategyCircle(steps, center, radius){
	this.base = CircleBasedStrategy;
  	this.base(steps, center, radius);

	/**
	 * Computes positions of amountNewItems items in inSteps steps.
	 * @param {Number} amountNewItems	the amount of new item positions to compute
	 * @param {Number} inSteps	the frame offset
	 */
	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = this.getBaseCoordsForFutureItems(amountNewItems, inSteps);
		for(var i = 0; i < positions.length; i++){
			positions[i] = positions[i].add(this.center);
		}
    	return positions;
  	}

	this.moveItems = function(){
		var baseCoords = this.getBaseCoords();
		for(var i = 0; i < this.items.length; i++){
			this.items[i].setPosition( baseCoords[i].add(this.center) );
		}
	}

	this.perform = function(){
		this.moveItems();
		this._nextFrame();
	}

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TWISTER ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyTwister.prototype = new CircleBasedStrategy;
StrategyTwister.prototype.STRATEGYNAME = "Twister Strategy";

function StrategyTwister(steps, center, radius, angularSpeed){
	this.base = CircleBasedStrategy;
  	this.base(steps, center, radius);

	this.angularSpeed = angularSpeed;
  	var alpha = 0;

	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = this.getBaseCoordsForFutureItems(amountNewItems, inSteps);
		var ownAlpha = alpha + inSteps * ( 1 / this.angularSpeed / this.getSteps() );
		var tmp = Math.cos(ownAlpha * this.angularSpeed);
		for(var i = 0; i < positions.length; i++){
			var x = positions[i].x * tmp + this.center.x;
			var y = positions[i].y * tmp + this.center.y;
			positions[i] = new Point(x, y);
		}
		return positions;
	}

	this._DEBUG_ = false;

	this.moveItems = function(){
		var baseCoords = this.getBaseCoords();
		var tmp = Math.cos(alpha);

		for(var i = 0; i < this.items.length; i++){
			var x = baseCoords[i].x * tmp + this.center.x;
			var y = baseCoords[i].y * tmp + this.center.y;

			this.items[i].setPosition( new Point(x, y) );
		}
		alpha = this.getIncrementedAlpha(alpha, this.angularSpeed, alpha);
		
		if(this._DEBUG_){
		console.log("Alpha is: " + alpha + ", angularSpeed is: " + this.angularSpeed + ", temp is: " + tmp);
		}
	}

	this.perform = function(){
		this.moveItems();
		this._nextFrame();
	}

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ROTATION-X ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyRotationX.prototype = new CircleBasedStrategy;
StrategyRotationX.prototype.STRATEGYNAME = "Rotation - X Strategy";

function StrategyRotationX(steps, center, radius, angularSpeed){

	this.base = CircleBasedStrategy;
  	this.base(steps, center, radius);

  	var angularSpeed = angularSpeed;
  	var alpha = 0;

	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = this.getBaseCoordsForFutureItems(amountNewItems, inSteps);
		var ownAlpha = alpha + inSteps * ( 1 / angularSpeed / this.getSteps() );
		for(var i = 0; i < positions.length; i++){
			var x = positions[i].x * Math.cos(ownAlpha * angularSpeed) + this.center.x;
			var y = positions[i].y + this.center.y;
			positions[i] = new Point(x, y);
		}
    	return positions;
  	}

	this.moveItems = function(){
		var baseCoords = this.getBaseCoords();
		var tmp = alpha * angularSpeed;
		for(var i = 0; i < this.items.length; i++){
			var x = baseCoords[i].x * Math.cos(tmp) + this.center.x;
			var y = baseCoords[i].y + this.center.y;

			this.items[i].setPosition( new Point(x, y) );
		}
		alpha = this.getIncrementedAlpha(alpha, angularSpeed, tmp);
	}

	this.perform = function(){
		this.moveItems();
		this._nextFrame();
	}
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ROTATION-Y ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyRotationY.prototype = new CircleBasedStrategy;
StrategyRotationY.prototype.STRATEGYNAME = "Rotation - Y Strategy";

function StrategyRotationY(steps, center, radius, angularSpeed){

	this.base = CircleBasedStrategy;
	this.base(steps, center, radius);

	var angularSpeed = angularSpeed;
	var alpha = 0;

	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = this.getBaseCoordsForFutureItems(amountNewItems, inSteps);
		var ownAlpha = alpha + inSteps * ( 1 / angularSpeed / this.getSteps() );
		var tmp = ownAlpha * angularSpeed;
		for(var i = 0; i < positions.length; i++){
			var x = positions[i].x + this.center.x;
			var y = positions[i].y * Math.cos(tmp) + this.center.y;
			positions[i] = new Point(x, y);
		}
    	return positions;
  	}

	this.moveItems = function(){
		var baseCoords = this.getBaseCoords();
		var tmp = alpha * angularSpeed;
		for(var i = 0; i < this.items.length; i++){
			var x = baseCoords[i].x + this.center.x;
			var y = baseCoords[i].y * Math.cos(tmp) + this.center.y;

			this.items[i].setPosition( new Point(x, y) );
		}
		alpha = this.getIncrementedAlpha(alpha, angularSpeed, tmp);
	}

	this.perform = function(){
		this.moveItems();
		this._nextFrame();
	}

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HORIZONTAL-HOP ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyHorizontalHop.prototype = new CircleBasedStrategy;
StrategyHorizontalHop.prototype.STRATEGYNAME = "Horizontal Hop Strategy";

function StrategyHorizontalHop(steps, center, radius, angularSpeed){

	this.base = CircleBasedStrategy;
	this.base(steps, center, radius);

	var angularSpeed = angularSpeed;
	var alpha = 0;

	var direction = 1;  // sliding direction: 1-left&right, 2-one side
	var isLeft = false;

	/**
	 * if oneSide is true it will hop just to one side.
	 * @param {Boolean} isOneSide
	 */
	this.setHopToBothSides = function(isOneSide){
		if(isOneSide){ direction = 2; }
		else{ direction = 1; }
	}

	/**
   	 * if true it will hop only to the left otherwise to the right.
   	 * @param {Boolean} isLeft
	 */
	this.setHoppingSideLeft = function(sideIsLeft){
		isLeft = sideIsLeft;
	}

	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = this.getBaseCoordsForFutureItems(amountNewItems, inSteps);
		var ownAlpha = alpha + inSteps * ( 1 / angularSpeed / this.getSteps() );
		for(var i = 0; i < positions.length; i++){
			var tmp = ( (this.center.x + positions[i].x - this.radius)
            			* Math.pow( Math.cos(ownAlpha * angularSpeed), direction) );
			var x = this.center.x;
	      	if(isLeft && this.direction != 1){ x -= tmp; }
			else{ x += tmp; }
			var y = positions[i].y + this.center.y;
			positions[i] = new Point(x, y);
		}
    	return positions;
  	}

	this.moveItems = function(){
		var baseCoords = this.getBaseCoords();
		
		var alphaTimesAng = alpha * angularSpeed;
		
		for(var i = 0; i < this.items.length; i++){
			var tmp = ( (this.center.x + baseCoords[i].x - this.radius)
            			* Math.pow( Math.cos(alphaTimesAng), direction) );
			var x = this.center.x;
	      	if(isLeft && this.direction != 1){ x -= tmp; }
			else{ x += tmp; }
		  	var y = baseCoords[i].y + this.center.y;
			this.items[i].setPosition(new Point(x, y));
		}
		alpha = this.getIncrementedAlpha(alpha, angularSpeed, alphaTimesAng);
	}

	this.perform = function(){
		this.moveItems();
		this._nextFrame();
	}

}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ VERTICAL-HOP ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyVerticalHop.prototype = new CircleBasedStrategy;
StrategyVerticalHop.prototype.STRATEGYNAME = "Vertical Hop Strategy";

function StrategyVerticalHop(steps, center, radius, angularSpeed){

	this.base = CircleBasedStrategy;
	this.base(steps, center, radius);

	var angularSpeed = angularSpeed;
	var alpha = 0;

	var direction = 1;
  	var isUp = false;

	this.setHopToBothSides = function(isOneSide){
		if(isOneSide){ direction = 2; }
		else { direction = 1; }
  	}

	this.setHoppingSideUp = function(sideIsUp){
	  	isUp = sideIsUp;
	}

	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = this.getBaseCoordsForFutureItems(amountNewItems, inSteps);
		var ownAlpha = alpha + inSteps * ( 1 / angularSpeed / this.getSteps() );
		for(var i = 0; i < positions.length; i++){
			var tmp = ( (this.center.y + positions[i].y - this.radius)
            			* Math.pow( Math.cos(ownAlpha * angularSpeed), direction) );
			var y = this.center.y;
	      	if(isUp && this.direction != 1){ y -= tmp; }
			else{ y += tmp; }
			var x = positions[i].x + this.center.x;
			positions[i] = new Point(x, y);
		}
    	return positions;
  	}

	this.moveItems = function(){
		var baseCoords = this.getBaseCoords();
		var alphaTimesAng = alpha * angularSpeed;
		for(var i = 0; i < this.items.length; i++){
			var tmp = ( (this.center.y + baseCoords[i].y - this.radius)
            			* Math.pow( Math.cos(alphaTimesAng), direction) );
			var y = this.center.y;
	      	if(isUp && this.direction != 1){ y -= tmp; }
			else{ y += tmp; }
		  	var x = baseCoords[i].x + this.center.x;
			this.items[i].setPosition(new Point(x, y));
		}
		alpha = this.getIncrementedAlpha(alpha, angularSpeed, alphaTimesAng);
	}

	this.perform = function(){
		this.moveItems();
		this._nextFrame();
	}

}


// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ LINE +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyLine.prototype = new ItemSingleStrategy;
StrategyLine.prototype.STRATEGYNAME = "Simple Line Strategy";

function StrategyLine(item, startPoint, targetPoint, steps) {

	this.base = ItemSingleStrategy;
	this.base(item, steps);

	this.start = startPoint;
	this.end = targetPoint;

	this.totDistance = this.start.getDistance(this.end);;
  	this.alpha = Math.acos(Math.abs(this.start.x - this.end.x) / this.totDistance);
  	if(isNaN(this.alpha)) {
  		this.alpha = 0;
  	}

  	this.xMultiplier = 1;
  	this.yMultiplier = 1;

	this.getFuturePoints = function(amountNewItems, inSteps){
		var positions = new Array();
		for(var i = 0; i < amountNewItems; i++){
			positions[i] = this.start;
		}
		return positions;
	}

	this.moveItem = function(){
		var position = _getPointOnHypotenuse(this.getFrame(), this.getSteps(), this.alpha, this.totDistance, this.xMultiplier, this.yMultiplier);
	  	this.item.setPosition( this.start.add(position) );
	}

	this.perform = function(){
		this.moveItem();
		this._nextFrame();
	}

	this.toString = function(){
		return "Strategy: " + this.STRATEGYNAME + this.getStringBase() + this.getStringFrame() + this.getStringItem() + " line: " + this.start + " - " + this.end + "\n";
	}

// ++++++++++++++++++++++++++++++++++++
// +++++++++++++ PRIVATES +++++++++++++
// ++++++++++++++++++++++++++++++++++++

	function _getPointOnHypotenuse(frame, steps, alpha, distance, xMult, yMult) {
		var hypotenuse = frame * distance / steps;

    	var y = Math.sin(alpha) * hypotenuse * yMult;
		var x = Math.cos(alpha) * hypotenuse * xMult;

		if(isNaN(x) || isNaN(y)) {
			//throw new Error("This shouldn't happen - " + "x: " + x + ", y: " + y);
			return new Point(0, 0);
		}

		return new Point(x, y);
	}

	this._computeTemps = function(){

  		var position = this.item.getPosition();
  		this.totDistance = this.start.getDistance(this.end);
  		if(this.totDistance == 0) {
     		this.running = false;
     		this.fullStop();
     		return;
  		}
  		this.alpha = Math.acos(Math.abs(this.start.x - this.end.x) / this.totDistance);
//  		if(isNaN(this.alpha)){
//  			this.alpha = 0;
//  		}
  		if(this.start.x > this.end.x) this.xMultiplier = -1;
  		if(this.start.y > this.end.y) this.yMultiplier = -1;
	}

	this._computeTemps();
	this.setLoopsToDo(1);
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ POLYGON +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyPolygon.prototype = new ItemSingleStrategy;
StrategyPolygon.prototype.STRATEGYNAME = "Simple Polygon Strategy";
/**
 * Item will move to first point of polygon and then move to all other points given.
 * @param {ImageItem} item - the item to move
 * @param {[Point || Strategy]} points - they make up the polygon, or / and strategies that stop themself somehow (if they are endless, no following movements get executed)
 * @param {bool} loopEndless - if false: animation will stop when reached the last point. Otherwise the animation will start again at first point given.
 * @param {Number} steps - time in frames to do one loop, is optional (default is 100) you can also use setStepsPerSegment()
 */
function StrategyPolygon(item, points, loopEndless, steps) {

	this.base = ItemSingleStrategy;
	this.base(item, steps);

	this.actLine;
	this.isLoop = false;

	var steps = steps ? steps : 100;

	var firstLineOnPoly;
	var distance = 0;
	var stepsPerDistance = 0;

	this.moveItem = function(){
		if(!this.actLine.isDone()){
			this.actLine.perform();
		} else {
			if(this.actLine.getNextStrategy() != null){
				this.actLine = this.actLine.getNextStrategy();
				this.actLine.resetDone();
			}
		}
	}

	this.isDone = function(){
		if(this.isLoop) return false;
		else return (this.actLine.getNextStrategy() == null && this.actLine.isDone());
	}

	this.perform = function(){
		this.moveItem();
		this._nextFrame();
	}

	/**
	 * Here you may define individual steps for each segment of this polygon.
	 * You are allowed to give less steps than poly has lines. Then only the first n lines will have non-default steps.
	 * @param {[Number]} stepsArray - an array of steps with same length as points of this polygon
	 */
	this.setStepsPerSegment = function(stepsArray){

		var lineToAlter = firstLineOnPoly;
		for(var i = 0; i < stepsArray.length; i ++){
			if(lineToAlter != null){
				lineToAlter.setSteps(stepsArray[i]);
				lineToAlter = lineToAlter.getNextStrategy();

			} else {
				throw new Error("setStepsPerSegment(stepsArray): given array is too long!");
			}
		}
	}

	this.strategyChainToString = function(){
		var str = "";
		var line = firstLineOnPoly;
		while(line.getNextStrategy() != null){
			str += "Move: " + line.start + " -> " + line.end + " @steps:" + line.getSteps() + "\n";
			line = line.getNextStrategy();
		}
		str += "Move: " + line.start + " -> " + line.end + " @steps:" + line.getSteps() + "\n";
		if(line.getNextStrategy() == firstLineOnPoly){
			str += " --> back to first, do loop!\n";
		}
		return str;
	}

	this.toString = function(){
		return "chain of points:\n" + " with total distance: " + distance + "\n" + this.strategyChainToString();
	}

    // PRIVATE:
	this._computePolygonDistance = function(){
		distance = 0;
        var realPoints = new Array();
        for(var i = 0; i < points.length; i++){
           if(points[i] instanceof Point){
               realPoints.push(points[i]);
           }
        }

		for(var i = 1; i < realPoints.length; i++){
	        distance += realPoints[i-1].getDistance(realPoints[i]);
		}
		if(this.isLoop){
			distance += realPoints[realPoints.length - 1].getDistance(realPoints[0]);
		}
		stepsPerDistance = steps / distance;
	}

    this._generateLines = function(points){

        var amount = points.length;
        var actPoint = this.item.getPosition();
        var strategies = new Array();
        for(var i = 0; i < amount; i++){
            if(points[i] instanceof Point){
                var distOfSegment = actPoint.getDistance(points[i]);
                var strategy = new StrategyLine(item, actPoint, points[i], Math.round(stepsPerDistance * distOfSegment));
    			strategies.push(strategy);
                actPoint = points[i];
            } else if(points[i].perform instanceof Function){
                strategies.push(points[i]);
            } else
                throw new Error("must put Point's or Strategies as points in constructor, " + points[i] +" is illegal");
        }
        if(points[points.length-1] instanceof Point){
		    strategies.push(new StrategyLine(item, actPoint, points[points.length-1], Math.round(stepsPerDistance * distOfSegment)));
        } else {
            strategies.push(points[points.length-1]);
        }
        this.actLine = strategies.shift();
		this._attachPolyLines(strategies);
    }

	this._attachPolyLines = function(strategies){
		var act = this.actLine;
		for(var i = 0; i < strategies.length; i++){
			var nextLine = strategies[i];
			if(i == 0) {
				firstLineOnPoly = nextLine;
			}
			act.setNextStrategy(nextLine);
			act = nextLine;
		}
	}

	this._computePolygonDistance();
	this._generateLines(points);
}

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ PAUSE +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
StrategyPause.prototype = new ItemSingleStrategy;
StrategyPause.prototype.STRATEGYNAME = "Simple PAUSE Strategy";
/**
 * Item will do nothing for the next n steps.
 * @param {ImageItem} item - the item to move
 * @param {Number} steps - time in frames to pause
 * @param {Function} funcOnBegin - a pointer to a function invoked at beginning of this strategy
 * @param {Function} funcOnEnd - a pointer to a function invoked at the end of this strategy
 */
function StrategyPause(item, steps, funcOnBegin, funcOnEnd) {
	
	this.base = ItemSingleStrategy;
	this.base(item, steps);
	this.setLoopsToDo(1);

    this.onStart = funcOnBegin;
    this.onEnd = funcOnEnd;
        

    this.hasNothingToDo = function(){
        return false;
    }

    this.didOnStart = false;
    this.didOnEnd = false;

	this.perform = function(){

        if(!this.didOnStart){
        	if(this.onStart && this.onStart instanceof Function){
            	this.onStart();
            }
            this.didOnStart = true;
        }
        
        this._nextFrame();
        
        if(this.isDone()){
	        if(!this.didOnEnd){
	        	if(this.onEnd && this.onEnd instanceof Function){
                	this.onEnd();
                }
                this.didOnEnd = true;
	        }
        }
	}
	this.reset = function(){
	 this.didOnStart = false;
	 this.didOnEnd = false;
	 this.resetDone();
	 this.resume();
	}
}
StrategyMoveTo.prototype = new ItemSingleStrategy;
StrategyMoveTo.prototype.STRATEGYNAME = "MOVETO Strategy";
function StrategyMoveTo(item, targetPoint, isTargetAbsolute, steps) {
	
	this.base = ItemSingleStrategy;
	this.base(item, steps);
	
	this.target = targetPoint;
	this.isTargetAbsolute = isTargetAbsolute;	// if not the target is computed relative to items current point (current means the time when perform is invoked the first time)
	this._nextHolder = null;
	
	this.perform = function(){
		var destinationP = this._getDestinationPoint();
		this.line = new StrategyLine(this.item, this.item.getPosition(), destinationP, this.getSteps());
		if(this._nextHolder != null){
		 this.line.setNextStrategy(this._nextHolder);
		 this._nextHolder = null;
		}
	}
	
	this._getDestinationPoint = function(){
	  if(isTargetAbsolute){
	    return this.target;
	  } else {
	    this.target = this.item.getPosition().add(this.target);
	  }
	  return this.target;
	}
	
	this.isDone = function(){
		return this.line != null;
	}
	
	this.setNextStrategy = function(strategy){
		this._nextHolder = strategy;
	}
	this.getNextStrategy = function(){
	 	if(this.line == null){
		 return this._nextHolder;
		} else {
		 var result = this.line;
		 this.line = null;
		 return result;
		}
	}
	this.hasNothingToDo = function(){
		return false;
	}
}

StrategyChain.prototype = new StrategyBase;
StrategyChain.prototype.STRATEGYNAME = "Strategy Chain";
function StrategyChain( strategies ){ 
	
	this.base = StrategyBase;
	this.base();
	
	this.strategies = strategies;
	this.currentStrategy = this.strategies.shift();  
	
	this.perform = function(){
		if(!this.currentStrategy.isDone()){
			this.currentStrategy.perform();
		} else { 
		// current is done
		var next = this.currentStrategy.getNextStrategy();
		var hasNext = next != null
		if(!hasNext && this.strategies.length > 0){
			next = this.strategies.shift();
			hasNext = true;
		} 
		if(!hasNext){
			this.fullStop();
			return;
		}
		var items = this.currentStrategy.removeAllItems();
		next.setItems(items);
		next.reset();
		
		this.currentStrategy = next;
		}
	}
	
	this.removeAllItems = function(){
	 return this.currentStrategy.removeAllItems();
	}
	
	this.hasNothingToDo = function(){
	 return this.currentStrategy.hasNothingToDo();
	}
}















