/**
 * Objeto para requisições Ajax.
 * Método construtor
 * @author Jose Berardo
 * @author Tulio Carvalho
 * @author Cledson Hilder
 */
var Asynx = function(metodo, url, sincrono, callback) {
	// ---------     Atributos Públicos   -----------------
	this.ajaxRequest = function() {
		var retorno = null;
		try {
			XMLHttpRequest.prototype.asynx = {};
			retorno = new XMLHttpRequest();
		} catch(e) {
			try {
				ActiveXObject.prototype.asynx = {};
				retorno = new ActiveXObject("Microsoft.XMLHTTP");
			} catch(e) {
				alert("Seu browser não suporta requisições assíncronas");
			}
		}
		return retorno;
	}();
	
	this.metodo   = metodo;
	this.url      = url;
	this.sincrono = sincrono;
	this.xsl      = null;
	this.resposta = null;
	
	if (callback) this.callback = callback;

	// -----------    Atributos Internos --------------
	this._requestObjects = [];
	this._requestValues  = [];
	this._querystring    = "";

	// --------     Métodos que devem ser definidos pelo usuário   -----------------
	this.callback  = function(){}, 
	this.loading   =  function(codigo, texto){}, 
	this.exception = function(codigo, mensagem){}
};

// Constantes de Classe
Asynx.UNINITIALIZED = 0;
Asynx.LOADING       = 1;
Asynx.LOADED        = 2;
Asynx.INTERACTIVE   = 3;
Asynx.COMPLETE      = 4;
Asynx.PLAIN         = "plain";
Asynx.XML           = "xml";
Asynx.JSON          = "json";


Asynx.prototype = {
	// ---------     Métodos públicos gerais ------------
	resetarValores: function() {
		this._requestObjects = [];
		this._requestValues = [];
		this._querystring = "";
	},
	addObject: function(obj) {
		this._requestObjects[this._requestObjects.length] = obj;
	},
	addParam: function(chave, param) {
		this._requestValues[this._requestValues.length] = [chave, param];
	},
	removeParam: function(chave) {
		for (var x in this._requestValues) {
			if (this._requestValues[x][0] == chave) {
				this._requestValues.splice(x,1);
				break;
			}
		}
	},
	addForm: function(form) {
		for (var x in form.elements) {
			if (!form.elements[x].disabled) {
				if ((form.elements[x].type != 'checkbox' && form.elements[x].type != 'radio')
						|| form.elements[x].checked){
					this.addObject(form.elements[x]);
				}
			}
		}
	},
	
	transformTemplate: function(no, tipo, xsl) {
		if (typeof no == "string") {
			no = document.getElementById(no);	
		}
		if ((typeof xsl == 'undefined') || (xsl == null)) {
			xsl = this.xsl;
		}
		var resposta = null;
		if (tipo == Asynx.JSON) {
			resposta = this._jsonToXml();
		} else {
			resposta = this.resposta;
		}

		if (typeof XSLTProcessor != "undefined") { // W3C
			var processor = new XSLTProcessor();
			processor.importStylesheet(xsl);
			var fragmento = processor.transformToFragment(resposta, document);
			no.innerHTML = "";
			no.appendChild(fragmento);
		} else {
			var versoes = ['MSXML2.DOMDocument.5.0', 'MSXML2.DOMDocument.4.0',
						   'MSXML2.DOMDocument.3.0','MSXML2.DOMDocument',
						   'Microsoft.XMLDOM'];
			try {
				for (var x in versoes) {
					parser = new ActiveXObject(versoes[x]);
					parser.loadXML(resposta.outerHTML);
					break;
				}
			} catch(e) {}			
			if ("transformNode" in parser) { // IE
				var string = parser.transformNode(xsl);
				no.innerHTML = string;
			} else {
				this.exception(0, "Seu browser não suporta XSLT!");
			}
		}
		
	},
	
	
	// ---------     Métodos de envio   -----------------
	buscarXsl: function(metodo, url) {
		this.loading(Asynx.UNINITIALIZED, "Loading template");
		this._sobrescreverConfiguracoes(metodo, url, true);
		this.addParam("Asynx-Type", "xsl"); 
		this._load(Asynx.XML);
		this.removeParam("Asynx-Type");
	},
	buscarXml: function(metodo, url, sincrono) {
		this._sobrescreverConfiguracoes(metodo, url, sincrono);
		this._load(Asynx.XML);
	},
	buscarTxt: function(metodo, url, sincrono) {
		this._sobrescreverConfiguracoes(metodo, url, sincrono);
		this._load(Asynx.PLAIN);
	},
	buscarJson: function(metodo, url, sincrono) {
		this._sobrescreverConfiguracoes(metodo, url, sincrono);
		this._load(Asynx.JSON);
	},
	carregarFormEmLayer: function(form, metodo, url, layer, sincrono) {
		this.addForm(document.forms[form]);
		this.carregarLayer(metodo, url, layer, sincrono, true);
	},
	carregarLayer: function (metodo, url, layer, sincrono, manterValores) {
		this._sobrescreverConfiguracoes(metodo, url, sincrono);
		if (!manterValores) this.resetarValores();
		this.loading = function() {
			document.getElementById(layer).innerHTML = "Carregando...";	
		};
		this.callback = function() {
			document.getElementById(layer).innerHTML = this.resposta;			
		};
		this.buscarTxt();
	},
	carregarConteudo: function(metodo, url, no, formato) {
		if (this.xsl == null) {
			this.buscarXsl(metodo, url);
			this.xsl = this.resposta;
		}
		this._sobrescreverConfiguracoes(metodo, url, true);
		this._load(formato, true, no);
		this.loading(Asynx.COMPLETE, "Complete");
	},

	// ---------     Métodos privados   -----------------
	_load: function(formato, carregarConteudo, no) {
		this.resposta = null;
		this._montarQueryString();
		if (this.metodo.toUpperCase() == "GET" ) {
			var querystring = (this._querystring) ? '?' + this._querystring : '';
			this.ajaxRequest.open(this.metodo, this.url + querystring, !this.sincrono);
			this.ajaxRequest.setRequestHeader('x-User-Agent', 'Asynx Ajax Tool - v1.0');
			this.ajaxRequest.send(null);
		}
		if (this.metodo.toUpperCase() == "POST") {
			this.ajaxRequest.open(this.metodo, this.url, !this.sincrono);
			this.ajaxRequest.setRequestHeader('x-User-Agent', 'Asynx Ajax Tool - v1.0');
			this.ajaxRequest.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
			this.ajaxRequest.setRequestHeader('Content-Length',this._querystring.length);
			this.ajaxRequest.send(this._querystring);
		}
		if (this.sincrono) {
			this._recuperarResultado(formato);
			if (carregarConteudo != null) {
				try {
					this.transformTemplate(no, formato);
				}catch (e) {
					this.exception(0, e);
				}		
			}
			this.callback();
		} else {
			var varAsynx = this; // Gambi pro IE 6 (essa murrinha)
			this.ajaxRequest.onreadystatechange = function() {
				// Rotina do Loading
				var state;
				if (varAsynx.ajaxRequest.readyState == Asynx.UNINITIALIZED) { state = 'uninitialized' }
				if (varAsynx.ajaxRequest.readyState == Asynx.LOADING) { state = 'loading' }
				if (varAsynx.ajaxRequest.readyState == Asynx.LOADED) { state = 'loaded' }
				if (varAsynx.ajaxRequest.readyState == Asynx.INTERACTIVE) { state = 'interactive' }
				if (varAsynx.ajaxRequest.readyState == Asynx.COMPLETE) { state = 'complete' }
				if (varAsynx.loading) varAsynx.loading(varAsynx.ajaxRequest.readyState, state);
				
				// Ao terminar de carregar
				if (varAsynx.ajaxRequest.readyState == 4) {
					// código de resposta HTTP OK
					if (varAsynx.ajaxRequest.status ==  200) {
						varAsynx._recuperarResultado(formato);
						if (carregarConteudo != null) {
							try {
								varAsynx.transformTemplate(no, formato);
							}catch (e) {
								varAsynx.exception(0, e);
							}		
						}
						varAsynx.callback();

					// qualquer resposta de erro, se tivermos um método de exceção definido
					} else if (varAsynx.exception) {
						varAsynx.exception(varAsynx.ajaxRequest.status, varAsynx.ajaxRequest.statusText);
					}
				}
			}
		}
	},
	_montarQueryString: function() {
		this._querystring = "";
		// varrendo a coleção de valores
		for (var indice in this._requestValues) {
			this._querystring += this._requestValues[indice][0] + "=" + encodeURIComponent(this._requestValues[indice][1]) + "&";
		}
		
		// Varrendo a coleção de objetos de requisição
		var regexArray = /.*?\[\]/; // para verificar se o name do select-multiple já tem os [], se não tiver, inserir
		for (var indice in this._requestObjects) {
			try {
				if (this._requestObjects[indice].type == 'select-multiple') {
					for (var i = 0; i < this._requestObjects[indice].options.length; i++) {
						if (this._requestObjects[indice].options[i].selected == true) {
							var novoNome = regexArray.test(this._requestObjects[indice].name) ? 
														   this._requestObjects[indice].name :
														   this._requestObjects[indice].name + '[]';
							this._querystring += novoNome + '=' + encodeURIComponent(this._requestObjects[indice].options[i].value) + '&'; 
						}
					}
				}
				else{
					this._querystring += this._requestObjects[indice].name + '=' + encodeURIComponent(this._requestObjects[indice].value) + '&';
				}
	
			} catch (e) {
				if (this._exception) {
					this.exception(0, 'The object ' + e + ' passed is not a valid form input object!');
				}
			}
		}
		this._querystring = this._querystring.substring(0, this._querystring.length-1);
	},
	_sobrescreverConfiguracoes: function(metodo, url, sincrono) {
		if (metodo != null) this.metodo = metodo;
		if (url != null) this.url = url;
		if (sincrono != null) this.sincrono = sincrono; 
	},
	_recuperarResultado: function(formato) {
		this.resposta = null;
		switch (formato) {
			case Asynx.PLAIN:
				this.resposta = unescape(this.ajaxRequest.responseText);
				break;
			case Asynx.XML:
				var parser;
				if (typeof DOMParser != "undefined") {
					parser = new DOMParser();
					this.resposta = parser.parseFromString(this.ajaxRequest.responseText, "application/xml");
				} else {
					var versoes = ['MSXML2.DOMDocument.5.0', 'MSXML2.DOMDocument.4.0',
								   'MSXML2.DOMDocument.3.0','MSXML2.DOMDocument',
								   'Microsoft.XMLDOM'];
					try {
						for (var x in versoes) {
							parser = new ActiveXObject(versoes[x]);
							parser.loadXML(this.ajaxRequest.responseText);
							this.resposta = parser;
							break;
						}
					} catch(e) {}
				}
				break;
			case Asynx.JSON:
				eval ("this.resposta = " + unescape(this.ajaxRequest.responseText));
				break;
		}
		this._querystring = null;
		
	},
	_jsonToXml: function() {
		// Recuperando a posicao inicial do array de resposta para ser o nó raiz
		var raiz, noRaiz;
		for (raiz in this.resposta) break;
		noRaiz = document.createElement(raiz);
		
	    var inserirNos = function recursiva(superNo, array) {
		    for (var x in array) {
		    	// Se x for número não será possível criar uma tag
		    	if (!isNaN(x)) continue;
				if (typeof array[x] != "object" && typeof array[x] != "array") {
			    	// Criando o elemento da tag
			    	var subNo = document.createElement(x);
		    		// Apenas escrever o nó texto
		    		subNo.appendChild(document.createTextNode(array[x]));
			    	// Inserindo o subNo no superNo
			    	superNo.appendChild(subNo);

		    	// se a tag tiver recursividade
		    	} else {
		    		if (array[x] != null && array[x].length == 0) break;
		    		for (var y in array[x]) break;
		    		if (isNaN(y)) {
				    	// Criando o elemento da tag
				    	var subNo = document.createElement(x);
				    	recursiva(subNo, array[x]);
				    	// Inserindo o subNo no superNo
				    	superNo.appendChild(subNo);
		    		} else {
			    		for (var y in array[x]) {
					    	// Criando o elemento da tag
					    	var subNo = document.createElement(x);
					    	recursiva(subNo, array[x][y]);
					    	// Inserindo o subNo no superNo
					    	superNo.appendChild(subNo);
			    		}
		    		}
		    	}
		    }
	    }
	    inserirNos(noRaiz, this.resposta[raiz]);
	    return noRaiz;
	}
}
