/**
	ElementMover - controller for moving HTML elements.
	
	To be used in combination with Thread. The idea is that the ElementMover is a
	runnable object for some thread, that lets it do a small step repeatedly, until
	the mover is satisfied and ends the thread.
	
	Usage:
	
	var mover = new ElementMover(element, xdest, ydest, speedPixelsPerSecond, optionalEventListener);
	var thread = new IntervalThread(mover, refreshInterval);
	thread.start();
	
	or simply
	
	ElementMover.move(element, xdest, ydest, speedPixelsPerSecond, optionalEventListener);
	
	@param element the HTML element to be moved (in a straight line)
	@param x the destination x (left) coordinate
	@param y the destination y (top) coordinate
	@param speed the number of pixels per second (REMIND: does this really work?)
	@param eventListener optional eventlistener for letting you know what's going
		on during the movement.
	
	@version 0.1 Testing and development.
	@created 2006-04-01
	@author Jan Willem Nienhuis / Brikkelt Webprogramming
*/
function ElementMover(element, x, y, speed, eventListener){
	this.dest = {x: x, y: y};
	this.speed = speed;
	this.element = element;
	this.eventListener = eventListener;
	this.state = 'atrest';
	this.moving = false;
}
ElementMover.prototype.run = function(thread){
	try {
		// Each run, we may throw an event that notifies about changes in the movement:
		// atrest, accellerating, atspeed, decellerating, stopped.
		var stateChanged = false;
		if (thread.nrOfInvokations == 1){
			this.pixelsPerStep = Math.round(this.speed * thread.interval / 1000); // (pixels / sec.) * (millis / step) / (millis / sec.)
		}
		
		var currentPosition = {x: this.element.offsetLeft, y: this.element.offsetTop};
		var xDistance = Math.abs(currentPosition.x - this.dest.x);
		var yDistance = Math.abs(currentPosition.y - this.dest.y);
		var distance = Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
		var accellerationSteps = 300 / thread.interval; // millis / (millis per step) = steps te gebruiken voor versnellen en vertragen.
		
		var scrollPixels = this.pixelsPerStep;
		var stepEstimate = Math.ceil(distance / this.pixelsPerStep);

		// Determine if we have to accellerate of decellerate.
		if (thread.nrOfInvokations <= accellerationSteps){
			// Accelleration phase.
			scrollPixels = Math.ceil(this.pixelsPerStep / accellerationSteps * thread.nrOfInvokations);
			if (this.state != 'accellerating'){
				this.state = 'accellerating';
				stateChanged = true;
			}
		}else if (stepEstimate <= accellerationSteps){
			// Decelleration phase
			scrollPixels = Math.ceil(this.pixelsPerStep / accellerationSteps * stepEstimate);
			if (this.state != 'decellerating'){
				this.state = 'decellerating';
				stateChanged = true;
			}
		}else if (this.state != 'atspeed'){
			// Main phase
			this.state = 'atspeed';
			stateChanged = true;
		}

		if (distance <= scrollPixels){
			this.moving = false;
			this.state = 'stopped';
			stateChanged = true;
			var newXpos = this.dest.x;
			var newYpos = this.dest.y;
			thread.terminate();
		}else{
			var xDirection = ((this.dest.x - currentPosition.x > 0)?1:-1);
			var yDirection = ((this.dest.y - currentPosition.y > 0)?1:-1);
			var newXpos = currentPosition.x + xDirection * Math.round(scrollPixels * xDistance / distance);
			var newYpos = currentPosition.y + yDirection * Math.round(scrollPixels * yDistance / distance);
		}

		// Adjust the element's position.
		this.element.style.left = newXpos + 'px';
		this.element.style.top = newYpos + 'px';
		
		// Throw any events
		if (this.eventListener != null){
			// Always throw the 'move' event, because even if we stopped, we also moved.
			var moveevent = {id: 'move', x: newXpos, y: newYpos, moving: this.moving};
			this.eventListener(moveevent);
			// If the movement has changed state, tell them about it.		
			if (stateChanged){
				var stateevent = {id: 'statechange', state: this.state, x: newXpos, y: newYpos, moving: this.moving};
				this.eventListener(stateevent);
			}
		}
	}catch(e){
		// Mogelijke Exception: persmission denied. Dit gebeurt wanneer de module op een ander
		// domein draait dan de content pagina die we proberen te scrollen.
		thread.terminate();
		Debug.write('Move thread terminated: ' + e.description);
	}
}
/**
	move - convenience class method.
	
	@return the started Thread object that controls the movement.
*/
ElementMover.move = function(element, x, y, speed, eventListener, interval){
	if (interval == null){interval = 20;}
	var mover = new ElementMover(element, x, y, speed, eventListener);
	var moveThread = new IntervalThread(mover, interval);
	moveThread.start();
	return moveThread;
}

/**
	MultiMover - bundles multiple movements in one Thread.
*/
function MultiMover (){
	this.movers = new Array();
	this.eventListener = null;
	this.activeMovers = 0;
}
MultiMover.prototype.run = function (thread){
	for (var i=0; i<this.movers.length; i++){
		if (this.movers[i].state != 'stopped'){
			this.movers[i].run(thread);
		}
	}
	if (this.eventListener != null){
		var event = {id: 'move', source: this, moving: (this.activeMovers > 0)};
		this.eventListener(event);
	}
}
MultiMover.prototype.addMove = function (element, x, y, speed, eventListener){
	var elementMover = new ElementMover(element, x, y, speed, eventListener);
	this.addElementMover(elementMover);
}
MultiMover.prototype.addElementMover = function (elementMover){
	this.movers[this.movers.length] = elementMover;
}
/**
	move - start the ElementMovers in this MultiMover.
	
	Passes an adjusted thread to the ElementMovers, that keeps track of
	which of the movers has terminated in the meantime. Only if all have 
	terminated, the thread itself will also terminated.
*/
MultiMover.prototype.move = function (eventListener, interval){
	this.eventListener = eventListener;
	if (interval == null){interval = 20;}
	var moveThread = new IntervalThread(this, interval);
	// Change the thread's terminate behaviour, so that it only really terminats
	// when all movers have called terminate().
	moveThread.finalTerminate = moveThread.terminate;
	moveThread.terminate = function (){
		this.runnable.activeMovers--; // The runnable is the MultiMover, not the ElementMovers.
		if (this.runnable.activeMovers == 0){
			this.finalTerminate();
		}
	};
	this.activeMovers = this.movers.length;
	moveThread.start();
	return moveThread;
}