/*
 * Copyright 2002-2006 Jahia Ltd
 *
 * Licensed under the JAHIA COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (JCDDL), 
 * Version 1.0 (the "License"), or (at your option) any later version; you may 
 * not use this file except in compliance with the License. You should have 
 * received a copy of the License along with this program; if not, you may obtain 
 * a copy of the License at 
 *
 *  http://www.jahia.org/license/
 *
 * 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.
 */

/**
 * @class AsynchCommand
 * This class executes AJAX commands asynchronously.
 * When command is executed (Rpc call returns) all invokeListeners are notified.
 * @ constructor
 **/
function AsynchCommand() {
    this.invokeListeners = new Array();
    this.commandXmlDoc = "";
    this._responseXmlDoc = null;
    this._st = null;
    //start time (call sent)
    this._en = null;
    //end time (call returned)
}

AsynchCommand.prototype.toString =
function () {
    return "AsynchCommand";
}

/**
 * @method addInvokeListener
 * @param obj : AjxCallback
 * use this method to be notified when rpc call returns.
 * Callback receives an argument that is either responseXmlDocBody or exceptionObject
 * -	_responseXmlDoc:AjxXmlDoc is a resonse Xml document
 **/
AsynchCommand.prototype.addInvokeListener =
function (obj) {
    this.invokeListeners.push(obj);
    GlobalCommands[GlobalListenerCount] = this;
    GlobalListenerCount++;
}

/**
 * @method removeInvokeListener
 * @param obj
 * use this method to unsubscribe obj from events of this command object
 **/
AsynchCommand.prototype.removeInvokeListener =
function (obj) {
    var cnt = this.invokeListeners.length;
    var ix = 0;
    for (; ix < cnt; ix++) {
        if (this.invokeListeners[ix] == obj) {
            this.invokeListeners[ix] = null;
            break;
        }
    }
}

/**
 * Remove all invoke listeners
 */
AsynchCommand.prototype.removeAllInvokeListeners =
function() {
    var cnt = this.invokeListeners.length;
    var ix = 0;
    for (; ix < cnt; ix++) {
        this.invokeListeners[ix] = null;
    }
    this.invokeListeners = null;
}

/**
 * @method _fireInvokeEvent
 * @param exceptionObject
 **/
AsynchCommand.prototype._fireInvokeEvent =
function (exceptionObject) {
    var cnt = this.invokeListeners.length;
    for (var ix = 0; ix < cnt; ix++) {
        if (this.invokeListeners[ix] != null) {
            if (exceptionObject) {
                this.invokeListeners[ix].run(exceptionObject);
            } else {
                if (this._responseXmlDoc) {
                    this.invokeListeners[ix].run(this._responseXmlDoc);
                } else {
                    this.invokeListeners[ix].run(new AjxException("Service error", AjxException.UNKNOWN_ERROR, "AsynchCommand.prototype._fireInvokeEvent", "Service returned empty document"));
                }
            }
        }
    }
}

/**
 * @method rpcCallback
 * @param response
 * this method is called by XMLHttpRequest object's event handler.
 response obejct contains the following properties
 text, xml, success, status
 **/
AsynchCommand.prototype.rpcCallback =
function (response) {
    this._en = new Date();
    DBG.println(AjxDebug.DBG1, "<H4>ASYNCHRONOUS REQUEST RETURNED</H4>");
    DBG.println(AjxDebug.DBG1, "ASYNCHRONOUS ROUND TRIP TIME: " + (this._en.getTime() - this._st.getTime()));
    var newEx = null;
    if (! response.success) {
        // Safari may produces some response with null response body before the real response is received
        if (response.text == null) return;

        try {
            var respDoc = AjxEnv.isIE || response.xml == null
                    ? AjxXmlDoc.createFromXml(response.text)
                    : AjxXmlDoc.createFromDom(response.xml);
            if (respDoc.getBody()) {
                DBG.println(AjxDebug.DBG1, "<H4>RESPONSE</H4>");
                DBG.printXML(AjxDebug.DBG1, respDoc.getDocXml());
            }
        } catch (ex) {
            newEx = new AjxException();
            newEx.method = "AsynchCommand.prototype.rpcCallback";
            newEx.detail = "Unknown problem ecnountered while communicating with the server. ";
            newEx.detail += "text: ";
            newEx.detail += response.text;
            newEx.detail += "\n";
            newEx.detail += "xml: ";
            newEx.detail += response.xml;
            newEx.detail += "\n";
            newEx.detail += "status: ";
            newEx.detail += response.status;
            newEx.detail += "\n";
            newEx.code = AjxException.UNKNOWN_ERROR;
            newEx.msg = "Unknown Error";
        }
    } else {
        try {
            // responseXML is empty under IE and FF doesnt seem to populate xml if faulted
            var respDoc = AjxEnv.isIE || response.xml == null
                    ? AjxXmlDoc.createFromXml(response.text)
                    : AjxXmlDoc.createFromDom(response.xml);
            this._responseXmlDoc = respDoc;
            DBG.println(AjxDebug.DBG1, "<H4>RESPONSE</H4>");
            DBG.printXML(AjxDebug.DBG1, respDoc.getDocXml());
        } catch (ex) {
            if (ex instanceof AjxException) {
                newEx = ex;
            } else {
                newEx = new AjxException();
                newEx.method = "AsynchCommand.prototype.rpcCallback";
                newEx.detail = ex.toString();
                newEx.code = AjxException.UNKNOWN_ERROR;
                newEx.msg = "Unknown Error";
            }
            this._fireInvokeEvent(newEx);
            return;
        }
    }
    //call event listeners
    this._fireInvokeEvent(newEx);
}

/**
 * Cancels this request (which must be async).
 */
AsynchCommand.prototype.cancel =
function() {
    if (!this._rpcId) return;

    var req = AjxRpc.getRpcRequest(this._rpcId);
    if (req) req.cancel();
}

/**
 * @method invoke
 * @param xmlDoc     The XML document containing the data to send to the server
 * @param serverUri  The server's URL
 * Use this method to send a command to the server
 **/
AsynchCommand.prototype.invoke =
function (xmlDoc, serverUri) {
    if (arguments.length < 2) {
        DBG.println(AjxDebug.DBG1, "AsynchCommand.prototype.invoke requires 2 arguments");
        return;
    }

    try {
        DBG.println(AjxDebug.DBG1, "<H4>ASYNCHRONOUS REQUEST</H4>");
        DBG.printXML(AjxDebug.DBG1, xmlDoc.getDocXml());
        var requestStr = xmlDoc.getDocXml();

        this._st = new Date();
        GlobalCallback[GlobalCallbackCount] = new AjxCallback(this, this.rpcCallback);
        this._rpcId = AjxRpc.invoke(requestStr, serverUri, {"Content-Type": "text/xml; charset=utf-8"},
                GlobalCallback[GlobalCallbackCount]);
        GlobalCallbackCount++;
    } catch (ex) {
        //JavaScript error, network error or unknown error may happen
        var newEx = new AjxException();
        newEx.method = "AsynchCommand.invoke";
        if (ex instanceof AjxException) {
            newEx.detail = ex.msg + ": " + ex.code + " (" + ex.method + ")";
            newEx.msg = "Network Error";
            newEx.code = ex.code;
        } else {
            newEx.detail = ex.toString();
            newEx.code = AjxException.UNKNOWN_ERROR;
            newEx.msg = "Unknown Error";
        }
        //notify listeners
        this._fireInvokeEvent(newEx);
    }
}