/**
 * @constant FRWK_JS_PATH Define la ruta al Framework
 */
var FRWK_JS_PATH = "/js/"

/**
 * Funcion para simular NameSpacing en JS
 * @function
 * @param {string} ns Nombre del Namespace
 * @example registerNameSpace("Despegar.SearchBox")
 */
function registerNameSpace(ns)
{
	var nsParts = ns.split(".");
	var root = window;

	for(var i=0; i<nsParts.length; i++)
	{
		if(typeof root[nsParts[i]] == "undefined")
		root[nsParts[i]] = new Object();

		root = root[nsParts[i]];
	}
}

/**
 * Carga un srcipt externo y lo ejecuta en forma sincronica
 * @function
 * @param {string} jsPath
 * @return void
 */
function loadJS(jsPath)
{

	xmlhttp=null;
	if (window.XMLHttpRequest)
	{// code for IE7, Firefox, Opera, etc.
		xmlhttp=new XMLHttpRequest();
	}
	else if (window.ActiveXObject)
	{// code for IE6, IE5
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}

	 headTag = document.getElementsByTagName("head")[0];
	 var scriptTag = document.createElement("script");
	 scriptTag.setAttribute("type", "text/javascript");
	 scriptTag.setAttribute("charset", "utf-8");

	if (xmlhttp!=null)
	{
		xmlhttp.open("GET",jsPath,false);
		//xmlhttp.setRequestHeader("Content-Type", "application/x-javascript; charset=UTF-8");

		xmlhttp.send(null);

		var scriptContent = "//START of '" + jsPath + "' file \n"
		scriptContent += xmlhttp.responseText;
		scriptContent += "//END of '" + jsPath + "' file \n";


		if(xmlhttp.status == 200)
		{
			scriptTag.text = scriptContent;
		}
		else
		{
			scriptTag.text = "//ERROR al cargar archivo '" + jsPath + "' (" + xmlhttp.statusText + ")\n"
		}
	}
	else
	{
		scriptTag.text = "//ERROR al cargar archivo '" + jsPath + "' (no se pudo instanciar XMLHttpRequest)\n"
	}
	headTag.appendChild(scriptTag);
}


/**
 * Parsea la el nombre de la clase, y retorna el path fisico
 * al archivo que la contiene
 * @param {Object} sClassName
 * @return {string} Path al archivo JS que contiene la clase
 */
function getJSClassPath(sClassName)
{
	 var sPath = "";
	 var aClassParts = sClassName.split(".");
	 for(var i = 0; i < aClassParts.length; i++)
	 {
		  sPath += aClassParts[i];
		  if(i<aClassParts.length - 1)
		  {
				sPath += "/";
		  }
	 }
	 sPath += ".js";
	 return FRWK_JS_PATH + sPath;
}

/**
 * Carga un archivo JS no definido en el Framework Generico
 * Creada principalmente para que sea compatible con el esquema
 * de post build que copia el contenido de los archivos
 * referenciados directamente en los paquetes
 *
 * @function
 * @param {string} jsPath Path relativo a la carpeta "custom" del framework, sin poner "/" al final.
 * @example loadJSClass("vuelos/resultado.js")
 * @requires loadJS
 */
function loadCustomJS(jsPath)
{
	 loadJS(FRWK_JS_PATH + "custom/" + jsPath);
}

/**
 * Carga una clase definida en un srcipt externo. Esta funcion
 * es compatible con el esquema de post build que copia el
 * contenido de los archivos referenciados directamente en los
 * paquetes
 *
 * @function
 * @param {string} ns Nombre completo de la clase
 * @example loadJSClass("Despegar.SearchBox.Box")
 * @requires loadJS
 * @requires getJSClassPath
 */
function loadJSClass(sClassName)
{
	 loadJS(getJSClassPath(sClassName));
}


/**
 * Logger de errores automatico
 * Este componente se bindea al window.onerror, y envia la informacion de los
 * errores JavaScript a un log en el server.
 * Se autoinstancia en todas las paginas que usan al frameworkJS, y permite agregar
 * informacion para el log usando el metodo errorHandler.addDebugInfo().
 *
 * Este metodo acepta un JSON de strings como parametro, y todo lo que se le pase
 * en este JSON, sera enviado al server con el resto de la informacion del error
 *
 * Ejemplos:
 * errorHandler.addDebugInfo({"nombre1":"valor1","nombre2":"valor2"});
* errorHandler.addDebugInfo({"Funcion":"MiFuncion()","Contexto":"Checkout"});
 *
 * @constructor errorHandler
 */
var errorHandler = function() {
	var LOG_SERVICE_URL = "/loggerjs";
	var INSTANCE_ID = Math.floor((900000000000)*Math.random()) + 100000000000;
	/**
	 * Pila para custom debug info
	 * @return {Object}
	 */
	var Stack = function() {
		var aStack = new Array();
		var stackPos = 0;
		return {
			/**
			 * Funcion privada que agrega info de debug al reporte de errores.
			 * @param {Object} options Objeto JSON con variables de tipo string, con las clave:valor a almacenar
			 */
			add:function(options) {
				options.StackPosition = stackPos;
				aStack.push(options);
				stackPos ++;
			},
			/**
			 * Funcion que limpia la pila de informacion de debug agregada con add
			 */
			clear:function() {
				aStack = new Array();
				stackPos = 0;
			},
			toString:function() {
				var str = "";
				var messageRepeatCount = 1;
				for(var i = aStack.length -1; i >= 0; i--){
					if(aStack[i].SentErrorMessage == undefined) {
						messageRepeatCount = 1;
						strCurrPos = "{";
						for(var prop in aStack[i]) {
							var stackType = typeof aStack[i][prop];
							if(stackType == "string" || stackType == "number" || stackType == "boolean"){
								strCurrPos += "\"" + prop + "\":\"" + aStack[i][prop] + "\",";
							} else {
								strCurrPos += "\"" + prop + "\":\"ERROR: Data type '" + typeof aStack[i][prop] + "' not allowed as debugInfo\",";
							}
						}
						if(strCurrPos[strCurrPos.length - 1] == ",") strCurrPos = strCurrPos.substring(0, strCurrPos.length -1);
						strCurrPos += "},";
						if(strCurrPos != "{},") str += strCurrPos;
					} else {
						//handling de las error sent
						if (i > 0 && aStack[i].SentErrorMessage != aStack[i - 1].SentErrorMessage) {
							//repite en la siguiente
							str += "{\"SentErrorMessage\":\"" + aStack[i].SentErrorMessage;
							if(messageRepeatCount > 1) str += " (" + messageRepeatCount + " repetitions)";
							str += "\"},";
							messageRepeatCount = 1;
						} else {
							//primer ocurrencia o ultima aparicion
							if(i==0) {
								str += "{\"SentErrorMessage\":\"" + aStack[i].SentErrorMessage;
								if(messageRepeatCount > 1) str += " (" + messageRepeatCount + " repetitions)";
								str += "\"},";
							}
							messageRepeatCount++;
						}
					}
				}

				if(str[str.length - 1] == ",") str = str.substring(0, str.length -1);
				str = "[" + str + "]";
				return str;
			}
		};
	}();

	/**
	 * Informacion generica brindada por el Browser
	 * @return {Object} Informacion obtenida del browser
	 */
	var UsrNavigator = function() {
		var oProps = new Object();
		for(var prop in navigator) {
			var propType = typeof navigator[prop];
			//Solo me interesan los siguientes tipos de informacion
			if(propType == "string" || propType == "number" || propType == "boolean")
				//Descarto esta info porque no aporta
				switch(prop) {
					case "javaEnabled":
					case "taintEnabled":
					case "preference":
					case "registerContentHandler":
					case "registerProtocolHandler":
					case "mozIsLocallyAvailable":
						break;
					default:
						oProps[prop] = navigator[prop].toString();
						break;
				}
		}

		return {
			props:oProps,
			toString:function(){
				var str = "";
				str += "{";
				for(var prop in oProps) {
					str += "\"" + prop + "\":\"" + oProps[prop].toString() + "\",";
				}
				if(str[str.length - 1] == ",") str = str.substring(0, str.length -1);
				str += "}";
				return str;
			}
		}
	}();

	/**
	 * Informacion basica del error
	 * @return {Object}
	 */
	var CurrentError = function() {
		return {
			curMsg:"",
			curUrl:"",
			curLineNumber:"",
			currLocationHref:window.location.href,
			set:function(msg, url, linenumber) {
				this.curMsg = msg;
				this.curUrl = url;
				this.curLineNumber = linenumber;
			},
			toString:function() {
				return "{\"Message\":\"" + this.curMsg + "\",\"Url\":\"" + this.curUrl + "\",\"LineNumber\":\"" + this.curLineNumber + "\",\"LocationHref\":\"" + this.currLocationHref + "\"}";
			}
		}
	}();
	/**
	 * Metodo que envia un string a un servicio en forma asincronica
	 * @param {Object} msg
	 */
	var send = function(msg) {
		if(typeof XMLHttpRequest!="undefined"){
			var oXmlHttp = new XMLHttpRequest();
			oXmlHttp.open("POST",LOG_SERVICE_URL,true);
			oXmlHttp.send(msg);
		}

	}

	return {
		/**
		 * Esta funcion se llama luego de hacer todo el handle del error capturado
		 * La idea es hacer un override de esta funcion si es necesario hacer algun
		 * tratamiento especial de los errores.
		 * Esta funcion, si retorna False, envia el error al browser, si retorna
		 * true, el error se considera atrapado, y el browser no vera el error.
		 *
		 * @param {string} msg Descripcion del error enviada por el browser
		 * @param {string} url Url de la pagina que genero el error
		 * @param {int} linenumber Linea donde ocurrio el error
		 */
		throwOverride:function(msg, url, linenumber) {
			return false;
		},
		/**
		 * Esta es la funcion que se bindea con el evento window.onerror
		 *
		 * @param {string} msg Descripcion del error enviada por el browser
		 * @param {string} url Url de la pagina que genero el error
		 * @param {int} linenumber Linea donde ocurrio el error
		 */
		handler:function(msg, url, linenumber) {
			CurrentError.set(msg, url, linenumber);
			send(this.toString());
			//Agrego marca de error enviado al stack
			Stack.add({"SentErrorMessage":msg});
			return this.throwOverride(msg, url, linenumber);
		},
		/**
		 * Funcion privada que agrega info de debug al reporte de errores.
		 * @param {Object} options Objeto JSON con variables de tipo string, con las clave:valor a almacenar
		 */
		addDebugInfo:Stack.add,
		/**
		 * Funcion que limpia la pila de informacion de debug agregada con addDebugInfo
		 */
		clearDebugInfo:Stack.clear,
		/**
		 * Sobreescribe el toString de Object, y retorna un string en formato JSON
		 * con toda la info del error
		 */
		toString:function() {
			var str = "{";

			str += "\"InstanceID\":" + INSTANCE_ID + ",";
			str += "\"ErrorInfo\":" + CurrentError.toString() + ",";
			str += "\"DebugInfo\":" + Stack.toString() + ",";
			str += "\"Navigator\":" + UsrNavigator.toString() + ",";

			if(str[str.length - 1] == ",") str = str.substring(0, str.length -1);
			str += "}";
			return str;
		},
		/**
		 * Retorna la info del error en formato HTML
		 */
		toHTML:function() {
			var oContainer = document.createElement("div");
			var str = '\
			<dl>\
					<dt>Informacion del error:</dt>\
					<dd>Mensaje: '+ CurrentError.curMsg +'</dd>\
					<dd>Linea: '+ CurrentError.curLineNumber +'</dd>\
					<dd>URL: '+ CurrentError.curUrl +'</dd>\
					<dt>Informacion del Browser:</dt>'
			for(prop in UsrNavigator.props) {
				str += '<dd>'+prop+': '+ UsrNavigator.props[prop] +'</dd>';
			}
			str+=  '<dt>Debug Info Stack:</dt>\
					<dd>\
						'+ Stack.toString() +'\
					</dd>\
			</dl>\
			';
			oContainer.innerHTML = str;
			return oContainer;
		}
	}
}();

window.onerror = function(msg, url, linenumber){
	return errorHandler.handler(msg, url, linenumber);
};
