'remmah'.namespace();
Function.requires('remmah.object.Color');

remmah.Morph = function(id)
{
	this.id = (id) ? id : new Date().getTime();
	this.state = remmah.state.WAIT;
	this.queue = [];
	this.transition = function(pos){ return -(Math.cos(Math.PI*pos) - 1)/2; };
	this.duration	= 1000;
	this.delay	= 30;

	this.begin	= 0;
	this.time	= 0;
	this.pauseStart	= 0;
	this.pauseEnd	= 0;
	this.timer	= null;

	if (remmah.Debugger)
		this.debugConsole = new remmah.Debugger(document.getElementById('debugConsole'));
};
remmah.Morph.testedIn = [
		'Firefox v.3.5.7',
		'Chrome v4.1.249.1064 (45376)',
		'Safari v4.0.5',
		'Opera v10.53 b3374',
		'IE v6'
];
var proto = remmah.Morph.prototype;
proto.toString = function()
{
	return 'remmah.Morph';
};
proto.add = function(morphObject)
{
	if (morphObject.toString().indexOf('remmah.Morph.Object') != -1)
	{
		if (this.state == remmah.state.RUN)
			morphObject.begin = new Date().getTime();
		this.queue.push(morphObject);
	}
	else
		alert('Error in remmah.Morph.add(): Cannot add non Morph.Object');
};
proto.find = function(morphObjectId)
{
	for (var i = 0; i < this.queue.length; i++)
	{
		if (this.queue[i].element.id == morphObjectId)
			return this.queue[i];
	}
	return null;
}
proto.remove = function(morphObject)
{
	var length = this.queue.length;
	this.queue.remove(morphObject);
	if (length != this.queue.length)
		this.debug(morphObject.toString() + ' removed from queue, queue length (' + this.queue.length + ')');
	else
		this.debug(morphObject.toString() + ' wasn\'t in queue, queue length (' + this.queue.length + ')', '#f00');
};
proto.resetObjects = function()
{
	//for (var i=0; i<this.queue.length; i++)
	//	this.queue[i].reset();
	var i = this.queue.length-1;
	do
	{
		this.queue[i].reset();
	}
	while (i--);
}
proto.start = function()
{
	this.state = remmah.state.RUN;
	this.resetObjects();
	this.begin	= new Date().getTime();
	this.timer	= window.clearInterval(this.timer);
	this.timer 	= window.setInterval(this.run.bind(this), this.delay);	
};
proto.reverse = function()
{
	for (var i=0; i<this.queue.length; i++)
	{
		var mo = this.queue[i];
		mo.duration = this.newDuration;
		var styles = mo.styles;
		var tmp = styles.map(function(style){
			if (style.currentState)
			{
				style.to = style.currentState;
				mo.styleFinished(style);
			}
		});
	}
	this.start();
}
proto.pause = function()
{
	if (this.state != remmah.state.PAUSE)
		this.pauseStart = new Date().getTime();
	else
		this.pauseEnd = new Date().getTime();

	this.state = (this.state == remmah.state.PAUSE) ? remmah.state.RUN : remmah.state.PAUSE;
	for (var i = 0; i < this.queue.length; i++)
		this.queue[i].state = this.state;

	if (this.state == remmah.state.RUN)
		this.begin = this.begin + (this.pauseEnd - this.pauseStart);

	this.debug('State changed:' + this.state + ' ' + this.begin);
	return this.state;
};
proto.stop = function()
{
	this.state = remmah.state.STOP;
	//this.debug('Animation stoped');
	this.timer = window.clearInterval(this.timer);
	if (this.onEnd)
		this.onEnd();
};
proto.run = function()
{
	if (this.queue.length == 0)
	{
		this.stop();
		return false;
	}

	if (this.state == remmah.state.PAUSE)
		return false;
	
	var text = 'interval is running ' + new Date().getTime() + ' timer(' + this.timer + ':' + this.id + ') queue(' +this.queue.length + ')';
	//this.debug(text);
	var i = this.queue.length-1;
	do
	{
		var morphObject = this.queue[i];
		if (morphObject == null)
			continue;

		var styles = morphObject.styles;

		if (morphObject.state == remmah.state.DONE || styles.length == 0)
		{
			if (morphObject.onEnd)
				morphObject.onEnd();
			continue;
		}
		
		if (morphObject.state != remmah.state.RUN)
			continue;

		var transition	= (morphObject.transition) ? morphObject.transition : this.transition;
		var duration	= (morphObject.duration)   ? morphObject.duration   : this.duration;
		var begin	= (morphObject.begin)      ? morphObject.begin      : this.begin;
		
		if (styles.length == 0)
		{
			// don't know how this can happen, but it does!! :S
			this.debug('FATAL ERROR: styles.length:' + styles.length + ' - index:' + i + ', length:'+this.queue.length, '#f00');
			continue;
		}

		var j = styles.length-1;
		do
		{
			var s = styles[j];
			if (s.finished)
				continue;

			var to    	= s.to;
			var amount 	= to;
			
			this.time = new Date().getTime();
			var time = (this.time - begin);
			var left = duration - time;
			var Tpos = time / duration;

			this.newDuration = duration - left;

			var t = typeof to;
			switch(t)
			{
				case 'string' :
  				case 'number' :
					amount = transition(Tpos) * (to-s.from) + s.from;
					styles[j].currentState = amount;
					break;
  				default :
					amount = [0,1,2];
					amount = amount.map(function(z){return parseInt( to[z] * Tpos + s.from[z] * (1 - Tpos) );});
					styles[j].currentState = amount;
					amount = '#'+amount.map(remmah.object.Color.dec2hex).join('');
    					break;
			}

			if (left <= 0)
			{
				morphObject.styleFinished(styles[j]);
				amount = to;
			}

			morphObject.step(s.style, amount, s.unit);
			//this.debug(s.toString() + ', left:' + left + ', Tpos:' + Tpos.toFixed(3) + ', amount:' + amount);
		}
		while (j--);
	}
	while (i--);
};
proto.debug = function(text, color)
{
	if (remmah.DEBUG_DISABLED_Morph && remmah.DEBUG_DISABLED_Morph == true)
		return;

	if (this.debugConsole)
		this.debugConsole.writeLine(text, color);
	else
		window.status = text;
};


remmah.Morph.Object = function(element, styles, duration, effectTransition, state)
{
	this.element 	= (typeof element == 'string') ? document.getElementById(element) : element;
	this.duration 	= (duration) ? duration : null;
	this.transition = (typeof effectTransition == 'function') ? effectTransition : null;
	this.state 	= (state) ? state : remmah.state.RUN;

	this.styles 	= [];
	
	
	this.num = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/;
	this.rgb = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
	this.hex = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
	this.opacity = /^alpha\(opacity=\s*(\d+)\s*\)$/i;

	if (remmah.Debugger)
		this.debugConsole = new remmah.Debugger(document.getElementById('debugConsole'));

	this.parseStyles(styles);	

	return this;	
};
var prototype = remmah.Morph.Object.prototype;
prototype.toString = function()
{
	return 'remmah.Morph.Object (' + this.element.id + ')';
};
prototype.parseStyles = function(styles)
{
	styles = styles.split(';');
	for (var i = 0; i < styles.length; i++)
	{
		if (styles[i].length != 0)
		{
			if (styles[i].indexOf(':') != -1)
			{
				var parts = styles[i].split(':');
				var style = parts[0].trim();
				var to = parts[1].trim();

				var so = this.setStyleObject(style, to);
				if (so)
					this.styles.push(so);
			}
			//else
			//	this.styles.push(this.setPath(styles[i]));
		}
	}
};
prototype.updateStyleObject = function(style, property, amount)
{
	for (var i=0; i<this.styles.length; i++)
	{
		if (this.styles[i].style == style)
		{
			this.styles[i][property] = amount;
			break;
		}
	}
};
prototype.setStyleObject = function(style, to)
{
	var value = remmah.DOM.getStyle(this.element, this.getStyleUniform(style));

	if (value && (value == 'auto' || value.indexOf('%') != -1))
	{
		switch(style)
		{
			case 'width':
				value = this.element.offsetWidth + 'px';
				break;
			case 'height':
				value = this.element.offsetHeight + 'px';
				if (this.element.offsetHeight != this.element.clientHeight)
					value = this.element.clientHeight + 'px';
				break;
			case 'left':
			case 'top':
				if (this.element.style.position == 'absolute')
				{
					if (style == 'left')
						value = this.element.offsetLeft;
					else
						value = this.element.offsetTop;
				}
				else
				{
					this.element.style.position = 'relative';
					this.element.style[style] = 0 + 'px';
					value = this.element.style[style];
				}
				break;
			default:
				break;
		}
	}
	
	var isRgb = this.rgb.exec(value);
	var isHex = this.hex.exec(value);
	var isNum = this.num.exec(value);
	var isOpa = this.opacity.exec(value);
	var afrom = '', ato = '', aunit = '';
		
	if (isRgb || isHex)
	{
		afrom = [255,255,255];
		if (isRgb)
			afrom = isRgb.slice(1);
		else if (isHex)
			afrom = this.hexToArray(isHex[1]);
			
		isHex = this.hex.exec(to);
		if (isHex)
			ato = this.hexToArray(isHex[1]);

	}
	else if (isNum)
	{
		afrom = parseFloat(value);
		ato   = parseFloat(to);
		aunit = (isNum[1]) ? isNum[1] : '';
	}
	else if (isOpa)
	{
		afrom = parseFloat(isOpa[1]/100);
		ato   = parseFloat(to);
	}

	var equal = true;
	if (typeof afrom == 'object')
		equal = afrom.compare(ato);
	else
		equal = (afrom == ato);
	
	//if (!equal)
	//{
		var styleObject = new remmah.Morph.StyleObject(style, afrom, ato, aunit)
		//this.debug(this.toString() + ' -> ' + styleObject.toString());
		return styleObject;
	/*}
	else
		return null*/
};
prototype.hexToArray = function(hex)
{
	hexArray = [];
	if (hex.length == 3)
	{
		var t = hex.chunk(1);
		for (var i = 0; i <= 2; i++)
			hexArray.push(remmah.object.Color.hex2dec(t[i]+t[i]));
	}
	else if (hex.length == 6)
		hexArray = hex.chunk(2).map(remmah.object.Color.hex2dec);

	return hexArray;
};
prototype.getStyleUniform = function(style)
{
	var s = this.element.style;
	switch(style)
	{
		case 'padding':
			return 'paddingLeft';
		case 'margin':
			return 'marginLeft';
		case 'borderWidth':
			return 'borderLeftWidth';
		case 'borderColor':
			return 'borderLeftColor';
		case 'opacity':
			if (s.filter == '')
				s.filter = "alpha(opacity=100)";
			return  (s.filter)	? 'filter'	:
				(s.opacity)	? 'opacity'	: 
				(s.MozOpacity)	? 'MozOpacity' 	: 
				(s.KhtmlOpacity)? 'KhtmlOpacity': style;
		default:
			return style;
	}
};
prototype.styleFinished = function(style)
{
	style.finished = true;
	var from = style.from;
	var to = style.to;
	style.from = to;
	style.to = from;

	var count = 0;
	//for (var i=0; i<this.styles.length; i++)
	var i =  this.styles.length-1;
	do
	{
		if (this.styles[i].finished == true)
			count++;
	}
	while (i--);
	if(count == this.styles.length);
		this.state = remmah.state.DONE;

	//this.debug('Style finished: ' + style.toString());
}
prototype.reset =  function()
{
	try
	{
		this.begin = null;
		//for (var i=0; i<this.styles.length; i++)
		var i = this.styles.length-1;
		do
		{
			this.styles[i].finished = false;
			//this.debug('Style resat: ' + this.styles[i].toString());
		}
		while (i--);
		if (this.state != remmah.state.WAIT)
			this.state = remmah.state.RUN;

		//this.debug('Object resat: ' + this.toString());
	}
	catch (err)
	{
		this.debug('Error in object.reset(): ' + err);
	}
}
prototype.step =  function(style, amount, unit)
{
	var s = this.element.style;
	var styles = [];
	var amounts=[];
	switch(style)
	{
		case 'path':
			styles  = ['left', 'top'];
			amounts = [amount[0], amount[1]];
			break;
		case 'scale':
			styles  = ['width', 'height'];
			amounts = [amount[0], amount[1]];
			break;
		case 'margin':
		case 'padding':
			styles  = [style+'Bottom', style+'Top', style+'Left', style+'Right'];
			break;
		case 'borderWidth':
			styles  = ['borderBottomWidth', 'borderTopWidth', 'borderLeftWidth', 'borderRightWidth'];
			break;
		case 'borderColor':
			styles  = ['borderBottomColor', 'borderTopColor', 'borderLeftColor', 'borderRightColor'];
			break;
		case 'opacity':
			if (s.filter)
			{
				s.filter = "alpha(opacity=" + (amount*100) + ")";
				break;
			}
			
			s.opacity = amount; s.MozOpacity = amount; s.KhtmlOpacity = amount;
			break;
		default:
			try
			{
				s[style] = amount + unit;
			}
			catch (err)
			{
				alert(err + ', ' + style + ', ' + amount + ', ' + unit);
			}
			break;
	}
	for (var i=0; i<styles.length; i++)
	{
		amount = (amounts[i]) ? amounts[i] : amount;
		s[styles[i]] = amount + unit;
	}
	//this.debug(this.toString() + ', style:' +style+ ', amount:' +amount+ ', unit:' +unit);
	if (this.onStep)
		this.onStep();
};
prototype.debug = function(text, color)
{
	if (this.debugConsole)
		this.debugConsole.writeLine(text, color);
	else
		window.status = text;
}


remmah.Morph.StyleObject = function(style, from, to, unit)
{
	this.style = style;
	this.from = from;
	this.to = to;
	this.unit = unit;
	this.finished = false;

	return this;	
};
var prototype = remmah.Morph.StyleObject.prototype;
prototype.toString = function()
{
	return 'remmah.Morph.StyleObject [style:' + this.style + '; from:' + this.from + '; to:' + this.to + '; unit:' + this.unit + ', finished:' + this.finished + ']';
};


remmah.state = {
	"WAIT"		:  'wait', 
	"START"		:  'start',
	"RUN"		:  'run', 
	"PAUSE"		:  'pause', 
	"STOP"		:  'stop',
	"DONE"		:  'done',
	"FINISHED"	:  'finished'
};
