/*
 * LIBRARY D (CIA: custom, animation)
 * --------------------------------------- start ----------------------------------------
 */
//----------------------------------------------------------------------------------------------
//generic ANIMATION class
//----------------------------------------------------------------------------------------------
function ANIMATOR(engine) {
	//fail safe:
	if(!engine) { return };
	//register here aditional animation engines (hardcoded, for now...):
	this.ENGINES = {
		'zoom':  zoomInOutEngine,
		'fade':  fadeInOutEngine,
		'slide': slideInOutEngine
	};
	this.engine = this.ENGINES[engine];
	//fail safe:
	if(!this.engine) { return };
	this.run = false;
	this.target = null;
	this._usingMsExtension = null;
	this._usingMsFilters = null;
	this._usingW3cAlpha = null;
	this.state = 0;
	this.config = {};
	this.config.name = this.engine._name;
	this.config.highest_val = this.engine._maximum || 1;
	this.config.lowest_val = this.engine._minimum || 0;
	this.config.base_step = this.engine.step || 0.1;
	this.config.base_time = this.engine._time || 10;
	this.config.curve = null;
	this.dependances = new Assoc_Array();
	this.lastReportedValue = 0;
};

ANIMATOR.prototype.attachTo = function(target) {
	this.target = target;
	this._usingMsExtension = (typeof this.target.style.zoom == 'string')? true: false;
	this._usingMsFilters = (typeof this.target.filters != 'undefined')? true: false;
	this._usingW3cAlpha = (typeof this.target.style.opacity == 'string')? true: false;
};

ANIMATOR.prototype.relateTo = function(relation) {
	this.relation = relation;
};

ANIMATOR.prototype.detachFrom = function(target) {
	this.target = null;
};

ANIMATOR.prototype.registerStartCallback = function(functor) {
	this.onStartCallback = functor;
};

ANIMATOR.prototype.registerCompleteCallback = function(functor) {
	this.onCompleteCallback = functor;
};

ANIMATOR.prototype.registerDependant = function(dependance) {
	if(this.dependances === null) {
		return;
	};
	var _tmp = new ANIMATOR(this.config.name);
	_tmp.dependances = null;
	_tmp.registerStartCallback = null;
	_tmp.registerCompleteCallback = null;
	_tmp.attachTo(dependance);
	this.dependances.push(_tmp);
};
ANIMATOR.prototype.registerCurveGenerator = function(functor) {
	this.config.curve = functor;
};

ANIMATOR.prototype._start = function(direction) {
	//failsafe:
	if(!this.engine){ return };
	//prevent overlapping of two asynchronous animations in different directions (in/out):
	var state_OK;
	if(!this.state){
		state_OK = true;
	} else if(direction) {
		if(this.state == -1) {
			state_OK = true;
		} else if (this.state == 1) {
			state_OK = false;
		};
	} else {
		if(this.state == 1) {
			state_OK = true;
		} else if (this.state == -1) {
			state_OK = false;
		};
	};
	if(!state_OK) {
		return;
	};
	this.run = true;
	this.engine(direction);
	if(this.dependances && this.dependances.length) {
		//also start all dependances
        var _this = this;
		this.dependances.each(function(dependancy) {
            dependancy.__parent = _this;
			dependancy.run = true;
			dependancy.engine(direction);
		});
	};
};

ANIMATOR.prototype._stop = function() {
	this.run = false;
	if(this.dependances && this.dependances.length) {
		//also stop all dependances
		this.dependances.each(function(dependancy){
			dependancy.run = true;
		});
	};
};

//----------------------------------------------------------------------------------------------
//specific animation engines
//----------------------------------------------------------------------------------------------
function zoomInOutEngine(direction) {
	//init
	var animator_object = this;
	if(typeof animator_object.onStartCallback == 'function') {
		animator_object.onStartCallback();
	};
	var _target = animator_object.target;
	if( !animator_object._usingMsExtension ) {
        var getTBox = function(element) {
            var response = {};
            var _width = getCSSProperty(element, 'width', 'number');
            var _height = getCSSProperty(element, 'height', 'number');
            if(!_width || !_height) {
                var layoutBox = getLayout(element);
            };
            response.width = _width? _width: layoutBox.width;
            response.height = _height? _height: layoutBox.height;
            return response;		
        };
        if(typeof animator_object.orig == 'undefined') {
        	//if this very animation process runs for a dependancy, I'll derive the box of the
        	//current target from the one of its parent, and cache it:
        	if(typeof animator_object.__parent != 'undefined'){
        		if(typeof animator_object.__parent.orig != 'undefined'){
					animator_object.orig = {
						'width': animator_object.__parent.orig.width,
						'height': Math.ceil(
							animator_object.__parent.orig.height / 
							animator_object.__parent.dependances.length )
					};
        		};
        	} else {
        		//if this very animation process runs for the ancestor (i.e., it has no parent),
        		//I'll have to compute it box, and cache it.
	        	animator_object.orig = getTBox(_target);  
        	};
		};
		//to mimic IE's native zoom extension, children must also be zoomed
		if(animator_object.dependances && !animator_object.dependances.length) {
			var _all = _getOwnChildrenOnly(_target);
			if(_all.length) {
				var ignored = ['br'];
				_Array_each(_all, function(childElement) {
					if( _Array_indexOf(ignored, childElement.nodeName.toLowerCase()) == -1) {
						animator_object.registerDependant(childElement);
					};
				});
			} else {
				animator_object.dependances = null;	
			};
		};
	};
	//basic logic
	var _step   = animator_object.config.base_step;
	var start_value, finish_value, condition, toAdd, _state;
	if(direction) {
		start_value = animator_object.config.lowest_val + _step;
		finish_value = animator_object.config.highest_val;
		condition = function(a, b) { return (a < b)? true: false };
		toAdd = _step;
		_state = 1;
	} else {
		start_value = animator_object.config.highest_val - _step;
		finish_value = animator_object.config.lowest_val + _step;
		condition = function(a, b) { return (a > b)? true: false };
		toAdd = (-1) * _step;
		_state = -1;
	};
	//crossbrowser ZOOM executant sub-class
	var ZOOM_OBJECT = function() {
		this.target = _target;
	};
	ZOOM_OBJECT.prototype.get = function() {
		if (animator_object._usingMsExtension) {
			return getCSSProperty( this.target, 'zoom', 'number' );
		} else {
			var current = getTBox(this.target);
			var response = current.width / animator_object.orig.width;
			return response;
		};
	};
	ZOOM_OBJECT.prototype.set = function(value) {
		if(animator_object._usingMsExtension) {
			this.target.style.zoom = value;
		} else {
			//zoom stuff...
			//MAIN: width & height
			response_width = (value * animator_object.orig.width);
			response_height = (value * animator_object.orig.height);
			this.target.style.width = response_width + 'px';
			this.target.style.height = response_height + 'px';
			//OTHERS...
			var percent = Math.floor(100 * value) + '%';
			//line height
			if(typeof animator_object.origLineHeight == 'undefined') {
				animator_object.origLineHeight = getCSSProperty(this.target, 'line-height');
			};
			this.target.style.lineHeight = percent;
			//font-size
			if(typeof animator_object.origFontSize == 'undefined') {
				animator_object.origFontSize = getCSSProperty(this.target, 'fontsize');
			};
			this.target.style.fontSize = percent;
			//reset stuff... 
			if(percent == '100%') {
				this.target.style.lineHeight = animator_object.origLineHeight;
				this.target.style.fontSize = animator_object.origFontSize;
			};
			var clipString = (percent != '100%')?
					'rect(' + 
						'0px ' +
					    Math.round(response_width + 2) + 'px ' +
					    Math.round(response_height + 2) + 'px ' +
					    '0px' + 
					')' : 'rect(0px 1000px 1000px 0px)';
			this.target.style.clip = clipString;
		};
	};
	//init & reset the ZOOM executant
	var _zoom = new ZOOM_OBJECT();
	_zoom.set(start_value);
	//and finally, RUN all
	var EXEC = function() {
		if(!animator_object.run) { return };
		var old_value = _zoom.get();
		if(condition ( old_value, finish_value ) ) {
			new_value = old_value + toAdd;
			if(!condition(finish_value, new_value)){
				_zoom.set(new_value);
				var next_iteration_time = 10;
				window.setTimeout(function(){ EXEC(); }, next_iteration_time);
			} else {
				_zoom.set(finish_value);
				//done; (try to) free RAM for IE:
				_target = null;
				animator_object.state = _state;
				if(typeof animator_object.onCompleteCallback == 'function') {
					animator_object.onCompleteCallback();
				};
			};
		};
	};
	EXEC();
};
//defaults are hardcoded into the engine
zoomInOutEngine._name    = 'zoom';
zoomInOutEngine._minimum = 0.01;
zoomInOutEngine._maximum = 1;
zoomInOutEngine._time    = 10;
zoomInOutEngine.step     = 0.08;
/* -------------------------------------------------------------------------------------*/

function fadeInOutEngine(direction){
	//init
	var animator_object = this;
	if(typeof animator_object.onStartCallback == 'function') {
		animator_object.onStartCallback();
	};
	var _target = animator_object.target;
	if(animator_object._usingMsFilters){
		this.target.style.filter = '';
		this.target.style.filter = "progid:DXImageTransform.Microsoft.Alpha()";
		this.target.filters.item("DXImageTransform.Microsoft.Alpha").Enabled = true;
		this.target.filters.item("DXImageTransform.Microsoft.Alpha").Style = 0;
		this.target.filters.item("DXImageTransform.Microsoft.Alpha").Opacity = 1;
	} else {
		_target.style.opacity = 0.01;
	}
	//basic logic
	var _step = animator_object.config.base_step;
	var start_value, finish_value, condition, toAdd, _state;
	if(direction) {
		start_value = animator_object.config.lowest_val + _step;
		finish_value = animator_object.config.highest_val;
		condition = function(a, b) { return (a < b)? true: false };
		toAdd = _step;
		_state = 1;
	} else {
		start_value = animator_object.config.highest_val - _step;
		finish_value = animator_object.config.lowest_val + _step;
		condition = function(a, b) { return (a > b)? true: false };
		toAdd = (-1) * _step;
		_state = -1;
	};
	//crossbrowser FADE executant sub-class
	var FADE_OBJECT = function() {
		this.target = _target;
	};
	FADE_OBJECT.prototype.get = function() {
		if(animator_object._usingMsFilters){
			try {
				var currValue = 
					this.target.filters.item("DXImageTransform.Microsoft.Alpha").Opacity/100;
				animator_object.lastReportedValue = currValue;
				return currValue;
			} catch (e){
				return animator_object.lastReportedValue;
			}
		} else {
			if(animator_object._usingW3cAlpha){
				return getCSSProperty(this.target, 'opacity', 'number');
			};
			//or, can't support it...
			return finish_value;
		};
	};
	FADE_OBJECT.prototype.set = function(value) {
		if(animator_object._usingMsFilters){
			try {
			  this.target.filters.item("DXImageTransform.Microsoft.Alpha").Opacity = (value*100);
			} catch(e) {};
			if(value == 1){ this.target.style.filter = '' };
		} else {
			if(animator_object._usingW3cAlpha){
				this.target.style.opacity = value;
			};
		};
	};
	//init & reset the FADE executant
	var _fade = new FADE_OBJECT();
	//and finally, RUN all
	var EXEC = function() {
		if(!animator_object.run) { return };
		var old_value = _fade.get();
		if(condition ( old_value, finish_value ) ) {
			new_value = old_value + toAdd;
			if(!condition(finish_value, new_value)){
				_fade.set(new_value);
				var next_iteration_time = 20;
				window.setTimeout(function(){ EXEC() }, next_iteration_time);
			} else {
				_fade.set(finish_value);
				//done; (try to) free RAM for IE:
				_target = null;
				animator_object.state = _state;
				if(typeof animator_object.onCompleteCallback == 'function') {
					animator_object.onCompleteCallback();
				};
			};
		};
	};
	EXEC();
};
//defaults are hardcoded into the engine
fadeInOutEngine._name    = 'fade';
fadeInOutEngine._minimum = 0.01;
fadeInOutEngine._maximum = 1;
fadeInOutEngine._time    = 10;
fadeInOutEngine._step    = 0.08;
/* -------------------------------------------------------------------------------------*/

function slideInOutEngine(direction) {
	//init
	var animator_object = this;
	if(typeof animator_object.onStartCallback == 'function') {
		animator_object.onStartCallback();
	};
	var _related = animator_object.relation;
	var _target = animator_object.target;
	
	if(typeof animator_object.orig == 'undefined') { animator_object.orig = {} };
	var __rel_box = animator_object.orig.rel_box || 
					(animator_object.orig.rel_box = getLayout(_related));
	var __targ_box = animator_object.orig.targ_box ||
					(animator_object.orig.targ_box = getLayout(_target));
	var __disp_delta = animator_object.orig.disp_delta ||
					(animator_object.orig.disp_delta = getBoxesDelta(__rel_box, __targ_box));
	var __slide_dir = animator_object.orig.slide_dir ||
					(animator_object.orig.slide_dir = 
					 (Math.abs(__disp_delta.x) > Math.abs(__disp_delta.y) )? 'horz': 'vert');
	var orig_slide_string =  'rect(' +
								__targ_box.height+ 'px '+
							 	__targ_box.width+ 'px ' +
							 	__targ_box.height+ 'px '+
 							 	__targ_box.width+ 'px' +
 							  ')';
	_target.style.clip = orig_slide_string;
	//basic logic
	var _step = animator_object.config.base_step;
	var start_value, finish_value, condition, toAdd, _state;
	if(direction) {
		start_value = animator_object.config.lowest_val + _step;
		finish_value = animator_object.config.highest_val;
		condition = function(a, b) { return (a < b)? true: false };
		toAdd = _step;
		_state = 1;
	} else {
		start_value = animator_object.config.highest_val - _step;
		finish_value = animator_object.config.lowest_val + _step;
		condition = function(a, b) { return (a > b)? true: false };
		toAdd = (-1) * _step;
		_state = -1;
	};
	//crossbrowser SLIDE executant sub-class
	var SLIDE_OBJECT = function() {
		this.target = _target;
	};
	SLIDE_OBJECT.prototype.get = function() {
		var clipString = getCSSProperty(_target, 'clip');
		var clipValues = clipString.match(/\d{1,4}px/g);
		if(__slide_dir == 'vert'){
			var topClip = parseInt(clipValues[0]);
			var percent = Math.round((topClip * 100)/__targ_box.height);
		} else {
			var rightClip = parseInt(clipValues[3]);
			var percent = Math.round((rightClip * 100)/__targ_box.width);
		};
		percent = 100 - percent;
		var value = percent/100;
		return value;
	};
	SLIDE_OBJECT.prototype.set = function(value){
		var percent = 100 - (Math.round(value * 100));
		if(__slide_dir == 'vert'){
			var topClip = Math.round((percent * __targ_box.height) / 100);
			var clipString = 'rect(' +
						     	topClip + 'px ' +
						     	__targ_box.width+ 'px '+
						     	__targ_box.height+'px '+
						     	'0px' + 
						     ')';
			_target.style.clip = clipString;
			_target.style.marginTop = ((-1) * topClip) + 'px';
		} else {
			var rightClip = Math.round((percent * __targ_box.width) / 100);
			var clipString = 'rect(' +
						     	'0px ' +
						     	__targ_box.width+ 'px ' + 
						     	__targ_box.height+ 'px '+
						     	rightClip+ 'px' + 
						     ')';
			_target.style.clip = clipString;
			_target.style.marginLeft = ((-1) * rightClip) + 'px';
		};
	};
	//init & reset the SLIDE executant
	var _slide = new SLIDE_OBJECT();
	//and finally, RUN all
	var EXEC = function() {
		if(!animator_object.run) { return };
		var old_value = _slide.get();
		if(condition ( old_value, finish_value ) ) {
			new_value = old_value + toAdd;
			if(!condition(finish_value, new_value)){
				_slide.set(new_value);
				if(new_value >= finish_value) {
					//done (error type 1)
					_target.style.clip = 'rect(0px 1000px 1000px 0px)';
					_target.style.marginTop = '0px';
					_target.style.marginLeft = '0px';
					_target = null;
					animator_object.state = _state;
					if(typeof animator_object.onCompleteCallback == 'function') {
						animator_object.onCompleteCallback();
					};
					return;
				};
				var next_iteration_time = 10;
				window.setTimeout(function(){ EXEC() }, next_iteration_time);
			} else {
				//done (error type 2)
				_target.style.clip = 'rect(0px 1000px 1000px 0px)';
				_target.style.marginTop = '0px';
				_target.style.marginLeft = '0px';
				_target = null;
				animator_object.state = _state;
			};
		} else {
			//done( no error -- the natural way)
			_target.style.clip = 'rect(0px 1000px 1000px 0px)';
			_target.style.marginTop = '0px';
			_target.style.marginLeft = '0px';
			_target = null;
			animator_object.state = _state;
		};
	};
	EXEC();
};
//defaults are hardcoded into the engine
slideInOutEngine._name    = 'fade';
slideInOutEngine._minimum = 0.01;
slideInOutEngine._maximum = 1;
slideInOutEngine._time    = 10;
slideInOutEngine._step    = 0.08;
/* -------------------------------------------------------------------------------------*/
