if (!window.nDynamic) alert('Require nDynamic library.');

/**
 * Callbacks order:
 * 		public		onInitialize
 * 		protected	beforeActivate
 * 		public		onActivate
 * 		public	{	onLoop
 *		protected	onEndDetection
 * 		public		onUpdate	}
 * 		public 		onDeactivate
 */
function nAction(options) {
	this.nName = 'nAction';

	this.setOptions = function(options) {
		if (options) {
			// Ustawianie konfiguracji:
			for (option in options) {
				this[option] = options[option];
			}
		}
	}
	
	this.activate = function(options) {
		nActionLoop.enqueueAction(this);
		if (this.beforeActivate) {
			this.beforeActivate(options); // @todo : sprawdzic, czy to moze byc przed enqueueAction i przeniesc
		}
		if (this.onActivate) {
			this.onActivate(options);
		}
		return this;
	}
	
	this.deactivate = function() {
		// @todo : dodac w tym miejscu beforeDeactivate();
		nActionLoop.dequeueAction(this);
		if (this.onDeactivate) {
			this.onDeactivate();
		}
		if (onThan) {
			onThan();
		}
		return this; // @todo : czy to potrzebne?
	}
	
	var onThan = null;
	this.than = function(func) {
		onThan = function() {
			func();
			onThan = null;
		}
	}
	
	this.run = function() {
		if (this.onLoop) {
			this.onLoop();
		}
		if (this.onEndDetection) {
			this.onEndDetection();
		}
		if (this.onUpdate) {
			this.onUpdate();
		}
	}

	
	this.beforeActivate = function() {
		this.detectDirection();
	}
	
	this.onEndDetection = function() {
		if (this.isEnd()) {
			this.deactivate();
		}
	}
	
	
	// konstruktor {
		// public
		this.start = 0;
		this.end = 10;
		this.factor = 0.1;
		// private
		this.direction = 1;
		this.n = this.start;
		
		this.setOptions(options);
		this.actionId = nActionLoop.registerActionId();
		
		if (typeof this.onInitialize == 'function') {
			this.onInitialize();
		}
	// }
	
	
	
	this.detectDirection = function() {
		this.direction = (this.start <= this.end) * 2 - 1;
	}

	
	this.isEnd = function() {
		return (this.n * this.direction >= this.end * this.direction);
	}
	
	
	/**
	 * Ruch jednostajnie opozniony.
	 * Wartosc zalezy od faktora i od dystantu.
	 */
	this.decelerate = function() {
		this.n += Math.ceil(this.direction * (this.end - this.n) * this.factor) * this.direction;
	}
	
	
	/**
	 * Ruch jednostajnie przyspieszony.
	 * Wartosc przyspiszenia zalezy tylko od faktora.
	 */
	this.accelerate = function() {
		this.n += (Math.ceil((this.n - this.start) * this.factor) || this.direction);
	}
	
	
	/**
	 * Ruch jednostajny.
	 * Predkosc zalezy tylko od factora.
	 */
	this.uniform = function() {
		this.n += (Math.ceil(this.factor * 100 * this.direction) || this.direction);
	}
	
	
	/**
	 * Ruch jednostajny.
	 * Predkosc zalezy od factora i od DYSTANSU. Im wiekszy dystans pomiedzy start i end, tym wieksza predkosc.
	 */
	this.distantional = function() {
		this.n += (Math.ceil(this.factor / 10 * (this.end - this.start)) || this.direction);
	}
	

	/**
	 * Ruch zalezny.
	 * Predkosc ruchu zalezy od predkosci ruchu akcji, od ktorej biezaca akcja jest zalezna.
	 * @todo : Dodac do wzoru "direction". Zoptymalizowac.
	 */
	this.depended = function(affectingAction) {
		this.n = Math.ceil((this.end - this.start) / (affectingAction.end - affectingAction.start) * (affectingAction.n - affectingAction.start)) + this.start || this.direction;
//		alert(((this.end - this.start)) + '/' + (affectingAction.end - affectingAction.start) + '*' +  (affectingAction.n - affectingAction.start) + '=' + this.n);
	}
	
	return this;
}


/**
 * Passive animation loop.
 *
 * ====== API: ======
 *
 * Public:
 * 		start([fps])			: Initialize and enable animations (if already started, method executes stop() before start()).
 * 		stop()				: Disable animations and clear actions queue.
 *
 * Protected:
 *		sleep()				: Suspend animations (faster than stop(); does not clear the queue).
 *		wake()				: Unsuspend animations (faster than start()).
 *		nName				: nDynamic object category.
 *
 * Private:
 *		registerActionId()
 *		enqueueAction(action)
 *		dequeueAction(action)
 * 		frame()
 */
function nActionLoop() {
	this.nName = 'nActionLoop';
	
	var frameRate = 30; // =~ 33fps
	var actionAutoIncrement = 0;
	var queue = new Array;
	this.loop = null;


	// Rejestruje akcje = przydziela jej identyfikator, niezbedny do dalszej pracy z akcja.
	this.registerActionId = function() {
		return actionAutoIncrement++;
	}
	
	this.enqueueAction = function(action) {
		queue[action.actionId] = action;
		if (this.loop == null) this.wake();
	}
	
	this.dequeueAction = function(action) {
		queue[action.actionId] = null;
	}
	
	// Initialize loop.
	this.start = function(fps) {
		if (this.loop) {
			this.stop();
		}
		
		if (fps) {
			frameRate = Math.round(1000/fps);
		}
		
		this.wake();
	}

	// Wake up loop.
	this.wake = function() {
		this.loop =	setInterval(
			function() {
				nActionLoop.frame();
			},
			frameRate);
	}
	
	// Stop loop and clear queue.
	this.stop = function() {
		this.sleep();
		this.clearQueue();
	}
	
	// Asleep loop.
	this.sleep = function() {
		clearInterval(this.loop);
		this.loop = null;
	}
	
	this.clearQueue = function() {
		var queueLength = queue.length;
		for (var i in queue) {
			delete queue[i];
		}
	}
	

	// Execute all enqueued actions "frameRate" times per second.
	this.frame = function() {
		// @todo : zoptymalizowac przez uzycie listy zamiast tablicy akcji
		var amount = 0;
		for (var id in queue) {
			if (queue[id]) {
				queue[id].run();
				amount++;
			}
		}
		if (amount == 0) this.sleep();	// @todo : Czy w tym miejscu kolejka tez nie powinna byc czyszczona? (stop() zamiast sleep()).
	}
}

var nActionLoop = new nActionLoop();