/**
 * Animation
 */
JS.Anim = function (node, conf) {
	//Configration
		this.config = JS.extend({
			speed: 500,
			from: {},
			to: {},
			transition: JS.AnimHelpers.Transitions.Linear,
			direction: 1
		}, conf, {
			event: null,
			start_time: null,
			end_time: null,
			props: {},
			id: (new Date()).getTime() + Math.random()
		});
		
		this.config.event = new JS.Event.CustomEvent();
		
	//Node
		this.node = JS.Dom.get(node);
	
	//Functions
		var self = this;
		this.functions = {
			step: function (now) { self._step(now); }
		};
		
	//
		this.prepareProperties();
};

//Node
JS.Anim.prototype.node = null;

//Configuration
JS.Anim.prototype.config = null;

//Functions
JS.Anim.prototype.functions = null;

/**
 * Prepare properties for animation
 */
JS.Anim.prototype.prepareProperties = function () {
	for(var i in this.config.to) {
		if (this.config.to.hasOwnProperty(i) && this.config.from.hasOwnProperty(i)) {
			var unit = this.config.from[i].match(/(px|\%|pt|em)$/);
				unit = (unit && unit.length > 0 ? unit[1] : '');
				
			var from_val = parseFloat(this.config.from[i].match(/(\-?[0-9]+(\.[0-9]+)?)/)); //Match any float or integer number
			var to_val = parseFloat(this.config.to[i].match(/(\-?[0-9]+(\.[0-9]+)?)/)); //Match any float or integer number
			
			if (from_val != to_val) {
				this.config.props[i] = {
					from: from_val,
					to: to_val,
					dif: (to_val - from_val),
					unit: unit
				};
			}
		}
	}
};

/**
 * Do a single step
 */
JS.Anim.prototype._step = function (now) {
	var pos = 0;
	
	if (now >= this.config.end_time) {
		pos = 1;
	} else if (now <= this.config.start_time) {
		pos = 0;
	} else {
		pos = 1 - (this.config.end_time - now) / this.config.speed;
	}
	
	pos = this.config.transition(pos);
	
	if (this.config.direction == -1) {
		pos = 1 - pos;
	}
	
	for(var i in this.config.props) {
		if (this.config.props.hasOwnProperty(i)) {
			
			var p = this.config.props[i];
			var val = 0;
			
			if (pos > 0 && pos < 1) {
				val = p.dif * pos + p.from;
				val = Math.round(val * 100) / 100;
			} else if (pos == 0) {
				val = p.from;
			} else {
				val = p.to;
			}
			
			JS.CSS.set(this.node, i, val + p.unit);
		}
	}
	
	if ((pos == 1 && this.config.direction == 1) || (pos == 0 && this.config.direction == -1)) {
		this.stop();
		this.config.event.fire('onComplete', this);
	}
	
};

/**
 * Start animation
 */
JS.Anim.prototype.start = function (now) {
	if (!now) now = (new Date()).getTime();
	
	this.config.start_time = now;
	this.config.end_time = now + this.config.speed;
	this.config.direction = 1;
	
	JS.AnimTimerManager.add(this.config.id, this.functions.step);
};

/**
 * Start animation in reverse order
 * @param {Object} now
 */
JS.Anim.prototype.startReverse = function (now) {
	if (!now) now = (new Date()).getTime();
	
	this.config.start_time = now;
	this.config.end_time = now + this.config.speed;
	this.config.direction = -1;
	
	JS.AnimTimerManager.add(this.config.id, this.functions.step);
};

/**
 * Stop animation
 */
JS.Anim.prototype.stop = function () {
	JS.AnimTimerManager.remove(this.config.id);
};

/**
 * Stop animation at the end
 */
JS.Anim.prototype.finish = function () {
	now = (new Date()).getTime();
	
	if (now < this.config.end_time) {
		this._step(this.config.end_time + 1);
	}	
};



/**
 * Animation timer manager
 * Using one timer for all animations allows to run simultenious animations smoother
 */
JS.AnimTimerManager = {
	//Callbacks
	callbacks: {},
	callback_count: 0,
	
	//Status, true - running, false - idle
	status: false,
	
	step: 32,
	
	/**
	 * Do a step
	 */
	run: function () {
		if (JS.AnimTimerManager.callback_count == 0) { 
			JS.AnimTimerManager.status = false;
			return;
		}

		var now = (new Date()).getTime();
		for(var i in JS.AnimTimerManager.callbacks) {
			if (JS.AnimTimerManager.callbacks[i]) JS.AnimTimerManager.callbacks[i](now);
		}
		
		setTimeout(arguments.callee, JS.AnimTimerManager.step);
	},
	
	/**
	 * Add callback to the list
	 * 
	 * @param {String} id
	 * @param {Object} callback
	 */
	add: function (id, callback) {
		if (!this.callbacks[id]) {
			this.callback_count++;
		}
		this.callbacks[id] = callback;
		
		if (!this.status) {
			this.status = true;
			this.run();
		}
	},
	
	/**
	 * Remove callback from the list
	 * @param {Object} id
	 */
	remove: function (id) {
		if (this.callbacks[id]) {
			this.callbacks[id] = null;
			this.callback_count--;
		}
	}
};



/**
 * Helper functions
 */
JS.AnimHelpers = {
	Transitions: {
		Linear: function (pos) { return pos; },
		Sinoid: function (pos) { return (-Math.cos(pos*Math.PI)/2) + 0.5; },
		Double: function (pos) { return pos * pos; },
		DoubleReverse: function (pos) { return 1 - (1 - pos) * (1 - pos); }
	}
};

/**
 * CSS helper functions
 */
JS.CSS = {
	set: function (node, property, value) {
		//Apply batch of properties
		if (JS.Object.isObject(property)) {
			for(var i in property)
				if (property.hasOwnProperty(i)) JS.CSS.set(node, i, property[i]);

			return;
		}
		
		//Apply
		if (property == 'opacity' && !JS.Support.opacity) {
			node.style.filter = 'alpha(opacity="' + (Math.round(parseFloat(value) * 100)) + '")';
		} else {
			eval('node.style.' + property + '="' + value + '"');
		}
	}
};

/**
 * Feature support test
 */
JS.Support = {
	opacity: true
};
	JS.Event.addListener(window, 'load', function () {
		var div = document.createElement('DIV');
			div.innerHTML = '<span style="opacity: 0.5; filter: alpha(opacity=50);"></span>';
			
		var span = div.getElementsByTagName('SPAN')[0];
		JS.Support.opacity = (span.style.opacity === '0.5' && span.style.filter != 'alpha(opacity=50)');
	});