// Requires
// --------
// tools/XML.js


//*****************************************************
// Manages HTTP requests to & responses from the server
//*****************************************************

HTTP = {

        // ARGUMENTS USED IN THIS PACKAGE
        // ------------------------------
        // url: (string) The url of the request
        // fResponse: (function) Called by the HTTP response
        // method: ('GET' | 'POST') The HTTP request method
        // contentType: (string) The request's mime type
        // charset: (string) An ISO recognised charset name
        // oCharSet: (Object) Must have charset attribute, encode function & decode function
        // oParams: (Object) name : value mapping
        // fEncode: (funcion) Encodes the param values of oParams which is given as the argument
        // isSync: (boolean) If set to true, script execution will stop until the response is received
        // body: (string) Sent in body of the HTTP request if the method is 'POST'
        // trace: (boolean) If set to true, opens a popup window to trace critical variables
        // id: (string) The request id
        // xeBody: (XMLElement) The XMLElement is serialised and sent in body of the HTTP request (if the method is 'POST')
        // xd: (XMLDocument) The XMLDocument is serialised and sent in body of the HTTP request (if the method is 'POST')

        responseText : null, // use only for synchronous requests where the response is given immediately after

        getXMLContentType : function(charset) {
            if (!charset) charset = UnicodeCS.charset;
            return "text/xml; charset=" + charset},

        getFormContentType : function(charset) {
            if (!charset) charset = UnicodeCS.charset;
            return "application/x-www-form-urlencoded; charset=" + charset},

        getEncodedFormParams : function(oParams,fEncode) {
            if (!fEncode) fEncode = UnicodeCS.encode;
            var paramsStr = "";
            for (var id in oParams) {
                paramsStr += id + "=" + fEncode(oParams[id]) + "&"}
            return paramsStr.substr(0,paramsStr.length-1)}, // remove last '&'

        getBaseHref : function() {
            return location.protocol + '//' + location.hostname},

        // Sends a synchronous request to the server & returns the root XMLElement of the response
        getXMLElement : function(url,xd,charset,trace) {
            var contentType = this.getXMLContentType(charset);
            this.sendRequest(url, this.setResponseText, 'POST', contentType, true, xd.toXML(-1), trace);
            if (!this.responseText) throw new Error('No response text was received for the request to '+url+' with '+xd.toXML());
            return new ParsedXMLElement(this.responseText)},

        // Sends a synchronous request to the server & returns the root Element of the response
        getElement : function(url,xd,charset,trace) {
            var contentType = this.getXMLContentType(charset);
            this.sendRequest(url, this.setResponseText, 'POST', contentType, true, xd.toXML(-1), trace);
            if (!this.responseText) throw new Error('No response text was received for the request to '+url+' with '+xd.toXML());
            return XMLTools.parseDocumentElement(this.responseText);
         },
         
         getElementAsync : function(url,xd,charset,trace, funcCallback, pointThis) {
            var contentType = this.getXMLContentType(charset);
            $j.ajax({ url: url, contentType: contentType, data: xd.toXML(-1), dataType: "html", type: "POST", success: function(txt){
            	var xe = XMLTools.parseDocumentElement(txt);
	            //if (!xe) throw new Error('No response text was received for the request to '+url+' with '+xd.toXML());
	            funcCallback(xe, pointThis);
			}});
         },

        // Sends an asynchronous request to the server
        sendElementViaAJAX : function(url,xd,fResponse,trace) {
            var contentType = this.getXMLContentType(xd.charset);
            this.sendRequest(url, fResponse, 'POST', contentType, false, xd.toXML(-1), trace);
        },

        // Sends a synchronous request to the server & returns the text of the response
        getText : function(url,body,contentType,trace) {
            this.sendRequest(url, this.setResponseText, 'POST', contentType, true, body, trace);
            return this.responseText;
        },

        // This function should only be called by a synchronous HTTP response
        setResponseText : function(txt) {
            HTTP.responseText = txt;
        },
        
        // Submits a form represented by the given oParams object
        sendFormViaPost : function(url,fResponse,oCharSet,isSync,oParams,trace) {
            var contentType = this.getFormContentType(oCharSet.charset);
            var body = this.getEncodedFormParams(oParams,oCharSet.encode);
            HTTP.sendRequest(url, fResponse, 'POST', contentType, isSync, body, trace);
        },

        //*****************************************************
        // Universal function for sending a HTTP request to a server
        //*****************************************************
        sendRequest : function(url,fResponse,method,contentType,isSync,body,trace,id) {
            var req;
            if (window.XMLHttpRequest) req = new XMLHttpRequest();
        	else if (window.ActiveXObject) {
        		try {req = new ActiveXObject('MSXML2.XMLHTTP.3.0')}
        		catch (e) {
        			try {req = new ActiveXObject('Msxml2.XMLHTTP')}
        			catch (e1) {
        				try {req = new ActiveXObject('Microsoft.XMLHTTP')}
        				catch (e2) {}}}}
            if (!req) throw new Error('Error '+req.status+' : '+req.statusText);
            else {
                var isAsync = (isSync!=true);
                var fCallFResp = function() {
                    if (req.status==200) {
                        var id;
                        try {id = req.getResponseHeader('id')}
                        catch (e) {}
                        if (fResponse) {
                                if (trace) HTTP.blog('Received response (id = '+id+')...\n'
                                	+req.getAllResponseHeaders() + req.responseText);
                                fResponse(req.responseText,id)}}
                    else throw new Error('Error '+req.status+' : '+req.statusText);}
                if (isAsync) { // set fResponse
                    req.onreadystatechange = function() {
                            if (req.readyState==4) fCallFResp();}}
                req.open(method,url,isAsync);
                if (!contentType) contentType = this.getXMLContentType('UTF-8');
                req.setRequestHeader('Content-Type',contentType);
                if (id) req.setRequestHeader('id',id);
                if (method=='POST' && !body) body = '';
                else if (method=='GET') body = null;
                if (trace) {
                        var syncStr = (isAsync) ? 'asynchronous' :'synchronous';
                        this.blog('Sending '+syncStr+' '+method+' request to "'+url+'"\n'
                        +'Content-Type: "'+contentType+'" ...\n'+body);
                }
                req.send(body);
                if (!isAsync) fCallFResp();
             }
         },


        //************************************************************************************************
        //                            An log display opened in a popup window
        //************************************************************************************************
        blog : function(str) {
            try {
                var now = new Date();
                var formatNumber = function(num,minDigits) {
                    var numStr = num+'';
                    while (numStr.length<minDigits) numStr = '0' + numStr;
                    return numStr}
            	var time = formatNumber(now.getHours(),2) + ':'
	                + formatNumber(now.getMinutes(),2) + ':'
	                + formatNumber(now.getSeconds(),2) + ':'
	                + formatNumber(now.getMilliseconds(),3);
            	if (window.console) {
            		console.log(time + ' ' + str)}
            	else {
            		if (!HTTP.blogWindow || !HTTP.blogWindow.document) {
	                    HTTP.blogWindow = window.open('','psw_blog');
	                    HTTP.blogWindow.document.write('<HTML><HEAD><title>HTTP Blog</title></HEAD><BODY>'
	                    + '</BODY></HTML>');
	                    HTTP.blogWindow.document.close()}
	                str = str.replace(/></g,">\n<");
	                HTTP.blogWindow.document.body.innerHTML += '<P style="font: 12px monospace; white-space: nowrap">'
	                + '<b>' + time + '</b> '
	                + str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<BR>').replace(/\t/g,'&nbsp;&nbsp;&nbsp;&nbsp;').replace(/ /g,'&nbsp;')
	                + '</P>'}}
            catch (e) {
                alert(str);
            }
       }
}


//*****************************************************
// Latin Character Set
//*****************************************************
LatinCS = {

        charset : "ISO-8859-1", // NOTE: ISO-8859-1 does not contain the euro symbol

        encode : function(txt) {
            var encTxt = "";
            txt = txt.toString(); // Ensure that the text is in fact a string
            for (var i=0; i<txt.length; i++) {
                    var cc = parseInt(txt.charCodeAt(i),10);
                    if (cc==8364) encTxt += 'EUR'; // escape euro symbol
                    else if (cc<65 || (cc>90 && cc<97) || cc>122) {
                    encTxt += "%";
                    if (cc<16) encTxt += "0";
                    encTxt += cc.toString(16).toUpperCase()}
                    else encTxt += txt.charAt(i)}
            return encTxt},

        decode : function(encTxt) {
            var txt = "";
            encTxt = encTxt.toString(); // Ensure that the encoded text is in fact a string
            for (var i=0; i<encTxt.length; i++) {
                if (encTxt.charAt(i)=="%") {
                    txt += String.fromCharCode(parseInt(encTxt.substr(i+1,2),16).toString(10));
                    i += 2}
                else txt += encTxt.charAt(i)}
            return txt}
}


//*****************************************************
// Unicode Character Set (UTF-8)
//*****************************************************
UnicodeCS = {

	charset : "utf-8",

	encode : function(txt) {
		return this.replaceSpecialChars(encodeURIComponent(txt),true)},

	decode : function(txt) {
		return decodeURIComponent(this.replaceSpecialChars(txt))},

	replaceSpecialChars : function(txt,encode) { // if encode is not given, decode is assumed
		var sc = [ // special unicode characters
			"'","%27",
			"(","%28",
			")","%29",
			"~","%7E",
			"-","%2D",
			"_","%5F",
			"*","%2A",
			"!","%21",
			".","%2E"];
		for (var i=0; i < sc.length; i+=2) {
			var iToFind = (encode) ? i : i+1;
			var iToRepl = (encode) ? i+1 : i;
			while (txt.indexOf(sc[iToFind])>=0) txt = txt.replace(sc[iToFind],sc[iToRepl])}
		return txt}
}


//*****************************************************
// Elements specific to ProgramShop
//*****************************************************

ProgramShop = {

        COMPRESS_PATH : '/app/compress?action=noGui',
        CHARSET : UnicodeCS.charset,
        CharSet : UnicodeCS,

        QUERY_MAP : {
        	DataInputQuery : 'DataQuery'},

        PF_DTD_MAP : {
                PlatformAdminQuery : 'pfAdmin',
                PlatformInfoQuery : 'pfInfo',
                SessionQuery : 'session'},

        SPACE_DTD_MAP : {
        		CollectionQuery : 'collection',
        		CollectionWidgetQuery : 'collectionwidget',
                DataQuery : 'dataOutput',
				DataInputQuery : 'dataInput',
                SpaceQuery : 'space',
                StructureQuery : 'structure',
                WidgetQuery : 'widget'},

        /**
         * @return {Array} List of the tag names of the queries supported by the platform
         * and this javascript object
         */
        getSupportedQueryNames : function() {
                var sqns = new Array();
				var qms = [this.PF_DTD_MAP, this.SPACE_DTD_MAP];
                for(var i=0; i<qms.length; i++) {
                        for (var qn in qms[i]) {
                                sqns.push(qn)}}
                return sqns.sort()},

        /**
         * @param {String} dtdName Name of the DTD without the extension, required
         * @param {String} space Short name of the space, required if space query
         * @return {String} HREF for sending query
         */
        getQueryHref : function(rootName,space) {
                var href = HTTP.getBaseHref();
                var mapping = this.PF_DTD_MAP[rootName];
                if (!mapping) {
                        mapping = this.SPACE_DTD_MAP[rootName];
                        if (!mapping) throw new Error(rootName+' is not supported by this function');
                        if (!space) throw new Error('The space is required to call '+rootName);
                        href += '/ws/' + space + '/app'}
                return href + '/xml/' + mapping},

        /**
         * @param {String} xeQuery XML element to send to the platform, required
         * @return {XMLDocument} Complete XML Document for the query
         */
        getQueryXMLDoc : function(xeQuery) {
                var dtdName = this.getDTDName(xeQuery.tagName);
                return new XMLDocument('-//WebMuseo//DTD '+dtdName+'//EN',
                        'http://www.webmuseo.com/dtd/'+dtdName+'.dtd', 'UTF-8', xeQuery)},
        /**
         * @param {String} rootName Tag name of root element
         * @return {String} Name of the corresponding DTD file without the extension
         */
        getDTDName : function(rootName) {
			var newName = this.QUERY_MAP[rootName];
			return (newName) ? newName : rootName;},

        getDataQueryHref : function(space) {
            return this.getQueryHref('DataQuery',space)},

        getDataQueryXMLDoc : function(xeDataQuery) {
            return this.getQueryXMLDoc(xeDataQuery)},

        getDataResult : function(space,xeDataQuery,trace) {
            return HTTP.getXMLElement(this.getDataQueryHref(space), this.getDataQueryXMLDoc(xeDataQuery), 'UTF-8', trace)},

        /**
         * @deprecated Use getXEResult instead.
         */
        getResult : function(xeQuery,space,trace) {
                return this.getXEResult(xeQuery,space,trace)},

        /**
         * @param {String} xeQuery XML element to send to the platform, required
         * @param {String} space Short name of the space, required if space query
         * @return {XMLElement} Query result as an XML element
         */
        getXEResult : function(xeQuery,space,trace) {
                var doc = this.getQueryXMLDoc(xeQuery);
                var href = this.getQueryHref(xeQuery.tagName,space);
                return HTTP.getXMLElement(href, doc, 'UTF-8', trace)},

        /**
         * Sends a synchronous request to the server & returns the result element
         * @param {String} xeQuery XML element to send to the platform, required
         * @param {String} space Short name of the space, required if space query
         * @return {Element} Result element
         */
        getResultElement : function(xeQuery,space,trace) {
                var doc = this.getQueryXMLDoc(xeQuery);
                var href = this.getQueryHref(xeQuery.tagName,space);
                return HTTP.getElement(href, doc, 'UTF-8', trace)},
                
        getResultElementAsync : function(xeQuery,space,trace, openPanelCallback, pointThis) {
                var doc = this.getQueryXMLDoc(xeQuery);
                var href = this.getQueryHref(xeQuery.tagName,space);
                return HTTP.getElementAsync(href, doc, 'UTF-8', trace, openPanelCallback, pointThis)},

        /**
         * Sends a synchronous request to the server & returns the result XML
         * @param {String} xeQuery (Required) XML element to send to the platform
         * @param {String} space (Required if space query) Short name of the space
         * @return {String} Result XML
         */
        getResultXML : function(xeQuery,space,trace) {
                var href = this.getQueryHref(xeQuery.tagName,space);
                return HTTP.getText(href, xeQuery.toXML(-1), 'UTF-8', trace)},

        /**
         * Sends an asynchronous request to the server
         * @param {String} xeQuery (Required) XML element to send to the platform
         * @param {String} space (Required if space query) Short name of the space
         * @param {Function} fResponse (Required) The function that will process the response using
         * an xml string as its argument
         */
        sendQueryElementViaAJAX : function(xeQuery,space,fResponse,trace) {
                var doc = this.getQueryXMLDoc(xeQuery);
                var href = this.getQueryHref(xeQuery.tagName,space);
                HTTP.sendElementViaAJAX(href, doc, fResponse, trace)},

        /**
         * @param {String} xmlQuery (Required) XML string to send to the platform
         * @param {String} dtdName (Required) Name of the DTD without the extension
         * @param {String} space (Required if space query) Short name of the space
         * @return {String} Result XML
         */
        getXMLResult : function(xmlQuery,space,trace) {
                var xeQuery = XMLTools.parseDocumentElement(xmlQuery);
                var href = this.getQueryHref(xeQuery.tagName,space);
                return HTTP.getText(href,xmlQuery,'UTF-8',trace)},

        getCompressHref : function() {
            return HTTP.getBaseHref() + this.COMPRESS_PATH},

        getCompressedString : function(str,trace) {
            return HTTP.getText(this.getCompressHref(), str, HTTP.getXMLContentType('UTF-8'), trace)},

        // Returns an array of Tuple XMLElements that have been formatted according the the user's preferences
        // space: short name of the space we want to request
        // tableId: (string) PSId of table
        // length: (int | null) Number of tuples to be returned
        // sortField: (int | string) The psId of the field to be used for sorting the tuples
        // sortOrder: ('asc' | 'desc' | null)
        getFormattedTuples : function(space,tableId,length,sortField,sortOrder,trace) {

                /************************************************
                        BE CAREFUL NEW : Now space param MUST be given
                ************************************************/

                var xeDQ = new XMLElement('DataQuery');
                xeDQ.setAttribute('format','formattedValues');
                xeDQ.setAttribute('dateFormatMode','long');
                xeDQ.setAttribute('localize','true');

                var xeReq = new XMLElement('Request',xeDQ);
                xeReq.setAttribute('psid',tableId);
                xeReq.setAttribute('target','table');

                var xeRS = new XMLElement('ReqSpec',xeReq);
                if (length!=null) xeRS.setAttribute('length',length);

                if (sortField!=null) {
                        var xeSF = new XMLElement('SortField',xeRS);
                        xeSF.setAttribute('fieldPSId',sortField);
                        if (sortOrder) xeSF.setAttribute('order',sortOrder);
                }

                var xeDataResult = this.getDataResult(space,xeDQ,trace);
                var xeResultSet = xeDataResult.getChild('ResultSet');
                if (xeResultSet) return xeResultSet.childNodes;},

    // Returns an array of Tuple XMLElements that have been formatted according the the user's preferences
    // and the given filter
    // space: short name of the space we want to request
        // tableId: (string) PSId of table
        // reqSpec: (XMLElement) Filter which must be applied to the request
        getFormattedTuplesFromReqSpec : function(space,tableId,reqSpec,trace) {
                var xeDQ = new XMLElement('DataQuery');
                xeDQ.setAttribute('format','formattedValues');
                xeDQ.setAttribute('dateFormatMode','long');
                xeDQ.setAttribute('localize','true');

                var xeReq = new XMLElement('Request',xeDQ);
                xeReq.setAttribute('psid',tableId);
                xeReq.setAttribute('target','table');

                var xeRS = new XMLElement('ReqSpec',xeReq);
                if (reqSpec != null && reqSpec.getAttribute('length') != null) xeRS.setAttribute('length',reqSpec.getAttribute('length'));
                if (reqSpec.childNodes.length > 0) xeRS.addChild(reqSpec.childNodes[0]);

                var xeDataResult = this.getDataResult(space,xeDQ,trace);
                var xeResultSet = xeDataResult.getChild('ResultSet');
                if (xeResultSet) return xeResultSet.childNodes;}
}


