/*---------------------------------------------------------------------------/ 
 *    ASAP JavaScript framework, version 0.1
 *  (c) 2009 J??e FEUILLEBOIS
 *
 *  ASAP is freely distributable under the terms of an MIT-style license.
 *  For details, see the documentation web site on :
 *  http://www.pricebuzz.fr/asap &
 *
 *--------------------------------------------------------------------------*/


/****************************************** AXCSS ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : Main class of the axCss API. (Singleton pattern with public static constants)
**/
function AXCSS(){
 	
	if ( AXCSS.caller != AXCSS.getInstance ) {  
         throw new Error("This object cannot be instanciated");  
    }
	
	this.requestH =  new RequestHandler();
	this.parseH = new ParseHandler();
	this.transitionH = new TransitionHandler();

}

/**
 * @Description : initialize the API including dependencies
**/
AXCSS.prototype.init = function(){
		
		this.include("axCss/prototype.js");
		this.include("axCss/scriptaculous.js");
		this.parseH.init();
};

/**
 * @Description : This method import an other .js file.
**/
AXCSS.prototype.include = function(scriptName){
		document.write('<script src="'+scriptName+'" type="text/javascript"></script>');
};

/**
 * @Description : This is how to send an AJAX request with axCss by using the scriptaculous effects.
 *
 * @param method : "GET" or "POST"
 * @param action : the url of your AJAX request
 * @param stringListArea : string containing the targets for which requests are made with effects that are driving change
**/
AXCSS.prototype.send = function(method, action, param, stringListArea){
	this.requestH.createRequest(method,action,param,stringListArea,this.transitionH.createTransition(stringListArea)).launch();
};

/** 
 * @Description : Singleton (instance and method)
**/
AXCSS.instance = null;

AXCSS.getInstance = function() {  
  if (this.instance == null) {  
      this.instance = new AXCSS();  
   }  
    return this.instance;  
}  

/***** CONSTANTS *****/

/*
* These variables are for Syntax parser. (match with the standard W3C)
* /!\ Do not change them if you don't understand what it means. /!\
*/
AXCSS.urlSeparator = "=";
AXCSS.areaSeparator = ":";
AXCSS.areaIndicator = "axcss:";

/*
* Here you can change the duration for each effect.
*/
AXCSS.fromLeftTimer = 1;
AXCSS.fromRightTimer = 1;
AXCSS.fromTopTimer = 1;
AXCSS.fromBottomTimer = 1;
AXCSS.changeTimer = 0.5;
AXCSS.instantTimer = 0;
AXCSS.maxQueueTransition = 10;


/****************************************** RequestHandler ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : Class used to manage each request
**/
function RequestHandler(){};

/**
 * @Description : Creates an axCss query with effects.
 *
 * @param method : "GET" or "POST"
 * @param action : the url of your AJAX request
 * @param stringListArea : string containing the targets for which requests are made with effects that are driving change
 * @param transition : Transition object using for launch effects
 * @return EffectRequest
**/
RequestHandler.prototype.createRequest = function(method, action, param, stringListArea, transition){
	return new EffectRequest(method, action, param, stringListArea, transition);
}


/****************************************** RequestHandler ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : Class used to interact with the server by http requests
 *
 * @param method : "GET" or "POST"
 * @param action : the url of your AJAX request
 * @param stringListArea : string containing the targets for which requests are made with effects that are driving change
 * @param transition : Transition object using for launch effects
**/
function EffectRequest(method, action, param, stringListArea, transition){
	var m = method;
	var a = action;
	var p = param;
	var strListArea = stringListArea;
	var t = transition;
	var req = null;
	
	/**
	 * @Description : This method sends the request and enables the Transition's effects.
	**/
	this.launch = function(){
		try {req = new XMLHttpRequest();} catch (ms1) {
			try {req = new ActiveXObject("Msxml2.XMLHTTP");} catch (ms2) {
				try {req = new ActiveXObject("Microsoft.XMLHTTP");} catch (echec){
					req = null;
				}
			}
		}
		if (req == null)alert("AJAX Request Error ! Your browser doesn't allow it.");
		else{
			
			req.open(m,a,true);
			
			req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			
			req.send(p);
			t.start();
			req.onreadystatechange = function()
			{
			   
				if(req.readyState == 4){
					if(req.status == 200){
						var areaList = strListArea.split(AXCSS.areaSeparator);
						t.setNbTotalArea(areaList.length);
						for(var i = 0; i < areaList.length ; i++){
							var nameValue = areaList[i].split(AXCSS.urlSeparator);
							var element = document.getElementById(nameValue[0]);
							var finaltext = AXCSS.getInstance().parseH.parseResult(element.id,req.responseText);
							t.setResult(element,finaltext);
						}
					}
					else{
						//TODO : Failure effects
					}
							
				}
				//else{
					//TODO : Loading effects
				//}
			}
		}
		
		
	}

}

/****************************************** ParseHandler ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : Class used to parse HTML code with DOM and Regular Expressions
**/
function ParseHandler(){};

/**
 * @Description : HTML document initialization
**/
ParseHandler.prototype.init = function(){
	this.parseWhenReady(function(){ParseHandler.prototype.parseDOMAXCSS(document);});
};

/**
 * @Description : verification the web page is loaded
 * @param handler : action to do when the web page is ready
**/
ParseHandler.prototype.parseWhenReady = function(handler) {
		if (document.addEventListener) {
			if (navigator.userAgent.indexOf('AppleWebKit/') > -1 || window.opera) {
				var timer = window.setInterval(function() {if (/loaded|complete/.test(document.readyState)) {window.clearInterval(timer);handler();}}, 30);
			} else
				document.addEventListener('DOMContentLoaded', handler, false);
		} else {
			var tempNode = document.createElement('document:ready');
			(function() {try {if (document.readyState != 'complete') return setTimeout(arguments.callee, 30); tempNode.doScroll('left');	tempNode = null; handler();	} catch (e) { setTimeout(arguments.callee, 30); }})()
		}
};
	
/**
 * @Description : Parse an HTML element and set the axCss effect on each <a> and <form>
 *
 * @param elementToParse : HTML element to be parsed
**/	
ParseHandler.prototype.parseDOMAXCSS = function(elementToParse){
	
	var linkList = elementToParse.getElementsByTagName('a');
	if(linkList != null){
		for(var i = 0; i < linkList.length; i++){
			if(linkList[i].className != null){
				var linkavailability = linkList[i].className.split(AXCSS.areaIndicator);
				if(linkavailability.length == 2){
					linkList[i].onclick = function(){
						AXCSS.getInstance().send("GET", this.href, '', this.className.split(AXCSS.areaIndicator)[1]);	
						return false;
					};            
				}
			}
		}
	}
	var formList = elementToParse.getElementsByTagName('form');

	for(var i = 0; i < formList.length; i ++){
		
		var formavailability = formList[i].className.split(AXCSS.areaIndicator);

		if(formavailability.length == 2){
			
			formList[i].submit = function(){
				var param = '';
				var start = true;
				for(var j = 0; j < this.elements.length ; j++){
					if(this.elements[j].type == 'radio' ||this.elements[j].type == 'checkbox'){
						if(this.elements[j].checked){
							if(start){
								start = false;
							}
							else{
								param += "&";
							}
							param += this.elements[j].name+'='+this.elements[j].value;
						}
					}
					else{
						if(start){
							start = false;
						}
						else{
							param += "&";
						}
						param += this.elements[j].name+'='+this.elements[j].value;
					}
				}
				AXCSS.getInstance().send(this.method, this.action, param, this.className.split(AXCSS.areaIndicator)[1]);	
				return false;
			};
				
			formList[i].onsubmit = function(){
				var param = '';
				var start = true;
				for(var j = 0; j < this.elements.length ; j++){
					if(this.elements[j].type == 'radio' ||this.elements[j].type == 'checkbox'){
						if(this.elements[j].checked){
							if(start){
								start = false;
							}
							else{
								param += "&";
							}
							param += this.elements[j].name+'='+this.elements[j].value;
						}
					}
					else{
						if(start){
							start = false;
						}
						else{
							param += "&";
						}
						param += this.elements[j].name+'='+this.elements[j].value;
					}
				}
				AXCSS.getInstance().send(this.method, this.action, param, this.className.split(AXCSS.areaIndicator)[1]);	
				return false;
			};
		}
	}
};

/**
 * @Description : This method executes JavaScript code in each <script>
 *
 * @param element : HTML element to be parsed
**/		
ParseHandler.prototype.executeScript = function(element){
	
	var ScriptArray = element.getElementsByTagName('script');
	
	if(ScriptArray != null){
		for(var i = 0; i < ScriptArray.length; i++){
			eval(ScriptArray[i].innerHTML);
		}
	}
}

/**
 * @Description : It takes the result needed from the AJAX query return.
 *
 * @param id : id of the HTML element needed
 * @param strToParse : AJAX query result
**/	
ParseHandler.prototype.parseResult = function(id,strToParse){
	var expr = '<div[^>]*id=['+"'"+'"]?'+id+'['+"'"+'"]?[^<]*>';
	var regToAdd = '(.|\\s)*?</div>';
	var regOpen = new RegExp('<div','gi');
	var regClose = new RegExp('</div>','gi');
	var reg=null;
	var result=null;
	
	while(result == null || result.split(regOpen).length != result.split(regClose).length){
		expr = expr+regToAdd;
		reg = new RegExp(expr,'gi');
		if(strToParse != null){
			var tempArrRes = null; 
				//tempArrRes = setTimeout("regExec("+reg+","+strToParse+")",5000);
				tempArrRes = reg.exec(strToParse);
				
			if(tempArrRes != null && tempArrRes.length >= 1){
				result = tempArrRes[0];
				
			}else{
				
				break;
			}
		}
		else
			break;
		//alert("open = "+result.split(regOpen).length+" close = "+result.split(regClose).length);
	}
	result = result.replace(new RegExp('<div[^>]*id=['+"'"+'"]?'+id+'['+"'"+'"]?[^<]*>', "gi"),'');
	result = result.substring(0,result.length-7);
	
	return result;
}

/****************************************** Animation ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : Animation JavaScript who extends script.aculo.us.Effect.Parallel
**/
Animation.prototype = new Effect.Parallel;

function Animation(tabEffect,beforeStart,afterFinish) {
	
	var end = false;
	
	/**
	 * @Description : action to do when the effect starts
	**/
	var start = function(){end=false; if(beforeStart != null)beforeStart();};
	
	/**
	 * @Description : action to do when the effect is over
	**/
	var finish = function(){end = true; if(afterFinish != null)afterFinish();};

	/* "this.Super" because "this.super" is not allowed on ie browsers */
	this.Super = Effect.Parallel;
	
	/**
	 * @Description : give the duration of the animation
	 * @return d : integer of animation duration (in seconds) 
	**/
	this.getDuration = function(){
		var d = 0;
		for(var i = 0; i<tabEffect.length ; i++){
			if(tabEffect[i].options.duration > d)
			d = tabEffect[i].options.duration;
		}
		return d;
	}
	
	/**
	 * @Description : Super constructor. read scriptaculous documentation for more informations. (http://script.aculo.us/)
	**/
	this.Super(tabEffect,{duration : this.getDuration(), beforeStart : start , afterFinish : finish }); 
	
	/**
	 * @Description : It returns the animation's status
	 *
	 * @return end : boolean
	**/
	this.isFinish = function(){
		return end;
	}
} 

/****************************************** Transition ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : This object manages the transition by synchronisation between JavaScript effects and AJAX query results.
 *
 * @param startTabEffect : an array of all JavaScript effects who will be launch before the transition.
 * @param endTabEffect : an array of all JavaScript effects who will be launch after the transition.
 * @param queueTransition : the QueueTransition where are stored all transitions to be executed one by one.
**/
function Transition(startTabEffect,endTabEffect,queueTransition){
	
	/* private attributes */
	var startListEffect = startTabEffect;
	var endListEffect = endTabEffect;
	var elements = new Array();
	var results = new Array();
	var nbTotalArea = null;
	var over = null;
	var startAnime = null;
	var endAnime = null;
	
	/**
	 * @Description : this method receive an AJAX query result who will be set in an HTML element.
	 *
	 * @param e : HTML element.
	 * @param r : string of the AJAX query result.
	**/
    this.setResult = function(e,r){elements.push(e); results.push(r);};
	
	/**
	 * @Description : this method setup the quantity of HTML element concerned by the request.
	**/
	this.setNbTotalArea = function(n){
		nbTotalArea = n;
	}
	/**
	 * @Description : start the transition.
	**/
	this.start = function(){
		queueTransition.add(this);
	}
	/**
	 * @Description : play the transition.
	**/
	this.play = function(){
		playStart();
	}
	/**
	 * @Description : play the first JavaScript Effects.
	**/
	var playStart = function(){
		startAnime = new Animation(startListEffect,null,playLoad);
		over = false;
	};
	/**
	 * @Description : play the last JavaScript Effects.
	**/
	var playEnd = function(){
		endAnime = new Animation(endListEffect,null,null);
	};
	/**
	 * @Description : while the AJAX query is loading...
	**/
	var playLoad = function(){
	   check();
	}
	/**
	 * @Description : return the status of the transition.
	 *
	 * @return over : boolean
	**/
	this.isOver = function(){
		if(endAnime != null && endAnime.isFinish())
			over = true;
		return over;
	}
	/**
	 * @Description : this method verifies the AJAX query results before strating the second part of the transition
	**/
	var check = function(){
		
		if(nbTotalArea != null && elements.length == nbTotalArea && results.length == nbTotalArea){
		   for(var i = 0; i < nbTotalArea; i++){
			   elements[i].innerHTML = results[i];
			   AXCSS.getInstance().parseH.executeScript(elements[i]);	
			   AXCSS.getInstance().parseH.parseDOMAXCSS(elements[i]);
			   playEnd();
		   }
	   }
	   else{
		   setTimeout(function(){check();},100);
	   }	
		
	};
	
}


/****************************************** Transition ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : This object checks the transition and launches them one by one.
**/
function QueueTransition(){
	
	var listTransition = new Array();
	/**
	 * @Description : play transitions.
	**/
	var play = function(){
		
		if(listTransition.length >0){
			if(listTransition[0].isOver()){
				listTransition.splice(0,1);
			}else if(listTransition[0].isOver() == null){
				listTransition[0].play();
			}
			setTimeout(function(){play();},100);
		}
		
	};
	/**
	 * @Description : add a transition at the end of the queue to be played after the others in front of it
	 *
	 * @param transition : a Transition object
	**/
	this.add = function(transition){
		if(listTransition.length < AXCSS.maxQueueTransition){
			listTransition.push(transition);
			play();
		}
	};

}

/****************************************** TransitionHandler ******************************************/

/**
 * Author : J??e FEUILLEBOIS
 * @Description : Class used to manage each Transition
**/
function TransitionHandler(){
	var qT = new QueueTransition();	
	
	/**
	 * @Description : creates a Transition object
	 *
	 * @param stringListArea : a string containing all effects and html elements concerned by the AJAX query
	 * @return a Transition object
	**/
	this.createTransition = function(stringListArea){
	
		var areaList = stringListArea.split(AXCSS.areaSeparator);
		var startTabEffect = new Array();	
		var endTabEffect = new Array();
		
		for(var i = 0; i < areaList.length ; i++){	
			var nameValue = areaList[i].split(AXCSS.urlSeparator);
			if(nameValue != null && nameValue.length == 2){
				
				var element = document.getElementById(nameValue[0]);
				var effect = nameValue[1];
	
				if(effect == 'instant'){
					
					startTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.instantTimer, x : 0, y : 0, mode : 'relative', sync : false }));
					endTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.instantTimer, x : 0, y : 0, mode : 'relative', sync : false }));
					
				}else if(effect == 'change'){
					
					startTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.changeTimer, from : 1, to : 0, sync : true}));
					endTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.changeTimer, from : 0, to : 1, sync : true}));
				
				}else if(effect == 'fromLeft'){
					
					var x = element.offsetWidth / 2;
					startTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.fromLeftTimer, x : -x, y : 0, mode : 'relative', sync : true }));
					startTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.fromLeftTimer, from : 1, to : 0, sync : true }));
					endTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.fromLeftTimer, x : x, y : 0, mode : 'relative', sync : true }));
					endTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.fromLeftTimer, from : 0, to : 1, sync : true }));
				}else if(effect == 'fromRight'){
					
					var x = element.offsetWidth / 2;
					startTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.fromRightTimer, x : x, y : 0, mode : 'relative', sync : true }));
					startTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.fromRightTimer, from : 1, to : 0, sync : true }));
					endTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.fromRightTimer, x : -x, y : 0, mode : 'relative', sync : true }));
					endTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.fromRightTimer, from : 0, to : 1, sync : true }));
				}else if(effect == 'fromTop'){
					
					var y = element.offsetHeight / 2;
					startTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.fromTopTimer, x : 0, y : -y, mode : 'relative', sync : true }));
					startTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.fromTopTimer, from : 1, to : 0, sync : true }));
					endTabEffect.push(new Effect.Move(element.id, {duration : AXCSS.fromTopTimer, x : 0, y : y, mode : 'relative', sync : true }));
					endTabEffect.push(new Effect.Opacity(element.id, {duration : AXCSS.fromTopTimer, from : 0, to : 1, sync : true }));
				}
				else if(effect == 'fromBottom'){
					
					var y = element.offsetHeight / 2;
					startTabEffect.push(new Effect.Move(element.id, {duration : 1, x : 0, y : y, mode : 'relative', sync : true }));
					startTabEffect.push(new Effect.Opacity(element.id, {duration : 1, from : 1, to : 0, sync : true }));
					endTabEffect.push(new Effect.Move(element.id, {duration : 1, x : 0, y : -y, mode : 'relative', sync : true }));
					endTabEffect.push(new Effect.Opacity(element.id, {duration : 1, from : 0, to : 1, sync : true }));
				}
					
			}
		}
			 
		return new Transition(startTabEffect,endTabEffect,qT);
	};
};

/*********************************************************/ 
//* A good little tool made to debug my code. 
//* Just give him an object to view usefull informations.
/*********************************************************/ 
function genereJSDoc(obj){
	document.write("<table class='jsDoc' border='1'>");
	document.write("<tr><td><b>NOM</b></td><td><b>VALEUR</b></td></tr>");
	for (var i in obj){
		
	   document.write("<tr><td>"+i+"</td><td>"+obj[i]+"</td></tr>");
	}
	document.write("</table>");
}
/*********************************************************/ 


 
/***** Launch the API *****/
if(!navie){
 AXCSS.getInstance().init();
}
