/**
*  xmlrpc.js
*
*  @author James Sas
*
*  Last Checked In By $Author: james $
*  $Date: 2007/11/15 20:09:46 $
*  $Revision: 1.16 $
*
*  (c) 2004 Copyright Basis Applied Technology
*  CONFIDENTIAL INFORMATION
*  All rights are reserved. Copying or other reproduction of
*  this program except for archival purposes is prohibited
*  without the prior written consent of Basis Applied Technology
*
*  Basis Applied Technology
*  Gibsons, BC
*  604.886.0721
*/

app={};
app.isNull     = function(n)  {return n==null || !String(n).length};
app.rInt       = function(n,d){return(app.isNull(n)||isNaN(n))?((app.isNull(d)||isNaN(d))?0:d):parseInt(n)};
app.rFloat     = function(n,d){return(app.isNull(n)||isNaN(n))?((app.isNull(d)||isNaN(d))?0:d):parseFloat(n)};
app.isFunction = function(a) { return typeof a == 'function';};
app.isObject   = function (a) { return (a && typeof a == 'object') || app.isFunction(a); };
app.isArray    = function(a) { return app.isObject(a) && a.constructor == Array;};
app.isA        = function(e,t) { if (e && app.isArray(e.CLS_TYPE) && e.CLS_TYPE.indexOf(t)>-1) { return true; } return false; };
app.rVal       = function(s,d){return(app.isNull(s)?(app.isNull(d)?"":d):s)};                                 // return value if not nil else default
app.luid       = function()   {return "L"+new Date().getTime()+Math.floor(1000 * Math.random());};
app.isIE       = core.isIE;

var Functor = function(obj, fn, args) {
    this.obj = obj;
    this.fn = fn;
    this.args = app.isArray(args)?args:[];
};
Functor.prototype.fire = function(args) {
    return this.fn.apply(this.obj, this.args.concat(app.isArray(args)?args:[]));
}
Functor.isFunctor = function(f) {
    return f instanceof Functor;
};

function List(){this.clear();};
List.prototype.getLength = function(){return this.keys.length};
List.prototype.clear = function(){this.keys  = [];this.values = [];};
List.prototype.add = function(n,v){var t = this.rPair(n,v);this.keys.push( t[0]);this.values.push(t[1]);};
List.prototype.insert = function(i,n,v){var t = this.rPair(n,v);this.keys.insert( i,t[0]);this.values.insert(i,t[1]);};
List.prototype.deleteItem = function(i){this.keys.deleteItem( i);this.values.deleteItem(i);};
List.prototype.deleteKey = function(n){for (var i=0;i<this.keys.length;i++){if (this.keys[i] == n){this.deleteItem(i);return true;}}return false;};
List.prototype.reverse = function(){this.keys.reverse();this.values.reverse();};
List.prototype.swapItems = function(i,j){var t;t = this.keys[i];this.keys[i]  = this.keys[j];this.keys[j]  = t;t = this.values[i];this.values[i] = this.values[j];this.values[j] = t;};
List.prototype.sortByName = function(){this._sort(0);};
List.prototype.sortByValue = function(){this._sort(1);};
List.prototype._sort = function(k){var b,n1,n2;for(var a=0;a<this.keys.length-1;a++)for(b=a+1;b<this.keys.length;b++){n1 = k?this.values[a]:this.keys[a];n2 = k?this.values[b]:this.keys[b];if(n1>n2)this.swapItems(a,b);}};
List.prototype.findNameByValue = function(s){var i = this.values.indexOf(s);return i==-1?null:this.keys[i];};
List.prototype.findValueByName = function(s){var i = this.keys.indexOf(s);return i==-1?null:this.values[i];};
List.prototype.valueAtIndex = function(i){return this.values[i];};
List.prototype.nameAtIndex = function(i){return this.keys[i];};
List.prototype.rPair = function(n,v){if(typeof v != "undefined")return [n,v];var t = n.split("=");if(t.length!=2)return [n,n];return [t[0],t[1]];};


//////////////////////////////////////////////////////////////////////////////////////////////
// xmlrpc fault implementation.

var XMLRPCFault = function(code, string, cls){this.faultCode = code;this.faultString = string;this.faultClass = cls;return this;};
XMLRPCFault.prototype = new Object();
XMLRPCFault.prototype.toString = function(){return "error number:"+this.faultCode+"\r\n"+this.faultString;};

//////////////////////////////////////////////////////////////////////////////////////////////
// static xmlrpc utility interface.

app.xmlrpc = {};
app.xmlrpc.doDebug = false;
app.xmlrpc.timeout = 30000;
app.xmlrpc.proxies = {};
app.xmlrpc.USE_CDATA_STRINGS = true;

var initializeIEXMLRPC = function() {
    if(core.isIE) {
        app.xmlrpc.IERequestObject = null;
        var idXmlHttpList = ["Msxml2.XMLHTTP.7.0", "Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
        for (var i=0; i < idXmlHttpList.length; i++)
        {try { var oDoc = new ActiveXObject(idXmlHttpList[i]); app.xmlrpc.IERequestObject = idXmlHttpList[i]; break;} catch (e){;}}
        app.xmlrpc.IEDOMParser = null;
        var idXmlList = ["Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"];
        for (var i=0; i < idXmlList.length; i++)
        {try{ var oDoc = new ActiveXObject(idXmlList[i]); app.xmlrpc.IEDOMParser = idXmlList[i]; break;} catch (e){}}
    }
}
initializeIEXMLRPC();
delete initializeIEXMLRPC;

app.xmlrpc.getXmlHttpRequest = function() {
    var xmlhttp;
    try { xmlhttp = (core.isIE ? new ActiveXObject(app.xmlrpc.IERequestObject) : new XMLHttpRequest());
    } catch(e) { xmlhttp=null; }
    return xmlhttp;
};
app.xmlrpc.getDomParser = function(xml) {
    var dom;
    try {
        if (core.isIE) {
            dom = new ActiveXObject(app.xmlrpc.IEDOMParser);
            if (arguments.length > 0)
            { dom.loadXML(xml); }
        } else {
            dom = new DOMParser();
            dom = dom.parseFromString(xml, "text/xml");
        }
    } catch(e) { dom=null; }
    return dom;
};
app.xmlrpc.FaultFromString = function(code, desc, cls) {
    return new XMLRPCFault(code, desc, cls);
};
app.xmlrpc.FaultFromException = function(err) {
    code = err.number ? err.number : (err.code ? err.code : -1);
    desc = err.desc ? err.desc : (err.message ? err.message : 'no message');
    return app.xmlrpc.FaultFromString(code, desc);
};
app.xmlrpc.fault = function(o, show, detect) {
    if (o instanceof XMLRPCFault || (detect && o && o.faultCode!==undefined && o.faultString!==undefined)) {
        if (show)
        {alert("Error ("+o.faultCode+"): "+o.faultString);}
        return true;
    }
    return false;
};
app.xmlrpc.loadFile = function(url, async, functor) {
    var http = app.xmlrpc.getXmlHttpRequest();
    var packet = null, response = null;
    if (http) {
        if (async) {
            var packet = new XMLRPCPacket(functor,http,true);
            response = packet;
        }
        try {
            http.open('GET', url, (async?true:false));
            if (async)
            { packet.QueueForSend(); }
            http.send('');
            if (!async)
            { response = http.responseText; }
        } catch(err) {
            if (async)
            { packet._receivecomplete(XMLRPCPacket.RESPONSE_CLIENTERR, app.xmlrpc.FaultFromException(err) ); }
            else
            { response = app.xmlrpc.FaultFromException(err); }
        }
    } else {
        response = app.xmlrpc.FaultFromString(0, 'Unable to send request');
    }
    return response;
};
app.xmlrpc.Call = function(server, key, method, args, context, async, functor) {
    // open the connection
    var http = app.xmlrpc.getXmlHttpRequest();
    var response = null;
    var packet = null;
    if (http) {
        // build the message
        var xml = "<?xml version=\"1.0\"?>";
        xml += "<packet>";
        xml += "<sessionKey>"+key+"</sessionKey>";
        xml += "<methodCall>";
        xml += "<guid>0</guid>";
        xml += "<methodName>" + method + "</methodName>";
        if (app.isArray(args) && args.length > 0) {
            xml += "<params>";
            for(var i=0;i<args.length;i++)
            { xml += "<param><value>" + app.xmlrpc.toXMLRPC(args[i]) + "</value></param>"; }
            xml += "</params>";
        }
        xml += "</methodCall>";
        xml += "</packet>";
        if (async) {
            var packet = new XMLRPCPacket(functor,http,false);
            response = packet;
        }
        try { http.overrideMimeType("text/xml");} catch(e) {;}
        try {
            http.open('POST', server, (async?true:false));
            http.setRequestHeader("User-Agent", "BASEXMLRPC v1.0 (" + navigator.userAgent + ")");
	        http.setRequestHeader("Content-type", "text/xml");
            xml = app.xmlrpc.getDomParser(xml); // just make sure this will parse...
            if (async)
            { packet.QueueForSend(); }
            http.send(xml);
            if (!async)
            { response = app.xmlrpc._parseHTTPResponse(http); }
	    } catch(err) {
            if (async)
            { packet._receivecomplete(XMLRPCPacket.RESPONSE_CLIENTERR, app.xmlrpc.FaultFromException(err) ); }
            else
            { response = app.xmlrpc.FaultFromException(err); }
	    }
    } else {
        response = app.xmlrpc.FaultFromString(0, 'Unable to send request');
    }
    return response;
};
app.xmlrpc._parseHTTPResponse = function( http ) {
    var response = null;
    if (http.status == 200) {
        var oDoc = app.xmlrpc.getDomParser(http.responseText);
        //var oDoc = http.responseXML;
        if (!oDoc || !oDoc.childNodes || oDoc.childNodes.length == 0) {
            response = app.xmlrpc.FaultFromString(0, "Malformed Response: " + http.responseText);
        } else {
            var oPacketNode;
            for (var i = 0 ; i < oDoc.childNodes.length ; i++) {
                if (oDoc.childNodes[i].nodeName == 'packet') {
                    oPacketNode = oDoc.childNodes[i];
                    break;
                }
            }

            var oResponseNode;
            if (oPacketNode) {
                for (var i = 0 ; i < oPacketNode.childNodes.length ; i++) {
                    if (oPacketNode.childNodes[i].nodeName == 'methodResponse') {
                        oResponseNode = oPacketNode.childNodes[i];
                        break;
                    }
                }
            }

            if (oResponseNode) {
                var oNode = oResponseNode.firstChild;
                while (oNode) {
                    if (oNode.nodeName == 'params') {
                        // expecting exactly one param
                        try { response = app.xmlrpc.valueNodeToObject(oNode.firstChild.firstChild);
                        } catch(e)
                        { response = app.xmlrpc.FaultFromString(0, ('Error Parsing a Method Response Value'));}
                        break;
                    } else if (oNode.nodeName == 'fault') {
                        try {
                            response = app.xmlrpc.valueNodeToObject(oNode.firstChild);
                            response = new XMLRPCFault(response.faultCode, response.faultString, response.faultClass);
                        } catch(e){ response = app.xmlrpc.FaultFromString(0, ('Error Parsing a Method Fault')); }
                        break;
                    }
                    oNode = oNode.nextSibling;
                }
            } else {
                response = app.xmlrpc.FaultFromString(0, "Malformed RPC Response: Missing methodResponse Node.");
            }
        }
    } else {
        response = app.xmlrpc.FaultFromString(0, "HTTP Exception("+http.status+"): " + http.statusText + "\n\n" + http.responseText);
    }
    return response;
};
app.xmlrpc.valueNodeToObject = function( oNode ) {
    var ret;
	switch(oNode.tagName) {
		case "string":
			ret = (oNode.firstChild) ? String(oNode.firstChild.nodeValue) : "";
            ret = ret.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
			break;
		case "int":
		case "i4":
		case "double":
			ret = (oNode.firstChild) ? Number(oNode.firstChild.nodeValue) : 0;
			break;
		case "dateTime.iso8601":
		    ret = new Date();
		    ret.fromISO8601(oNode.firstChild.nodeValue);
			break;
		case "array":
			oNode = oNode.firstChild;
			if(oNode && oNode.tagName == "data") {
			    ret = new Array();
			    var arr = oNode.childNodes;
				for (var i=0 ; i<arr.length ; i++)
				{ ret.push(app.xmlrpc.valueNodeToObject(oNode.childNodes[i])); }
			}
			else
            { throw(new Error("Malformed XMLRPC Array")); }
			break;
		case "struct":
		    oNode = oNode.firstChild;
		    ret = {};
			while (oNode) {
			    if(oNode.tagName == "member")
			    {ret[oNode.firstChild.firstChild.nodeValue] = app.xmlrpc.valueNodeToObject(oNode.firstChild.nextSibling);}
                oNode = oNode.nextSibling;
            }
			break;
		case "boolean":
			ret = oNode.firstChild.nodeValue == "true" ? true : false;
			break;
		case "base64":
		    ret = new Base64Data(oNode.firstChild.nodeValue, true);
		    break;
		case "encrypted":
		    ret = new EncryptedData(oNode.firstChild.nodeValue);
		    break;
		case "value":
			oNode = oNode.firstChild;
			ret = !oNode ? ( (oNode.firstChild) ? String(oNode.firstChild.nodeValue) : "") : app.xmlrpc.valueNodeToObject(oNode);
			break;
		default:
			throw(new Error("Unknown Tag in XMLRPC Message: " + oNode.tagName));
	}

    return ret;
};
app.xmlrpc.toXMLRPC = function(val) {
    if (typeof val == "function") {
	    throw({message:"Cannot Parse Functions!"});
	}
    if (val == null || val == undefined || val.constructor == Boolean ||(val.constructor == Number && !isFinite(val))) {
	    return "<boolean>"+val+"</boolean>";
	}
	if (val.constructor == Base64Data) {
	    return "<base64>"+val.getencoded()+"</base64>";
	}
	if (val.constructor == EncryptedData) {
	    return "<encrypted>"+val.GetEncrypted()+"</encrypted>";
	}
	if (val.constructor == Number) {
	    if(val == parseInt(val)){return "<int>"+val+"</int>";}
	    else if(val == parseFloat(val)){return "<double>"+val+"</double>";}
	    else{ return app.xmlrpc.toXMLRPC(false); }
	}
	if (val.constructor == String) {
	    var str = val.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
        if (app.xmlrpc.USE_CDATA_STRINGS)
        { return "<string><![CDATA["+str+"]]></string>"; }
        return "<string>"+str+"</string>";
	}
	if (val.constructor == Array) {
	    var ret = "<array><data>";
    	for(var i=0;i<val.length;i++)
    	{ ret += "<value>"+app.xmlrpc.toXMLRPC(val[i])+"</value>"; }
    	return ret + "</data></array>";
	}
	if (val.constructor == Date) {
	    return "<dateTime.iso8601>"+val.toISO8601()+"</dateTime.iso8601>";
	}
	if (val.constructor == Object || typeof val == "object") {
	    var retstr = "<struct>";
		for(prop in val){
			if(typeof val[prop] != "function"){
				retstr += "<member><name>" + prop + "</name><value>" + app.xmlrpc.toXMLRPC(val[prop]) + "</value></member>";
			}
		}
    	retstr += "</struct>";
    	return retstr;
	}
    return app.xmlrpc.toXMLRPC(String(val));
};
app.xmlrpc._leadingZero = function(n){if (n.length==1) n = "0" + n; return n;};

//////////////////////////////////////////////////////////////////////////////////////////////
// <base64> tag support.

function Base64Data(str, encoded){this.m_data = (encoded ? str : app.base64.encode(str));return this;};
Base64Data.prototype.getencoded = function(){ return this.m_data; };
Base64Data.prototype.getdecoded = function(){ return app.base64.decode(this.m_data); };

//////////////////////////////////////////////////////////////////////////////////////////////
// encryption support. the server side must support a custom <encrypted> tag and have support for TEA

/*
interface Cryptor {
	public function SetKey(key:String);
    public function GetKey();
    public function Encrypt(val:String):String;
    public function Decrypt(val:String):String;
}
*/
function EncryptedData(data, key, cryptor) {
    if (key==undefined){ this.m_data = data; }
    else{
        if (cryptor==undefined){
	        cryptor = new TEACryptor();
        }
        cryptor.SetKey(key);
        this.m_data = cryptor.Encrypt("<value>"+app.xmlrpc.toXMLRPC(data)+"</value>");
    }
};
EncryptedData.prototype.GetEncrypted = function(){
    return this.m_data;
};
EncryptedData.prototype.GetDecrypted = function(key, cryptor){
    if (cryptor==undefined) {cryptor = new TEACryptor();}
    cryptor.SetKey(key);
    var string = cryptor.Decrypt(this.m_data);
    var xml = app.xmlrpc.getDomParser(string);
    return app.xmlrpc.valueNodeToObject(xml.firstChild);
};

//////////////////////////////////////////////////////////////////////////////////////////////
// date object extension for xmlrpc marshalling

Date.prototype.fromISO8601 = function (string) {
    var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
        "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
        "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
    var d = string.match(new RegExp(regexp));
    var offset = 0;
    var date = new Date(d[1], 0, 1);
    if (d[3]) { date.setMonth(d[3] - 1); }
    if (d[5]) { date.setDate(d[5]); }
    if (d[7]) { date.setHours(d[7]); }
    if (d[8]) { date.setMinutes(d[8]); }
    if (d[10]) { date.setSeconds(d[10]); }
    if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
    if (d[14]) {
        offset = (Number(d[16]) * 60) + Number(d[17]);
        offset *= ((d[15] == '-') ? 1 : -1);
    }
    offset -= date.getTimezoneOffset();
    var time = (Number(date) + (offset * 60 * 1000));
    this.setTime(Number(time));
};
Date.prototype.toISO8601 = function () {
    var year  = this.getYear();
    if (year < 2000) year+=1900;
    var month = this.getMonth() + 1;
    var day  = this.getDate();
    var hour = this.getHours();
    var hourUTC = this.getUTCHours();
    var diff = hour - hourUTC;
    var hourdifference = Math.abs(diff);
    var minute = this.getMinutes();
    var minuteUTC = this.getUTCMinutes();
    var minutedifference;
    var second = this.getSeconds();
    var timezone;
    if (minute != minuteUTC && minuteUTC < 30 && diff < 0) { hourdifference--; }
    if (minute != minuteUTC && minuteUTC > 30 && diff > 0) { hourdifference--; }
    if (minute != minuteUTC)minutedifference = ":30";
    else minutedifference = ":00";
    if (hourdifference < 10) timezone = ("0" + hourdifference + minutedifference);
    else timezone = "" + hourdifference + minutedifference;
    if (diff < 0) timezone = ("-" + timezone);
    else timezone = ("+" + timezone);
    if (month <= 9) month = "0" + month;
    if (day <= 9) day = "0" + day;
    if (hour <= 9) hour = "0" + hour;
    if (minute <= 9) minute = "0" + minute;
    if (second <= 9) second = "0" + second;
    return (year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second + timezone);
};

//////////////////////////////////////////////////////////////////////////////////////////////
// the xmlrpcpacket handles asyncronous rpc calls. used internally by app.xmlrpc

var XMLRPCPacket = function(functor, http, isfile) {
    this.guid      = app.luid();
    this.http      = http;
    this.timer     = 0;
    this.functor   = functor;
    this.isfile    = app.rVal(isfile,false);
    this.response  = null;
    var guid = this.guid;
    this.http.onreadystatechange = function(){XMLRPCPacket._response_hook(guid);};
    return this;
}
XMLRPCPacket.prototype = new Object();
XMLRPCPacket.prototype.Abort = function() {
    this._receivecomplete(XMLRPCPacket.RESPONSE_ABORTED);
};
XMLRPCPacket.prototype.QueueForSend = function() {
    XMLRPCPacket.QueuePacket(this.guid, this);
    if (app.xmlrpc.timeout > 0) {
        var guid = this.guid;
        var fnTimeOut = function()
        { XMLRPCPacket._response_hook2(guid,XMLRPCPacket.RESPONSE_TIMEDOUT); };
        this.timeouttimer = setTimeout(fnTimeOut, app.xmlrpc.timeout);
    }
};
XMLRPCPacket.prototype._receivecomplete = function(iResultCode, fault) {
    var response = fault;
    clearTimeout(this.timer);
    clearTimeout(this.timeouttimer);
    XMLRPCPacket.DequeuePacket(this.guid);
    switch (iResultCode) {
        case XMLRPCPacket.RESPONSE_ABORTED:
            // dequeue _before_ aborting because that will fire onreadystatechange event
            if (this.http){ this.http.abort(); }
            break;
        case XMLRPCPacket.RESPONSE_TIMEDOUT:
            response = app.xmlrpc.FaultFromString(0, "Operation Timed Out. Packet: " + this.guid);
            break;
        case XMLRPCPacket.RESPONSE_CLIENTERR:
            if (!response) response = app.xmlrpc.FaultFromString(0, "Undefined Error. Packet: " + this.guid);
            break;
        case XMLRPCPacket.RESPONSE_COMPLETE:
            response = this.isfile ? this.http.responseText : app.xmlrpc._parseHTTPResponse(this.http);
            break;
    }
    this.http = null;
    if(typeof(this.functor)=="object" && app.isFunction(this.functor.fire)) {
        this.functor.fire([response]);
	}
};
XMLRPCPacket.RESPONSE_NONE      = 0;
XMLRPCPacket.RESPONSE_ABORTED   = 2;
XMLRPCPacket.RESPONSE_TIMEDOUT  = 3;
XMLRPCPacket.RESPONSE_CLIENTERR = 4;
XMLRPCPacket.RESPONSE_SERVERERR = 5;
XMLRPCPacket.RESPONSE_COMPLETE  = 6;
XMLRPCPacket._response_hook = function(guid) {
    var packet = XMLRPCPacket.Queue().findValueByName(guid);
    if (packet && packet.http.readyState == 4) {
        clearTimeout(packet.timer);
        clearTimeout(packet.timeouttimer);
        // set a timeout or IE's stack may blow.
        var fnT = function() { XMLRPCPacket._response_hook2(guid,XMLRPCPacket.RESPONSE_COMPLETE); };
        packet.timer = setTimeout(fnT, 0);
    }
};
XMLRPCPacket._response_hook2 = function( luid, code ) {
    var packet = XMLRPCPacket.Queue().findValueByName(luid);
    if (packet)
    { packet._receivecomplete(code); }
    else if (app.xmlrpc.doDebug)
    { alert("XMLRPCPacket._response_hook() Couldn't find packet. luid: " + luid + ", response code: " + code);}
};
XMLRPCPacket.Purge = function() {
    for (var idx = 0 ; idx < XMLRPCPacket.Queue().getLength() ; idx++)
    { XMLRPCPacket.Queue().valueAtIndex(idx).Abort(); }
};
XMLRPCPacket.QueuePacket = function(luid, oPacket) {
    XMLRPCPacket.Queue().add(luid, oPacket);
};
XMLRPCPacket.DequeuePacket = function(luid) {
    if ((!XMLRPCPacket.Queue().deleteKey(luid)) && (app.xmlrpc.doDebug))
    { alert("Couldn't Dequeue Missing XMLRPC Packet: " + luid);}
};
XMLRPCPacket.Queue = function() {
    if (!XMLRPCPacket.s_lstQueue)
    { XMLRPCPacket.s_lstQueue = new List(); }
    return XMLRPCPacket.s_lstQueue;
};