/**
 * Copyright (c) 2007, SoftAMIS, http://soft-amis.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Alexey Luchkovsky
 * E-mail: jsiner@soft-amis.com
 *
 * Version: 1.024
 * Last modified: 15/05/2007
 */


var COMMONS = {
	version: 1.024,
	userAgent: navigator.userAgent.toLowerCase()
};

COMMONS.isIE = COMMONS.userAgent.indexOf('msie') > -1;

COMMONS.isOpera = COMMONS.userAgent.indexOf('opera') > -1;

COMMONS.isSafari = (!COMMONS.isOpera && COMMONS.userAgent.indexOf('safari') > -1);

COMMONS.isGecko = (!COMMONS.isOpera && !COMMONS.isSafari && COMMONS.userAgent.indexOf('gecko') > -1);

COMMONS.isWin32 = (COMMONS.userAgent.indexOf('windows') > -1);

COMMONS.isMacOs = (COMMONS.userAgent.indexOf('mac') > -1);

COMMONS.returnFalse = function returnFalse(){
	return false;
};

COMMONS.returnTrue = function returnTrue(){
	return true;
};

COMMONS.proxy = function(aValue){
	return aValue;
};

COMMONS.isObject = function(anObject){
	return anObject !== null && typeof(anObject) === "object";
};

COMMONS.isArray = function(anObject){
	return anObject instanceof Array;
};

COMMONS.isRegExp = function(anObject){
	return anObject instanceof RegExp;
};

COMMONS.isDate = function(anObject){
	return anObject instanceof  Date;
};

COMMONS.isNumber = function(anObject){
	return typeof(anObject) === "number";
};

COMMONS.isBoolean = function(anObject){
	return typeof(anObject) === "boolean";
};

COMMONS.isString = function(anObject){
	return typeof(anObject) === "string";
};

COMMONS.isFunction = function(anObject){
	return typeof(anObject) === "function";
};

COMMONS.isUndefined = function(anObject){
	return anObject === undefined;
};

COMMONS.isDefined = function(anObject){
	return anObject !== null && anObject !== undefined;
};

COMMONS.toInteger = function(aValue){
	var result = 0;
	if (aValue){
		result = this.isNumber(aValue) ? aValue : parseInt(aValue, 10);
	}
	return result;
};

COMMONS.toFloat = function(aValue){
	var result = 0;
	if (aValue){
		result = this.isNumber(aValue) ? aValue : parseFloat(aValue);
	}
	return result;
};

COMMONS.BOOLEAN_TRUE = {"true":true, "yes":true, "ok":true};

COMMONS.toBoolean = function(aValue){
	var result = false;
	if (aValue){
		result = this.isBoolean(aValue) ? aValue : this.BOOLEAN_TRUE[ aValue.toString().toLowerCase() ];
	}
	return result;
};

function Logger(aPrefix){
	/**
	 * The logger prefix used to identify logged class.
	 * @type String
	 */
	this.fPrefix = aPrefix;
}

Logger.TRACE = 20;

Logger.DEBUG = 50;

Logger.INFO = 70;

Logger.WARN = 80;

Logger.ERROR = 90;

Logger.FATAL = 100;

Logger.prototype.trace = function(aMessage, anException){
	this.log(Logger.TRACE, aMessage, anException);
};

Logger.prototype.debug = function(aMessage, anException){
	this.log(Logger.DEBUG, aMessage, anException);
};

Logger.prototype.info = function(aMessage, anException){
	this.log(Logger.INFO, aMessage, anException);
};

Logger.prototype.warning = function(aMessage, anException){
	this.log(Logger.WARN, aMessage, anException);
};

Logger.prototype.error = function(aMessage, anException, aMethod){
	this.log(Logger.ERROR, aMessage, anException, aMethod);
};

Logger.prototype.fatal = function(aMessage, anException, aMethod){
	this.log(Logger.FATAL, aMessage, anException, aMethod);
};

Logger.prototype.log = function(aType, aMessage, anException, aMethod){
	var txt = COMMONS.isDefined(this.fPrefix) ? "[" + this.fPrefix + "] " + aMessage : aMessage;
	if (COMMONS.isDefined(anException)){
		txt += ": " + anException.name + ", " + anException.message;
	}
	if (COMMONS.isDefined(aMethod)){
		txt += "\n" + aMethod.toString();
	}
	this.printLog(aType, txt);
};

Logger.prototype.printLog = function(aType, aMessage){
	if (aType > Logger.WARN){
		alert(aMessage);
	}
};

COMMONS.fLogger = new Logger("Common");

var JSINER = {
	
  scriptPrefix:"script/",
	scriptSuffix:".js",
	version: 1.0
};

JSINER.fDependency = {};

JSINER.fLogger = new Logger("JSINER");

JSINER.getConstructor = function(anObject){
	var result = null;
	if (COMMONS.isString(anObject)){
		result = window[anObject];
	}
	else
	if (COMMONS.isFunction(anObject)){
		result = anObject;
	}
	else
	if (COMMONS.isObject(anObject)){
		result = anObject.constructor;
	}
	return result;
};

JSINER.getType = function(anObject){
	var result = (typeof anObject);
	if (COMMONS.isObject(anObject)){
		if ( COMMONS.isRegExp(anObject) ){
			result = "RegExp";
		} else {
			var constr = this.getConstructor(anObject);
			if (COMMONS.isDefined(constr)){
				var cons = constr.toString();
				var index = cons.indexOf('(');
				result = ((index > 0) ? cons.substring(cons.indexOf(' ') + 1, index) : cons);
			}
		}	
	}
	return result;
};

JSINER.getScriptURI = function(aURL){
	var result = null;
	if ( COMMONS.isString(aURL) ){
		result = this.scriptPrefix;
		if ( /js[i|o]ner/.test(aURL) ){
			result += aURL;
		} else {
		  result += aURL.replace(/[.]/g, '/');
		}
		result = Transporter.addParameter(result + this.scriptSuffix, "ver", this.version );
	}
	return result;
};

JSINER.isScriptLoaded = function(aKey){
	var result = this.fScripts.isContains(aKey);
	if ( !result ){
		var uri = this.getScriptURI(aKey);
		var index = uri.indexOf('?');
		if ( index > 0 ){
			uri = uri.substring(0, index);
		}

		var scripts = document.getElementsByTagName("script");
		for (var i = 0; i < scripts.length; i++){
			var src = scripts[i].src;
			if ( src.indexOf(uri) >= 0 ){
				result = true;
				break;
			}
		}
	}
	this.fLogger.info("The script [" + aKey + "] are " + (result ? "loaded" : "not loaded"));
	return result;
};

JSINER.setDependency = function(aJson){
	this.fDependency = COMMONS.isDefined(aJson) ? aJson : {};
};

JSINER.addDependency = function(aJson){
	if (COMMONS.isDefined(aJson)){
		for (var name in aJson){
			var dependency = this.fDependency[name];
			if ( !COMMONS.isArray(dependency) ){
				this.fDependency[name] = [];
				dependency = this.fDependency[name];
			}

			var value = aJson[name];
			if ( COMMONS.isArray(value) ){
				for( var i=0; i < value.length; i++){
					dependency.push( value[i] );
				}
			} else {
				dependency.push( value );
			}
		}
	}
};

JSINER.getDependency = function(anObject, aClass){
	var type = this.getType(anObject);
	return this.fDependency[type];
};

JSINER.createInstance = function(aConstructor, aClass){
	var result = null;
	if (COMMONS.isFunction(aConstructor) && COMMONS.isFunction(aClass) && aClass !== Object){
		var oldPrototype = aConstructor.prototype;        	// stores object prototype properties
		var oldToString = aConstructor.prototype.toString; 	// stores toString property for IE only

		aConstructor.prototype = new aClass(); 		          // overrides constuctor prototype
		aConstructor.prototype.constructor = aConstructor;	// restores class constructor
		aConstructor.superClass = aClass.prototype;         // adds superclass property

		for (var name in oldPrototype) //restores old prototype properties
		{
			aConstructor.prototype[name] = oldPrototype[name];
		}
		aConstructor.prototype.toString = oldToString;
		
		aConstructor.$extend = true; // sets flag that the class is already tied
		result = new aConstructor(); // creates new instance of the class
	}
	return result;
};

JSINER.extend = function(anObject, aClass){
	var doExtend = function(anObject, aClass){
		var result = anObject;
		var cons = anObject.constructor;
		if ( COMMONS.isUndefined(cons.$extend) ){
			var dependency = this.getDependency(anObject, aClass);
			if ( COMMONS.isArray(dependency) ){
				var toolkit = this;
				this.inject(dependency, function(){
					var inheritClass = toolkit.getConstructor(aClass);
					result = toolkit.createInstance(cons, inheritClass) || result;
				});
			} else {
				var inheritClass = this.getConstructor(aClass);
				result = this.createInstance(cons, inheritClass) || result;
			}
		}
		return result;
	};

	var result = doExtend.call(this, anObject, aClass);
	var inheritClass = this.getConstructor(aClass);
	if ( COMMONS.isFunction(inheritClass) ){
		inheritClass.call(result);
	}
	return result;
};

JSINER.INTERCEPT_BEFORE = 0;

JSINER.INTERCEPT_AFTER = 1;

JSINER.INTERCEPT_INSTEAD = 2;

JSINER.INTERCEPT_ON_ERROR = 3;

JSINER.fMethods = new HashMap();

JSINER.registerInterceptor = function(aClass, aMethodName, aType, aMethod){
	var constr = this.getConstructor(aClass);
	if (constr !== null && COMMONS.isFunction(aMethod)){
		var key = this.getType(constr) + "." + aMethodName;
		var oldMethod = constr.prototype[aMethodName];
		if (!this.fMethods.isContains(key)){
			this.fMethods.put(key, oldMethod);
		}
		if (this.isUndefined(oldMethod)){
			aType = this.INTERCEPT_INSTEAD;
		}

		var newFunction = null;
		switch (aType){
			case this.INTERCEPT_BEFORE:
				newFunction = function(){
					aMethod.apply(this, arguments);
					return oldMethod.apply(this, arguments);
				};
				break;
			case this.INTERCEPT_AFTER:
				newFunction = function(){
					var result = oldMethod.apply(this, arguments);
					aMethod.apply(this, arguments);
					return result;
				};
				break;
			case this.INTERCEPT_INSTEAD:
				newFunction = function(){
					var result = aMethod.apply(this, arguments);
					return result;
				};
				break;
			case this.INTERCEPT_ON_ERROR:
				newFunction = function(){
					var result = null;
					try {
						result = oldMethod.apply(this, arguments);
					}catch(ex){
						result = aMethod.apply(this, arguments);
					}
					return result;
				};
				break;
			default:
				this.fLogger.error("register interceptor, unsupported type " + aType);
				break;
		}
		constr.prototype[aMethodName] = newFunction;
	} else {
		this.fLogger.error("register interceptor, unsupported arguments " + aClass );
	}
};

JSINER.unregisterInterceptor = function(aClass, aMethodName){
	var constr = this.getConstructor(aClass);
	if (constr !== null){
		var key = this.getType(constr) + "." + aMethodName;
		if (this.fMethods.isContains(key)){
			constr.prototype[aMethodName] = this.fMethods.get(key);
			this.fMethods.remove(key);
		} else {
			this.fLogger.warning("unregister interceptor, " + key + " never was registered.");
		}
	} else {
		this.fLogger.error("unregister interceptor, unable to obtain object constructor " + aClass);
	}
};

JSINER.getInfo = function(anObject, anAttributesOnly){
	var result = typeof(anObject);
	if (COMMONS.isObject(anObject)){
		result += "[" + this.getType(anObject) + "]\n";
		if ( !anAttributesOnly ){
			var properties = [];
			var value;
			for (var name in anObject){
				try {
					value = anObject[name];
					properties.push(COMMONS.isFunction(value) ? (name + "()") : (name + "=" + value));
				}catch(ex){
					properties.push(name);
				}
			}
			if (properties.length > 0){
				result += properties.sort().join(", ");
			}
		}

		if ( COMMONS.isArray(Object.attributes) ){
			var attributes = [];
			var attribute;
			try {
				for (var j = 0; j < anObject.attributes.length; j++){
					attribute = anObject.attributes[j];
					if (attribute.nodeValue !== null && attribute.nodeValue !== ''){
						attributes.push(attribute.name + "=" + attribute.nodeValue);
					}
				}
			}catch(ex){
			}
			if (attributes.length > 0){
				result += "\n [" + attributes.sort().join(", ") + "]";
			}
		}
	}
	return result;
};

function HashMap(anObject){
	this.fObject = anObject || {};
	this.fSize = 0;
	for(var name in this.fObject ){
		if ( this.fObject.hasOwnProperty(name) ){
		  this.fSize++;
		}	
	}
}

HashMap.prototype.isEmpty = function(){
	return this.getSize() === 0;
};

HashMap.prototype.getSize = function(){
	return this.fSize;
};

HashMap.prototype.get = function(aKey){
	return this.fObject[aKey];
};

HashMap.prototype.isContains = function(aKey){
	return COMMONS.isDefined(this.get(aKey));
};

HashMap.prototype.put = function(aKey, aValue){
	if (!this.isContains(aKey)){
		this.fSize++;
	}
	this.fObject[aKey] = aValue;
};

HashMap.prototype.remove = function(aKey){
  if ( this.isContains(aKey)){
		this.fObject[aKey] = undefined;
		delete this.fObject[aKey];

		if ( !this.isContains(aKey) ){
			this.fSize--;
		}
	}
};

HashMap.prototype.clear = function(){
	this.fSize = 0;
	this.fObject = {};
};

function KeySet(){
	var self = JSINER.extend(this, HashMap);
	for( var i = 0; i < arguments.length; i++ ){
		self.add(arguments[i]);
	}
	return self;
}

KeySet.prototype.add = function(aKey){
	KeySet.superClass.put.call(this, aKey, true);
};

KeySet.prototype.getSize = function(){
	return this.fSize;
};

JSINER.fScripts = new KeySet();

function Transporter(anOnLoad, aTaskID, aMethod, aCounter, anAsynch){
	this.fAsynch = COMMONS.isBoolean(anAsynch) ? anAsynch : true;
	this.fTaskID = aTaskID;
	this.fCounter = COMMONS.isNumber(aCounter) ? Math.max(aCounter, 0) : 1;
	this.fMethod = aMethod;
	this.onLoad = anOnLoad;
	this.fLogger = new Logger("Transporter");
	this.fTimeout = 1000;
	this.fURI = null;
	this.fQueryString = null;
	this.fTaskCounter = this.fCounter;
	this.fReq = null;
	this.fReqTaskID = null;
	this.fResponseHandlers = new HashMap();
	this.registerDefaults();
}

Transporter.fTaskSet = new KeySet();

Transporter.setTaskAlive = function(aTaskID, anAlive){
	if (COMMONS.isDefined(aTaskID)){
		if (anAlive){
			Transporter.fTaskSet.add(aTaskID);
		} else {
			Transporter.fTaskSet.remove(aTaskID);
		}
	}
};

Transporter.isLoaderAlive = function(aTaskID){
	var result = !Transporter.fTaskSet.isEmpty();
	return result;
};

Transporter.isTaskAlive = function(aTaskID){
	var result = false;
	if (COMMONS.isDefined(aTaskID)){
		result = Transporter.fTaskSet.isContains(aTaskID);
	}
	return result;
};

Transporter.addParameter = function(aURL, aParamName, aValue){
	var result = aURL;
	if ( COMMONS.isString(aURL) && COMMONS.isString(aParamName) && COMMONS.isDefined(aValue) ){
		var prefix = aParamName + "=";
		var index = aURL.indexOf(prefix);
		if ( index > 0 ){
			result = aURL.substring(0, index + prefix.length) + aValue;
			var lastIndex = aURL.indexOf("&", index + prefix.length);
			if ( lastIndex > 0 ){
				result += aURL.substring(lastIndex, aURL.length);
			}
		} else {
			result += (result.indexOf('?') > 0) ? "&" : "?" ;
			result += prefix + aValue;
		}
	}
	return result;
};

Transporter.ActiveX_TRANSPORT = ["Msxml2.XMLHTTP", "Microsoft.XMLHTTP", "Msxml2.XMLHTTP.5.0",
																"Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0"];

Transporter.prototype.getQueryString = function(anArray, aIndex){
	var result = null;
	var index = COMMONS.isNumber(aIndex) ? Math.max(aIndex, 0) : 0;
	if (COMMONS.isArray(anArray) && anArray.length >= index){
		result = anArray[index];
		if (anArray.length >= index){
			for (var i = index + 1; i < anArray.length; i++){
				result += ((i % 2 === 1) ? "=" : "&") + anArray[i];
			}
		}
	}
	return result;
};

Transporter.prototype.getDefaultHandler = function(){
	return this.errorHandler();
};

Transporter.prototype.getResponseHandler = function(aStatusCode){
	var result = this.fResponseHandlers.get(aStatusCode);
	if (!COMMONS.isDefined(result)){
		result = this.getDefaultHandler();
	}
	return result;
};

Transporter.prototype.registerDefaults = function(){
	this.setResponseHandeler(400, this.fatalErrorHandler);
	this.setResponseHandeler(500, this.fatalErrorHandler);
	this.setResponseHandeler(0, this.okHandler);
	this.setResponseHandeler(200, this.okHandler);
};

Transporter.prototype.setResponseHandeler = function(aStatusCode, aHandler){
	this.fResponseHandlers.put(aStatusCode, aHandler);
};

Transporter.prototype.customizeHeaders = function(aRequest){
	var contentType = (this.fMethod === "POST") ? 'application/x-www-form-urlencoded;charset="utf-8"' :
	                                              'text/xml;charset="utf-8"';
	aRequest.setRequestHeader('Content-type', contentType);
	if ( COMMONS.isGecko ){
		aRequest.setRequestHeader('Connection', 'close');
  }
};

Transporter.prototype.request = function(aURI, aMethod, aQuery){
	this.fURI = aURI;
	this.fTaskCounter = this.fCounter;
	this.fQueryString = aQuery;
	this.fMethod = aMethod;
	Transporter.setTaskAlive(this.fTaskID, true);
	try {
		this.doLoadData();
	}catch (ex){
		this.fLogger.warning("doLoadData error happened", ex);
	}
};

Transporter.prototype.sendData = function(aURI){
	var query = this.getQueryString(arguments, 1);
	this.request(aURI, "POST", query);
};

Transporter.prototype.loadData = function(aURI){
	var query = this.getQueryString(arguments, 1);
	this.request(aURI, "GET", query);
};

Transporter.prototype.doLoadData = function(){
	if (window.XMLHttpRequest){
		this.fReq = new XMLHttpRequest();
	}
	else
	if (window.ActiveXObject){
		if (COMMONS.isDefined(Transporter.ActiveX_TRANSPORT.transport)){
			this.fReq = new ActiveXObject(Transporter.ActiveX_TRANSPORT.transport);
		} else {
			var value;
			for (var i = 0; i < Transporter.ActiveX_TRANSPORT.length; i++){
				value = Transporter.ActiveX_TRANSPORT[i];
				try {
					this.fReq = new ActiveXObject(value);
					Transporter.ActiveX_TRANSPORT.transport = value;
					break;
				}catch(ex){
				}
			}
		}
	}

	if ( COMMONS.isDefined(this.fReq) ){
    this.fProcessed = false;

		var transport = this;
		this.fReq.onreadystatechange = function(){
			transport.onReadyState.call(transport);
		};

		try {
			this.fReq.open(this.fMethod, this.fURI, this.fAsynch);
			this.customizeHeaders(this.fReq);
			this.fReq.send(this.fQueryString);
			this.fLogger.info("Open request: " + this.fURI + (this.fQueryString ? "?" + this.fQueryString : ""));
			
			if ( !this.fAsynch && COMMONS.isGecko && this.fReq.readyState === 4 ){
			  this.onReadyState();
			}
		}catch (ex){
			this.fatalErrorHandler();
		}
	} else {
		this.fLogger.error("Browser not supported XMLHttpRequest");
	}
};

Transporter.prototype.onReadyState = function(){
	var ready = this.fReq.readyState;
	if (ready === 4){
		if ( !this.fProcessed ){
			var handler = this.getResponseHandler(this.fReq.status);
			handler.call(this);
			this.fReq.onreadystatechange = COMMONS.returnTrue;
			this.fProcessed = true;
		}
	}
};

Transporter.prototype.okHandler = function(){
	this.fLogger.info("Request successfully: " + this.fURI);
	if (COMMONS.isFunction(this.onLoad)){
		try {
			this.onLoad.call(this);
		}catch(ex){
			this.fLogger.error("Callback error: " + this.fURI, ex, this.onLoad);
		}
	}
	Transporter.setTaskAlive(this.fTaskID, false);
};

Transporter.prototype.fatalErrorHandler = function(){
	if (this.fReqTaskID !== null){
		window.clearTimeout(this.fReqTaskID);
		this.fReqTaskID = null;
	}
	Transporter.setTaskAlive(this.fTaskID, false);
	if (COMMONS.isDefined(this.fReq)){
		this.fLogger.error(this.fReq.status + ", request " + this.fURI + " error. Headers: " +
		                   this.fReq.getAllResponseHeaders());

		this.fReq.onreadystatechange = COMMONS.returnTrue;
		this.fReq.abort();
	}
};

Transporter.prototype.errorHandler = function(){
	if (this.fReqTaskID !== null){
		window.clearTimeout(this.fReqTaskID);
		this.fReqTaskID = null;
	}
	if (this.fTaskCounter <= 0){
		Transporter.setTaskAlive(this.fTaskID, false);
		this.fLogger.error(this.fReq.status + ", request  " + this.fURI + " error. Headers: " +
		                   this.fReq.getAllResponseHeaders());
	} else {
		var transporter = this;
		this.fTaskCounter = this.fTaskCounter - 1;
		this.fReqTaskID = window.setTimeout(function(){
			transporter.doLoadData();
		}, this.fTimeout);
		this.fLogger.info(this.fReq.status + ", trying to request " + this.fURI + " again....");
	}
};

Transporter.prototype.getResponsedText = function(){
	var result = null;
	if (COMMONS.isDefined(this.fReq)){
		result = this.fReq.responseText;
	}
	return result;
};

Transporter.prototype.getResponsedXML = function(){
	var result = null;
	if (COMMONS.isDefined(this.fReq)){
		try {
			result = this.fReq.responseXML;
		}catch(ex){
			this.fLogger.error("Unable to parse responsed XML", ex);
		}
	}
	return result;
};

JSINER.inject = function(aDependency, aCallBack){
	var getUri = function(aLoader, aDependency){
		var result = null;
		for (var i = aLoader.fIndex; i < aDependency.length; i++){
			var link = aDependency[aLoader.fIndex];
			if (!JSINER.isScriptLoaded(link)){
				result = JSINER.getScriptURI(link);
				if (result !== null){
					aLoader.fIndex = i;
					break;
				}
			}
		}
		return result;
	};

	if ( COMMONS.isDefined(aDependency) ){
		if ( !COMMONS.isArray(aDependency) ){
			aDependency = [aDependency];
		}

		var loader = new Transporter(function(){
			var code = this.getResponsedText();
			if (window.execScript){
				window.execScript(code, 'javascript');
			}
			else
			if (COMMONS.isSafari){
				var heads = document.getElementsByTagName('head');
			  var head = heads.length > 0 ? heads[0] : document.body;

				var script = document.createElement('script');
				script.type = 'text/javascript';
				script.innerHTML = code;
				head.appendChild(script);
			} else {
				window.eval(code);
			}

			JSINER.fScripts.add(aDependency[this.fIndex]);
			this.fIndex++;

			var uri = getUri(this, aDependency);
			if (uri !== null ){
				this.loadData(uri);
			}
			else
			if ( COMMONS.isFunction(aCallBack) ){
				aCallBack();
			}
		});
		loader.fAsynch = false;
		loader.fIndex = 0;

		var uri = getUri(loader, aDependency);
		if ( uri !== null){
			loader.loadData(uri);
		}
		else
		if ( COMMONS.isFunction(aCallBack) ){
			aCallBack();
		}
	}
};