// JavaScript Document
/**
 * This file contains the colorAnimator JavaScript OOP Interface 
 * for adding functionality to HTML pages to animate DOM elements 
 * with an emphasis on color animation features
 *
 * The file contains a single JavaScript Class that provides the said 
 * functionality
 *
 * Language: 		JavaScript
 * Depenedancies: 	MooTools 1.11
 *					utilities.js
 *
 * LICENSE: This source file is subject to version 1.11 of the MooTools creative 
 * Commons license that is available through the world-wide-web at the following URI:
 * http://creativecommons.org/licenses/by-nc-sa/3.0/  
 *
 * @package    colorAnimator
 * @author     ProjectMiso.net <email@projectmiso.net>
 * @copyright  PhotoBiz, LLC
 * @version    1.0
 * @since      November 22, 2010
 * @deprecated N/A
 *
*/

/**
* Properties - Public Variables: 
		delay:				150,		//if the dropdown needs fixed width - OPTIONAL
		showLog:			true
*/


 
/**
* Public Methods: 
 	--------------------------
	animate(options)
	
	Syntext:
		packageInstance.animate ([{
	 		//String. REQUIRED - as DOM selector
	 		'items': 			'div#top_colored_bar, .navigation_container a.selected',	
	 
	 		//Object. REQUIRED - containing a list of CSS specifications to change to
	 		'styles': 			{
	 	 							'background-color': '#990000',
	 								'height': 30
	 							},			
	 
	 		//Integer. Optional - the duration for animation of the current set. The delfaut is 600 ms
	 		duration:			1000,
	 
	 		//MooTools Fx Object. Optional
	 		transition: 		Fx.Transitions.Expo.easeOut,
	 
	 		//Array of Objects, Optional - specification for color offsets
	 		colorOffsets:		[{
	 							 	'background-color': '20%',
	 								'toward': '#ffffff'
	 							},
	 							{
	 								'color': '10%',
	 								'toward': '#000000'
	 							}],		//the left offset number - OPTIONAL
	 		//Function, Optional - the start animation event function. Can be used to run custom code
	 		onEffectStart: 		function(){
	 								// your code here ....
	 							},
	 
	 		//Function, Optional - the end animation event function. Can be used to run custom code
	 		onEffectEnd: 		function(){
	 								// your code here ....
	 							}
	 	}])
		
	Arguments:
		'options' 		is an Object, Required. Please see the keys for the options object above. Some keys are required
						while others are not.
*/




var colorAnimator = new Class({		
	options: {
		moduleName: 		'colorAnimator',	//the name of the module - REQUIRED
		duration:			600,				//the default duration of animations - OPTIONAL
		transition: 		Fx.Transitions.Expo.easeOut, //MooTools transition Fx object - OPTIONAL
		
		//Events
		onEffectStart: 	function(){},
		onEffectEnd:	function(){},
		
		showLog:			false
	},
	initialize: function (options){
		var self = this;
		this.setOptions(options);
		this.parseOptions(this.options);
		
		this.EL = []; 	//storage for temp elements
		this.FX = [];	//storage for animation object
		this.setCount = 0;				//keeps count of number of sets per request
		this.unloadRequestCount = 0;	//keeps count of number of unload requests to prevent premature execution
		
		this.rollOverSets = [];			//store current roll overs to check for revents
		
	},
	parseOptions: function(optionsObj, ignoreUndefinedProps){
		if (!optionsObj){ return; } else {
			for (var optionName in optionsObj){
				if (ignoreUndefinedProps && optionsObj[optionName] == undefined){ continue; } else { this[optionName] = optionsObj[optionName]; }
			}
		}
	},
	/**
	 * This method should be called to animate DOM objects
	 * @param - array_object. This is an array contining one or more JS objects 
	 						  each object "{}" in the array is a set. There may be one or multiple sets
							  sent in the array. Each set will need all of the required elements below
	 * [{
	 *		//String. REQUIRED - as DOM selector
	 *		'items': 			'div#top_colored_bar, .navigation_container a.selected',	
	 *
	 *		//Object. REQUIRED - containing a list of CSS specifications to change to
	 *		'styles': 			{
	 *	 							'background-color': '#990000',
	 *								'height': 30
	 *							},			
	 *
	 *		//Integer. Optional - the duration for animation of the current set. The delfaut is 600 ms
	 *		duration:			1000,
	 *
	 *		//MooTools Fx Object. Optional
	 *		transition: 		Fx.Transitions.Expo.easeOut,
	 *
	 *		//Array of Objects, Optional - specification for color offsets
	 *		colorOffsets:		[{
	 *							 	'background-color': '20%',
	 *								'toward': '#ffffff'
	 *							},
	 *							{
	 *								'color': '10%',
	 *								'toward': '#000000'
	 *							}],		//the left offset number - OPTIONAL
	 *		//Function, Optional - the start animation event function. Can be used to run custom code
	 *		onEffectStart: 		function(){
	 *								// your code here ....
	 *							},
	 *
	 *		//Function, Optional - the end animation event function. Can be used to run custom code
	 *		onEffectEnd: 		function(){
	 *								// your code here ....
	 *							}
	 *	}]
	 *
	 *	//Boolean, Optional - default is false so all animations happens together. If true, each 
	 *							set will wait for the previous set to complete
	 *  @param - effect. Boolean. Optional - if set to false, the animation will be instantnious			
	 */
	animate: function(array_object, effect){
		var self = this;
		self.log(self.moduleName + ".animate: Executing the 'animate' method");
		this.setCount = 0;
		
		if (effect == undefined) effect = true;
		
		//******* data validation *******
		//check if array
		if (!($chk(array_object) && $defined(array_object.push))) { self.log(self.moduleName+'.animate: first argument is not an array.'); return; }
		//check if array contains values
		if (!array_object.length) { self.log(self.moduleName+'.animate: array does not contain any values.'); return; }
		//check if array values are objects and if the required indexes are there
		for (var i=0;i<array_object.length;i++) { 
			if ($type(array_object[i]) != 'object') { self.log(self.moduleName+'.animate: array contains non-objects.'); return; }
			//check if items and styles - required pameters - are supplied
			if (!$chk(array_object[i].items) || !$chk(array_object[i].styles))  { 
				self.log(self.moduleName+'.animate: array object does not contain "items" or "styles".'); 
				return; 
			}
			//check if the array 'styles' is supplied as an object
			if ($type(array_object[i].styles) != 'object') { 
				self.log(self.moduleName+'.animate: array object "styles" is not an object.'); 
				return; 
			}
		}
		
		self.log (self.moduleName+'.animate: validation passed.');
		
		//******** start processing each set **********
		for (var i=0;i<array_object.length;i++) { 
			var set = array_object[i]; //store the current set
			
			//create the DOM collection
			this.EL[i] = $$(set.items);
			
			//var excludedItems = new Array();
			if (typeof set.itemsExclude != 'undefined'){ //some sites don't have itemsExclude in their styles.
				//now remove the excluded items
				var numItems = this.EL[i].length - 1;
				for(var ei=numItems; ei >= 0; ei--){ //move backwards through this.EL, removing items
					if(this.EL[i][ei].hasClass(set.itemsExclude)){
						this.EL[i].splice(ei,1);
					}
				}
			}
			
			//make sure the collection contains DOM elements
			if (!this.EL[i].length) { self.log(self.moduleName+'.animate: items is not a DOM collection.'); return; }
			else { self.log(self.moduleName+'.animate: ' + this.EL[i]); }
			
			var duration = this.duration; if (set.duration) duration = set.duration;
			var transition = this.transition; if (set.transition) transition = set.transition;
			
			self.log(self.moduleName+'.animate: ready to execute animation for set = ' + i);
			
			//process the animation 
			var targetStyles = {};
			if (this.EL[i].length > 1){
				//the FX object
				this.FX[i] = new Fx.Elements(this.EL[i], {"link": "chain", duration: duration, transition: transition, 
					onComplete: function(){
						if (this.finish) this.finish()
						self.unloadLocal();
					}
				});	
				if (set.onEffectEnd) this.FX[i].finish = set.onEffectEnd;
				
				//calculate the new color based on offset
				if (set.colorOffset){
					var newColor = this.processColor(set);
					if (newColor){
						set.styles[newColor.style] = newColor.color;
					}
				}
				//create array-object based style for each DOM in the set
				for (var j=0;j<this.EL[i].length;j++){ 
					targetStyles[''+j] = set.styles;
					self.log('Item '+ j + " = " + set.styles);
				}
				
			//if the collection contains only one DOM element
			} else {
				this.FX[i] = new Fx.Styles(this.EL[i][0], {"link": "chain", duration: duration, transition: transition, 
					onComplete: function(){
						if (this.finish) this.finish()
						self.unloadLocal();
					}
				});	
				if (set.onEffectEnd) this.FX[i].finish = set.onEffectEnd;
				
				//calculate the new color based on offset
				if (set.colorOffset){
					var newColor = this.processColor(set);
					if (newColor){
						set.styles[newColor.style] = newColor.color;
					}
				}
				//object based style  for a single element
				targetStyles = set.styles;
			}
			
			//fire event
			if (set.onEffectStart) set.onEffectStart();
			
			//start the animation
			this.setCount++;
			if (effect){
				this.FX[i].start(targetStyles);
			} else {
				this.FX[i].set(targetStyles);
			}
		}
		this.onEffectStart();
		
	},
	processColor: function(data){
		var self = this;
		self.log(self.moduleName+'.processColor: executing function processColor')
				
		if ($chk(data.colorOffset) && $type(data.colorOffset) == 'object'){
			var offsetIndex = 0, offsetStyle, offsetPercent, offsetColor, newColor;
			for (var key in data.colorOffset){
				if (offsetIndex == 0 && key != 'toward') {
					offsetStyle = key;
					offsetPercent = data.colorOffset[key].replace('%', '').toInt();
				} else if (key == 'toward'){
					offsetColor = data.colorOffset[key];
				}
				offsetIndex++;
			}
			self.log(self.moduleName+'.processColor: calculating colorOffset ...');
			self.log(self.moduleName+'.processColor: offsetStyle = ' + offsetStyle);
			self.log(self.moduleName+'.processColor: offsetPercent = ' + offsetPercent);
			self.log(self.moduleName+'.processColor: offsetColor = ' + offsetColor);
			self.log(self.moduleName+'.processColor: calculating new color ...');
			if (data.styles[offsetStyle]){
				//var originalColor = new Color(data.styles[offsetStyle]);
				newColor = this.getColor(data.styles[offsetStyle], offsetColor,offsetPercent); //originalColor.mix(offsetColor, offsetPercent).rgbToHex()
				
				self.log(self.moduleName+'.processColor: newColor = ' + newColor);
				return {'style': offsetStyle, 'color': newColor};
			}
		}
		
		return false;
	},
	getColor: function(original, toward, offset) {
		var self = this;
		self.log(self.moduleName+'.getColor: executing function getColor');
		
		offset = ($type(offset) == 'number') ? offset : offset.replace('%', '').toInt();
		self.log(self.moduleName+'.getColor: offset is: ' + offset);
		
		var newColor;
		if ($type(offset) == 'number'){
			self.log(self.moduleName+'.getColor: offset is: ' + offset);
			self.log(self.moduleName+'.getColor: original color is: ' + original);
			self.log(self.moduleName+'.getColor: offset color is: ' + toward);
			
			original = new Color(original);
			newColor = original.mix(toward, offset).rgbToHex();
			
			self.log(self.moduleName+'.getColor: new color prcessed. new color is: ' + newColor);
			return newColor;
		}
		return original;
		self.log(self.moduleName+'.getColor: validation failed');
	},
	/**
	 * This method is called to add roll over CSS styling to elements, while removing any previous roll-over events
	 * @param - params. Object. This is an object contining roll over style options
	 * {
	 *		//String. REQUIRED - as DOM selector
	 *		'items': 			'#menu_container div.menu_items',
	 *
	 *		//Object. REQUIRED - containing a list of CSS specifications to change to for off State
	 *		'offStyles': 		{
	 *	 							'background-color': '#ffffff',
	 *								'color': '#990000'
	 *							},
	 *		//Object. REQUIRED - containing a list of CSS specifications to change to for onState
	 *		'onStyles': 		{
	 *	 							'background-color': '#990000',
	 *								'color': '#ffffff'
	 *							}
	 * }
	 */
	setRollover: function(params){
		var self = this;
		self.log(self.moduleName+'.setRollover: executing function setRollover');
		
		//******* data validation 
		self.log(self.moduleName+'.setRollover: validating ...');
		//check if object
		if ($type(params) != "object") { self.log(self.moduleName+'.setRollover: argument is not an object.'); return; }
		//check if object values are objects and if the required indexes are there
		var checkCount = 0, set; //must be three to pass
		for (var key in params){
			if (key == 'items')	{
				checkCount++;
				//collect the entire set
				set = $(document.body).getElementsBySelector(params.items);
				
				//process exclusions
				if (params.exclude){
					var excludeSet = $(document.body).getElementsBySelector(params.exclude);
					for (var x=0;x<excludeSet.length;x++){
						set.remove(excludeSet[x]);	
					}
				}
				if (!set.length){ self.log(self.moduleName+'.setRollover: the set returned no elements.'); return; }
			}
			if (key == 'offStyles' && $type(params.offStyles) == "object") checkCount++;
			if (key == 'onStyles' && $type(params.onStyles) == "object") checkCount++;
		}
		if (checkCount != 3){ 
			self.log(self.moduleName+'.setRollover: all required items were not supplied or they are not in valid format.'); 
			set = null;
			return; 
		}
		
		self.log(self.moduleName+'.setRollover: validation passed. set = ' + set);
		
		//apply the effects first
		for (var i=0;i<set.length;i++){
			if (set[i].state == undefined || set[i].state == 'off'){
				set[i].setStyles(params.offStyles);
			} else if (set[i].state == 'on'){
				set[i].setStyles(params.onStyles);
			}
		}
		
		//first check for existing roll over events and remove them
		this.removeRollover(params.items);
		
		//the action functions
		var rolloverFunction = function(){
			$clear(this.rolloutTimer);
			this.setStyles(params.onStyles);
			this.state = 'on';
			//if (window.console) window.console.log('M OVER')
		}
		var rolloutFunction = function(){
			//alert(params.offStyles.color + " " + params.offStyles['background-color'])
			this.setStyles(params.offStyles);
			this.state = 'off';
		}
		
		//now attach new roll over event
		set.addEvent('mouseover', rolloverFunction);
		
		//now attach new roll out event
		set.addEvent('mouseout', rolloutFunction);
		
		self.log(self.moduleName+'.setRollover: added roll-over and roll-out actions.');
		
		//add the set to the storage
		this.rollOverSets[this.rollOverSets.length] = {};
		this.rollOverSets[this.rollOverSets.length-1].items = params.items;
		this.rollOverSets[this.rollOverSets.length-1].fnOn = rolloverFunction;
		this.rollOverSets[this.rollOverSets.length-1].fnOff = rolloutFunction;
	},
	removeRollover: function(selector){
		var self = this;
		self.log(self.moduleName+'.removeRollover: executing function removeRollover');
		
		//first check for existing roll over events and remove them
		for (var i=0;i<this.rollOverSets.length;i++){
			if (selector){
				self.log(selector + " " + this.rollOverSets.items);
				if (selector != this.rollOverSets[i].items) continue;
			}
			
			var tempSet = $$(this.rollOverSets[i].items);
			if (tempSet.length){
				self.log(self.moduleName+'.removeRollover: removing roll over/out  actions for set: ' + this.rollOverSets[i]);
				self.log(self.moduleName+'.removeRollover: roll-over function: ' + this.rollOverSets[i].fnOn);
				tempSet.removeEvent('mouseover', this.rollOverSets[i].fnOn);
				tempSet.removeEvent('mouseout', this.rollOverSets[i].fnOff);
				tempSet = null;
			}
			this.rollOverSets[i] = null;
			this.rollOverSets.splice(i, 1);
		}
	},
	unloadLocal: function(force){
		var self = this;
		self.log(self.moduleName+'.unloadLocal: executing function unloadLocal');
		
		if (!$chk(force)){
			this.unloadRequestCount++;
			if (this.unloadRequestCount != this.setCount) {
				self.log(self.moduleName+'.unloadLocal: Not all animations are done yet.');
				return;
			}
			self.log(self.moduleName+'.unloadLocal: Animations are complete. Unloading local ... ');
			this.onEffectEnd();
			this.unloadRequestCount = this.setCount = 0;
		}
		
		for (var i=0;i<this.EL.length;i++){
			this.EL[i] = null;
		}
		this.EL = null; this.EL = [];
		
		for (var i=0;i<this.FX.length;i++){
			this.FX[i] = null;
		}
		this.FX = null; this.FX= [];
		
	},
	unload: function(){
		var self = this;
		self.log("Executing the 'unload' method");
		
		//******** Unload local DOMs
		self.unloadLocal();
		
		//unload this.rollOverSets
		self.removeRollover();
		
		//******* Unload global DOMs and EVENTs
		PBUtil.garbage.dispose ('all', self.moduleName)
		
		//***** Unload the class
		var moduleName = this.moduleName;
		for (var key in this){
			self.log(key + " --- " + $type(this[key]) + " --- " + this[key]);
			if ($type(this[key]) == 'object'){
				delete this[key];
				self.log("DELETED object: " + key );
			} else if ($type(this[key]) == 'string' || $type(this[key]) == 'number' || $type(this[key]) == 'collection' || $type(this[key]) == 'regexp' || $type(this[key]) == 'function'){
				if (key != 'log'){
					this[key] = null;
					delete this[key];
					self.log("DELETED: " + key );
				} 
			} else {
				self.log("****** NOT DELETED: " + key )	
			}
		}
		for (var key in this){
				self.log("DELETED Left Over: " + key  + " --- " + $type(this[key]) + " --- " + this[key]);
				this[key] = null;
				delete this[key];
		}
		
		this.log = null;
		PBUtil.log("Deleting the LOG method. Check: " + this.log);
		
		if (!window.ie) delete this;
		
		PBUtil.module[moduleName] = null;
		delete PBUtil.module[moduleName];
		
		PBUtil.log("Deleting the PBUtil store. Check: " + PBUtil.module[moduleName]);
		
		moduleName = null;
	},
	log: function(txt){
		if (window.console && this.showLog){
			window.console.log(txt);
		}
	}
});

// implementing the events and options
colorAnimator.implement(new Events, new Options);



