/**
 * Define una peticion de carga solicitada al gestor de flujo
 *
 * @param anUrl La URL de destino de la carga
 * @param aDestino El ID del tag de destino donde se pondran los datos de resultado, si se deja en blanco ("") o
 *                 el tag no existe, el resultado se dejara en la variable de texto resultante: textoResultado
 * @param aFuncion La funcion a ejecutar una vez recibidos los datos
 * @param aAsincrono Indica si la llamada sera asincrona
 */
function AjaxPeticion(anUrl, aDestino, aFuncion, anAsincrono) {
	this.url         = anUrl;       // URL de destino de la carga
	this.destino     = aDestino;    // ID del tag de destino, null si no lo hay
	this.postFuncion = aFuncion;    // Funcion post gestora, null si no la hay
	this.asincrona   = anAsincrono; // True si la peticion es asincrona
	this.resultado   = "";
}

/**
 * Define un procesador Ajax, con la peticion que esta atendiendo y su estado
 *
 * @param anId Identificador asignado al procesador
 * @param aLogger Logger sobre el que poner trazas (AjaxLog)
 */
function AjaxProcesador(anId, aLogger) {
	// Constantes que indican el estado del procesador ajax
	this.ESTADO_NO_INICIALIZADO = 0;
	this.ESTADO_CARGANDO        = 1;
	this.ESTADO_CARGADO         = 2;
	this.ESTADO_INTERACTIVO     = 3;
	this.ESTADO_COMPLETO        = 4;
	
	this.logger          = aLogger; // Logger sobre el que poner traz
	this.peticionEnCurso = null;    // Atributo que contiene la peticion que se esta atendiendo actualmente.
	this.procesador      = null;    // Objeto XmlHttpRequest encargado de realizar la transaccion
	this.id              = anId;    // Identificador del procesador
	
	// Metodos
	this.estaDisponible   = estaDisponible;
	this.realizarPeticion = realizarPeticion;
	this.stateChanged     = stateChanged;
	this.getXmlHttpObject = getXmlHttpObject;
	
	/**
	 * Indica si el procesador esta disponible para tratar una peticion
	 */
	function estaDisponible() {
		if (this.procesador == null || this.procesador.readyState == this.ESTADO_COMPLETO || this.procesador.readyState == "complete") {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Realiza la peticion de carga indicada
	 * @param aPeticion La peticion indicada (AjaxPeticion)
	 */
	function realizarPeticion(aPeticion) {	
		this.logger.log("Procesando peticion P" +  this.id + ": " +  aPeticion.url);	
		
		this.peticionEnCurso = aPeticion;

		this.procesador = this.getXmlHttpObject(_ajax_stateChanged);
		this.procesador.open("GET", aPeticion.url, aPeticion.asincrona);
		this.procesador.send(null);
	}
	
	/**
	 * Ejecuta las acciones necesarias una vez obtenida la respuesta a la peticion
	 */
	function stateChanged() {
		// Si el estado es 4 (completo)
		if (this.procesador != null && (this.procesador.readyState == this.ESTADO_COMPLETO || this.procesador.readyState == "complete")) {
			this.logger.log("Peticion atendida P" +  this.id);	
	
			// Se incrusta el contenido en el tag de destino, si existe
			var vDestino = document.getElementById(this.peticionEnCurso.destino);
			if (vDestino != null) {
		   	vDestino.innerHTML = this.procesador.responseText;
		   }

			// Ponemos el resultado en la peticion
			this.peticionEnCurso.resultado = this.procesador.responseText;

			// Si el trabajo terminado requiere la llamada a una funcion cuando se termine, se invoca
			if (this.peticionEnCurso.postFuncion != null) {
				eval(this.peticionEnCurso.postFuncion);
			}
			
			window.status = "Listo";
			this.peticionEnCurso = null;
			this.procesador = null;
	   }
	}
	
	/**
	 * Funcion que instancia el objeto XMLHttpRequest en funcion del navegador utilizado
	 *
	 * @param handler funcion que actuar de manejador de la respuesta dada por el motor
	 */
	function getXmlHttpObject(handler) { 
		var objXmlHttp = null;

		if (navigator.userAgent.indexOf("MSIE") >= 0) { 
	   	// INTERNET EXPLORER
			var ids = ["Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
	
			for(var i=0; i<ids.length; i++) {
			   try { 
			    	objXmlHttp = new ActiveXObject(ids[i]);
			    	objXmlHttp.onreadystatechange = handler;
			    	return objXmlHttp;
			   } catch(ex) { 
			   }
			}
	
	      alert("Error. Scripting for ActiveX might be disabled");
	      return;
		} else if ((navigator.userAgent.indexOf("Mozilla") >= 0) || (navigator.userAgent.indexOf("Opera") >= 0)) {
			// FIREFOX / NETSCAPE / OPERA
		   objXmlHttp = new XMLHttpRequest();
		   objXmlHttp.onload = handler;
		   objXmlHttp.onerror = handler; 
		   return objXmlHttp;
	   }
	}
}

/**
 * Gestor de flujo Ajax, controla y decide que peticiones se deben atender antes
 * y que procesador de la lista debe ocuparse de ello.
 *
 * @param aNumeroProcesadores Numero de procesadores disponibles para atender las peticiones de carga de datos
 */
function AjaxGestorFlujo(aNumeroProcesadores) {
	// Constantes
	this.PRIORIDAD_BAJA   = 0;
	this.PRIORIDAD_NORMAL = 1;
	this.PRIORIDAD_ALTA   = 2;
	
	this.logger                                = new AjaxLog(); // Logger para poner trazas
	this.procesadores                          = new Array();   // Lista de procesadores Ajax disponibles
	this.colaPeticiones                        = new Array(3);  // Cola de colas de peticiones
	this.colaPeticiones[this.PRIORIDAD_BAJA]   = new Array();
	this.colaPeticiones[this.PRIORIDAD_NORMAL] = new Array();
	this.colaPeticiones[this.PRIORIDAD_ALTA]   = new Array();
	this.ocupado                               = false;         // Flag que indica que el gestor de flujo esta ocupado
	this.contadorIds									 = 0;					// Contador de id de peticiones
	
	// Inicializamos los procesadores disponibles
	for (var iCnt = 0; iCnt != aNumeroProcesadores; iCnt++) {
		this.procesadores.push(new AjaxProcesador(iCnt, this.logger));
	}
		
	// Metodos
	this.cargarUrl = cargarUrl;
	this.procesarPeticiones = procesarPeticiones;
	this.stateChanged = stateChanged;
	
	/**
	 * Carga de una url mediante AJAX
	 *
	 * @param anUrl La URL a cargar
	 * @param aDestino El ID del tag donde depositar los datos cargados
	 * @param aFuncion Funcion JavaScript a invocar cuando el trabajo termine
	 * @param anAsincrono Indica si la peticion sera asincrona
	 */
	function cargarUrl(aPeticionAjax, aPrioridad) {		
		this.colaPeticiones[aPrioridad].push(aPeticionAjax);
		this.procesarPeticiones();
	}

	/**
	 * Funcion que lanza el siguiente trabajo que hay pendiente de lanzar en la lista de trabajos
	 */
	function procesarPeticiones() {
		var vProcesador = null;
		var vPeticion   = null;
	
		// Si el gestor de flujo no esta ocupado
		if (this.ocupado == false) {
			this.ocupado = true;

			try {			
				// Buscamos procesadores libres
				for (var iCnt = 0; iCnt != this.procesadores.length; iCnt++) {
					if (this.procesadores[iCnt].estaDisponible() == true) {
						vProcesador = this.procesadores[iCnt];
					}
				}
				
				// Si tenemos procesador
				if (vProcesador != null) {
					// Buscamos peticiones en las colas de peticiones, de prioridad mas alta a mas baja
					for (var iCnt = 2; iCnt >= 0; iCnt--) {
						// Miramos si hay peticiones por atender y procesadores libres
						if (this.colaPeticiones[iCnt].length > 0) {
							vPeticion = this.colaPeticiones[iCnt].shift();
							break;
						}
					}
					
					// Si tenemos alguna peticion que atender
					if (vPeticion != null) {
						window.status = "Abriendo pagina P" +  vProcesador.id + ": " + vPeticion.url;				
						vProcesador.realizarPeticion(vPeticion);
					}
				}
			} catch (ex) {
				this.logger.log("Error realizando la peticion");
			}
			
			this.ocupado = false;
		}
	}
	
	/**
	 * Funcion que se llamara cuando los datos esten disponibles
	 */
	function stateChanged() {
		// Buscamos el procesador que ha terminado la carga, si lo hay
		for (var iCnt = 0; iCnt != this.procesadores.length; iCnt++) {
			vProcesador = this.procesadores[iCnt].stateChanged();
		}
		
		this.procesarPeticiones();
	}
}

/**
 * Clase de log para el gestor Ajax
 */
function AjaxLog() {
	this.mensajesDebug = new Array(); // Lista de mensajes generados por la opcion de debug
	this.lineaActual   = 0;           // Linea actual en la que se encuentra el puntero de mensajes de log
	
	// Inicializacion, creacion del apartado de log en pantalla
	document.write("<span id='ajax_debug_div' style='border: 1px solid yellow; background-color: blue; padding: 2px; color:yellow; font-weight: bold; font-size:11px; position: absolute; display:");
	if (window.location.href.indexOf("ajaxDebug") != -1) {
		document.write("block");
	} else {
		document.write("none");
	}
	document.write("; top: 0; left: 0; z-index: 10'></span>");
	
	// Metodos
	this.setDebug = setDebug;
	this.log = log;
	
	/**
	 * Activa o desactiva el modo debug del motor Ajax
	 * @param aDebug Indica el modo de actuacion: "on" u "off"
	 */
	function setDebug(aDebug) {
		if (aDebug == "on") {
			document.getElementById("ajax_debug_div").style.display = "block";
		} else {
			document.getElementById("ajax_debug_div").style.display = "none";
		}
	}
	
	/**
	 * Escribe una traza de log en el visor del motor Ajax
	 * @param aTexto Texto a mostrar
	 */
	function log(aTexto) {
		var vDebugBox = document.getElementById("ajax_debug_div");
		if (vDebugBox != null && vDebugBox.style.display == "block") {
			this.mensajesDebug[this.lineaActual] = aTexto + "<br>";
			this.lineaActual = (this.lineaActual + 1) % 50;
		
			vDebugBox.innerHTML = this.mensajesDebug.toString();
		}
	}
}

// Instanciacion del motor e inicializacion
var _ajax = new AjaxGestorFlujo(4);

function _ajax_stateChanged() {
	_ajax.stateChanged();
}
