/**
 * This file is a part of Javascript ToolKit
 * 
 * Javascript ToolKit is a cross-browser javascript/DOM framework 
 * used to build rich web-based client interfaces with basic controls 
 * (dialog, button, imageButton, ...), complex controls 
 * (htmlEditor, colorChooser, grid, ...) and RPC components.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 * Copyright (C) 2005 Frédéric LECOINTRE <frederic.lecointre@burnweb.net>
 *
 * $Id: jtkXMLRpc.js,v 1.2 2005/04/07 18:05:35 gabriel_ Exp $ 
 */

/**
 * Modified from original file by Frédéric LECOINTRE  2007/08
 */
 
window.jtkVersion = '1.0 modified 1'

/** 
 * @class jtkXMLRPCRequest
 * @package rpc
 * @version 1.0.0 $Date: 2005/04/07 18:05:35 $ $Revision: 1.2 $
 * @author: Frédéric LECOINTRE<frederic.lecointre@burnweb.net>
 * 
 */
jtkXMLRPCRequest = function (method, host, path, port){

	this._asynchronous = false;
	this.initialize();
	
	if(host){
		this.setHost(host);
	}//end if
	
	if(path){
		this._path = path;
	}//end if

	if(port){
		this._port = Math.getInRange(port, 1, 65536);
	}//end if
	
	if(method){
		this._method = method;
	}//end if
	
	this._userAgent = 'jtkXMLRpc ' + window.jtkVersion + '/' + window.browser.agent + ')';
	
}//end function

jtkXMLRPCRequest.prototype._userAgent = 'jtkXMLRpc';
jtkXMLRPCRequest.prototype._transport = null;
jtkXMLRPCRequest.prototype._host = null;
jtkXMLRPCRequest.prototype._host = '';
jtkXMLRPCRequest.prototype._port = 80;
jtkXMLRPCRequest.prototype._listener = null;
jtkXMLRPCRequest.prototype._asynchronous = false;
jtkXMLRPCRequest.prototype._state = 0;
jtkXMLRPCRequest.prototype._method = null;
jtkXMLRPCRequest.prototype._parameters = null;
jtkXMLRPCRequest.prototype._response = null;
jtkXMLRPCRequest.prototype._isFault = false;
jtkXMLRPCRequest.prototype.faultCode = 0;
jtkXMLRPCRequest.prototype.faultString = '';
jtkXMLRPCRequest.prototype.response = '';

var __jtkXMLRPCRequestEnumState = 0x00;

jtkXMLRPCRequest.prototype.UNINITIALIZED = __jtkXMLRPCRequestEnumState++;
jtkXMLRPCRequest.prototype.LOADING = __jtkXMLRPCRequestEnumState++;
jtkXMLRPCRequest.prototype.LOADED = __jtkXMLRPCRequestEnumState++;
jtkXMLRPCRequest.prototype.INTERACTIVE = __jtkXMLRPCRequestEnumState++;
jtkXMLRPCRequest.prototype.COMPLETED = __jtkXMLRPCRequestEnumState++;
jtkXMLRPCRequest.prototype.ERROR = __jtkXMLRPCRequestEnumState++;

/**
 * 
 * @param boolean asynchronous
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setAsynchronous = function(asynchronous){
	this._asynchronous = (asynchronous || (asynchronous == null))? true : false;
}//end function

/**
 * 
 * @return boolean
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.isAsynchronous = function(bool){
	return this._asynchronous;
}//end function

/**
 * 
 * @param mixed parameter
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setParameter = function(parameter){
	this._parameters[this._parameters.length] = this.XMLConvert(parameter);
}//end function

/**
 * 
 * @param integer id
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getParameter = function(id){
	if(this._parameters != undefined){
		return this._parameters[id];
	}//end if
}//end function

/**
 * 
 * @param integer id
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.unsetParameter = function(id){
	if(this.parameters != undefined){
		delete this.parameters[id];
	}//end if
}//end function

/**
 * 
 * @param string method
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setMethod = function(method){
	this._method = method;
}//end function

/**
 * 
 * @return string
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getMethod = function(){
	return this._method;
}//end function

/**
 * 
 * @param string host
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setHost = function(host){

	var urlPatten = /^http:\/\//i;
	
	if(!urlPatten.test(host)){
		this._host = 'http://' + host;
	}//end if
	else{
		this._host = host;
	}//end else
	
}//end function

/**
 * 
 * @param string path
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getPath = function(){
	return this._path;
}//end function

/**
 * 
 * @param string path
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setPath = function(path){
	this._path = path;
}//end function

/**
 * 
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getUserAgent = function(){
	return this._userAgent;
}//end function

/**
 * 
 * @param string userAgent
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setUserAgent = function(userAgent){
	this._userAgent = userAgent;
}//end function

/**
 * 
 * @return string
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getHost = function(){
	return this._host;
}//end function

/**
 * 
 * @param string port
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.setPort = function(port){
	this._port = Math.getInRange(port, 1, 65536);
}//end function

/**
 * 
 * @return integer
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getPort = function(){
	return this._port;
}//end function

/**
 * 
 * @return integer
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getState = function(){
	return this._state;
}//end function

/**
 * @param integer state
 * @return string
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.getStateText = function(state){
	
	if(state == undefined)
		state = this._state;
		
	switch(state){
		case this.UNINITIALIZED : return 'UNINITIALIZED';
		case this.LOADING : return 'LOADING';
		case this.LOADED : return 'LOADED';
		case this.INTERACTIVE : return 'INTERACTIVE';
		case this.COMPLETED : return 'COMPLETED';
		case this.ERROR : return 'ERROR';
		default : return 'UNKNOWN STATE';
	}//end switch
	
}//end function

/**
 * 
 * @return boolean
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.isFault = function(){
	return this._isFault;
}//end function

/**
 * 
 * @param boolean Asynchronous
 * @return boolean
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.execute = function(asynchronous){

	if(asynchronous != null){
		this._asynchronous = (asynchronous)? true : false;
	}//end if
	
	this._transport = new jtkXMLHttpRequest();
	this._transport.setAsynchronous(this._asynchronous);
	this._transport.addListener('requestListener', this);
	
	if(!this._transport.open(this._host + '/' + this._path, this._transport.POST)){
		return false;
	}//end if
	
	this._transport.setHeader('User-agent', this._userAgent);
	this._transport.setHeader('Content-Type', 'text/xml');
	
	if(!this._transport.send(this._createMessage())){
		return false;
	}//end if
	
	return true;
	
}//end function

/**
 * 
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.initialize = function(){
	this._asynchronous = false;
	this._state = this.UNINITIALIZED;
	this._parameters = new Array();
	this._listener = null;
	this._response = null;
	this._isFault = false;
	this.faultCode = null;
	this.faultString = '';
	this.response = undefined;
}//end function

/**
 * 
 * @param jtkXMLHTTPRequest xhr
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.requestListener = function (xhr){
   
	switch(xhr.getState()){
		case xhr.COMPLETED:

			if(xhr.getStatus() != 200){
				this._haveFault('Unable to execute procedure. server return: (' + xhr.getStatus() + ') ' + xhr.getStatusText(), -1);
				this._state = xhr.ERROR;
			}//end if
			else{
				// for ie responseXML.firstChild is xml and for gecko is methodResponse ??
				try{
					this._parseResponse(xhr.getResponseXML().getElementsByTagName('methodResponse')[0]);
					this._state = xhr.COMPLETED;	
				}catch(e){
					this._state = xhr.ERROR;
					this._haveFault('Unable to execute procedure. ' + e.message, -1);
				}//end try
					
			}//end else

			//this._transport = null;
		break;
		case xhr.ERROR:
			this._state = xhr.ERROR;
			this._haveFault('Unable to execute procedure. ' + xhr.lastError, -1);
			this._transport = null;
		break;
		default:
			this._state = xhr.getState();
	}//end swicth
    
	this.callListener();
	
}//end function

/**
 * 
 * @param string handler
 * @param jtkObject obj
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.addListener = function(handler, obj){
	this._listener = (obj)? new Array(obj, handler) : new Array(null, handler);
}//end function

/**
 * 
 * @param string id
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.removeListener = function(){
	this._listener = null;
}//end function

/**
 * 
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.callListener = function(){
	if(this._listener != null){
		try{
			callFunction(this._listener[1], this._listener[0], Array(this));
		}//end try
		catch(e){}
	}//end if
}//end function

/**
 * 
 * @param string message
 * @param integer code
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype._haveFault = function(message, code){
	this._isFault = true;
	this.faultCode = code;
	this.faultString = message;
}//end function

/**
 * 
 * @param DOMdocument response
 * @return void
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype._parseResponse = function(response){

	var i, node, value, retval;
	
	//alert(this._transport.getResponseText());

	if(response){
	
		if(response.getElementsByTagName('fault').length ==1){

			node = response.getElementsByTagName('fault')[0];
			
			for(i = 0; i < node.childNodes.length; i++){
				if(node.childNodes[i].nodeType == 1){
					value = node.childNodes[i];
					break;
				}//end if
			}//end for
			
			retval = this.JSConvert(value);
			this._haveFault(retval['faultString'], retval['faultCode']);	
		}//end if
		else{
			this.response = this.JSConvert(response.getElementsByTagName('value')[0]);	
		}//end else
	}//end if
	else{
		throw Error("Following response is not a valid XML document :\n" + this._transport.getResponseText());
	}//end else

}//end function

/**
 * 
 * @return string
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype._createMessage = function(){
	
	var message = new Array();
	message[message.length] = '<'+'?xml version="1.0" ?>';
	message[message.length] = '<methodCall>';
	message[message.length] = '<methodName>' + this._method + '</methodName>';
	message[message.length] = '<params>';

	var id;
	for (id in this._parameters){
		if(this._parameters[id] != null){
			message[message.length] = '<param>';
			message[message.length] = '<value>' + this._parameters[id] + '</value>';
			message[message.length] = '</param>';
		}//end if
	}//end for
	
	message[message.length] = '</params>';
	message[message.length] = '</methodCall>';

	//alert(message.join("\n"));
	
	return message.join("\n");
	
}//end function

/**
 * 
 * @param DOMnode node
 * @return mixed
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.JSConvert = function (node){

	if(node.nodeName != 'value'){
		return null;
	}//end if
	
	var i; var child = null; var retval = null;
	
	for(i = 0; i < node.childNodes.length; i++){
		if(node.childNodes[i].nodeType == 1){
			child = node.childNodes[i];
			break;
		}//end if
	}//end for

	if(child == null){
		this._haveFault('Invalid XML-RPC value', -1);
		return null;
	}//end if

	switch(child.nodeName){
	
		case 'i4':
		case 'int':
			retval = Math.parseIntEx(child.firstChild.nodeValue);
		break;
		case 'double':
			retval = Math.parseFloatEx(child.firstChild.nodeValue);
		break;
		case 'boolean':
			retval = (Math.parseIntEx(child.firstChild.nodeValue) == 1) ? true: false;
		break;
		case 'string':
			retval = new String(child.firstChild.nodeValue);
		break;
		case 'base64':
			retval = Base64ToString(child.firstChild.nodeValue);
		break;
		case 'dateTime.iso8601':	
			retval = ISO8601ToDate(child.firstChild.nodeValue);
		break;
		break;
		case 'array':
		
			var dataNode = child.getElementsByTagName('data')[0];
			var values = child.getElementsByTagName('value');

			retval = new Array();

			for(i = 0; i < values.length; i++){
				if(values[i].parentNode == dataNode){
					retval[i] = this.JSConvert(values[i]);
				}//end if
			}//end if

			values = null;

		break;
		case 'struct':

			var values = child.getElementsByTagName('member');
			var member, name, j;
			
			retval = new Object();
			
			for(i = 0; i < values.length; i++){
			
				if(values[i].parentNode == child){

					for(j = 0; j < values[i].childNodes.length; j++){

						if(values[i].childNodes[j].nodeType == 1){

							if(values[i].childNodes[j].nodeName == 'name'){
								name = values[i].childNodes[j].firstChild.nodeValue;
							}//end if
							else if(values[i].childNodes[j].nodeName == 'value'){
								retval[name] = this.JSConvert(values[i].childNodes[j]);
							}//end elseif
				
						}//end if

					}//end for

				}//end if
				
			}//end for

			//values = null;
		break;
		default:
			this._haveFault('Invalid XML-RPC value', -1);
	}//end swicth
	
	return retval;
	
}//end function

/**
 * 
 * @param DOMnode node
 * @return mixed
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.JSStructMemberConvert = function (node){

	if(node.nodeName != 'member'){
		return null;
	}//end if
	
	var i, child = null, retval = new Array();
	
	retval['name'] = null;
	retval['value'] = null;
	
	for(i = 0; i < node.childNodes.length; i++){
		if(node.childNodes[i].nodeType == 1){
			
			if(node.childNodes[i].nodeName == 'name'){
				retval['name'] = node.childNodes[i].firstChild.nodeValue;
			}//end if
			else if(node.childNodes[i].nodeName == 'value'){
				retval['value'] = this.JSConvert(node.childNodes[i]);
			}//end elseif

		}//end if
	}//end for

	return retval;
	
}//end function
/**
 * 
 * @param mixed value
 * @return string
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.XMLConvert = function (value){

	var retval;
	
	try{
		switch(this.XMLTypeOf(value)){
			
			case 'i4':
			case 'int':
			case 'double':
				if (Math.round(value) == value){
					retval = '<int>' + String(value) + '</int>';
				}//end if
				else{
					retval = '<double>' + String(value) + '</double>';
				}//end else
			break;
			case 'boolean':
				retval = '<boolean>' + ((value == true)? '1' : '0') + '</boolean>';
			break;
			case 'string':
				retval = '<string>' + value + '</string>';
			break;
			case 'date':
				retval = '<dateTime.iso8601>' + DateToISO8601(value) + '</dateTime.iso8601>';
			break;
			break;
			case 'array':
				var i, data;
				retval = new Array();
				
				retval[retval.length] = '<array>';
				retval[retval.length] = '<data>';
				
				for (i in value){
					data = this.XMLConvert(value[i]);
					
					if(data != null){
						retval[retval.length] = '<value>' + data + '</value>';
					}//end if
					
				}//end for
				
				retval[retval.length] = '</data>';
				retval[retval.length] = '</array>';
				
				return retval.join("\n");
			break;
			case 'struct':
				var i, data;
				retval = new Array();
				
				retval[retval.length] = '<struct>';
			
				for (i in value){
					data = this.XMLConvert(value[i]);
					
					if(data != null){
						retval[retval.length] = '<member>';
						retval[retval.length] = '<name>' + i + '</name>';
						retval[retval.length] = '<value>' + data + '</value>';
						retval[retval.length] = '</member>';
					}//end if
					
				}//end for
				
				retval[retval.length] = '</struct>';
				
				return retval.join("\n");
			break;
			default:
				retval = null;
		}//end switch
	}//end try
	catch(e){
		throw Error('XMLRPC convertion exception: ' + e.message);
	}//end catch
	
	return retval;
	
}//end function

/**
 * 
 * @param mixed value
 * @return string
 * @since 1.0.0
 */
jtkXMLRPCRequest.prototype.XMLTypeOf = function (value){

	var type = typeof(value);
	type = type.toLowerCase();

	switch(type){
	
		case 'number':
			if (Math.isInt(value)){
				type = 'int';
			}//end if
			else{
				type = 'double';
			}//end else
		break;
		case 'string':
			type = 'string';
		break;
		case 'object':

			switch(value.constructor){
				case Date: type = 'date'; break;
				case Array: type = 'array'; break;
				default: type = 'struct';
			}//end swicth

		break;
		case 'function':
		default:
			throw Error('Cannot use "' + value + '"(' + type + ') as XML-RPC parameter');
		break;
	}//end switch
		
	return type;
	
}//end function