// Both AJAX and XMLHttpRequest are misnomers.  'HTTPConnection' is much more
// accurate.

////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

var __CFHTTP_ERROR_DIALOG_BOX_ID = "invalid CFHTTPDialogBox id";
var __CFHTTP_ERROR_IN_PROGRESS = "a request is already in progress";
var __CFHTTP_ERROR_RESPONSE = "no response loaded";

var __CFHTTP_WARNING_ACTIVEX_EXCEPTION =
    "can't test for ActiveX XMLHTTP support without exception support";
var __CFHTTP_WARNING_ACTIVEX_INTERFACES =
    "couldn't find ActiveX XMLHTTP interface";
var __CFHTTP_WARNING_TRANSPORT_UNAVAILABLE = "couldn't find HTTP transport";

var __CFHTTP_DEFAULT_HEADERS = {};
__CFHTTP_DEFAULT_HEADERS["connection"] = "close";
__CFHTTP_DEFAULT_HEADERS["content-encoding"] = "UTF-8";

var __CFHTTP_REQUEST_METHOD = "POST";
var __CFHTTP_SUPPORTS_CONNECTIONS = true;

var CFHTTP_STATE_IDLE = 0;
var CFHTTP_STATE_OPEN = 1;
var CFHTTP_STATE_SENT = 2;
var CFHTTP_STATE_RECEIVING = 3;
var CFHTTP_STATE_LOADED = 4;

////////////////////////////////////////////////////////////////////////////////
// Static variables
////////////////////////////////////////////////////////////////////////////////

var __cfHTTPDialogBoxMap = {};

////////////////////////////////////////////////////////////////////////////////
// Classes
////////////////////////////////////////////////////////////////////////////////

// __CFHTTPResponse

function __CFHTTPResponse(transport)
{
    this.__transport = transport;
}

__CFHTTPResponse.prototype.getBody = function()
{
    return this.__transport.responseText;
}

__CFHTTPResponse.prototype.getCode = function()
{
    var code = this.__transport.status;
    // IE stupidity
    if (code == 1223) {
        code = 204;
    }
    return code;
}

__CFHTTPResponse.prototype.getDocument = function()
{
    return this.__transport.responseXML;
}

__CFHTTPResponse.prototype.getHeader = function(key)
{
    return this.__transport.getResponseHeader(key);
}

__CFHTTPResponse.prototype.getMessage = function()
{
    var transport = this.__transport;
    var message = undefined;
    // IE stupidity
    if (transport.status == 1223) {
        message = "No Content";
    } else {
        message = transport.statusText;
    }
    return message;
}

__CFHTTPResponse.prototype.isError = function()
{
    var code = this.getCode();
    return ! ((code >= 200) && (code < 300))
}

// CFHTTPConnection

function CFHTTPConnection()
{
    this.__handlers = new CFEventHandlerExpandedSet();
    this.__stateChangeEvent = this.__handleStateChange.bind(this);
    this.__resetTransport();
}

CFHTTPConnection.prototype.__handleStateChange = function()
{
    var transport = this.__transport;
    var state = transport.readyState;
    if (state == CFHTTP_STATE_LOADED) {
        this.__response = new __CFHTTPResponse(transport);
    }
    this.__handlers.execute(state);
    if (state == CFHTTP_STATE_LOADED) {
        this.__resetTransport();
    }
}

CFHTTPConnection.prototype.__resetTransport = function()
{
    var transport = __cfHTTPCreateTransport();
    this.__response = undefined;
    this.__transport = transport;
    transport.onreadystatechange = this.__stateChangeEvent;
}

CFHTTPConnection.prototype.abort = function()
{
    this.__transport.abort();
    this.__resetTransport();
}

CFHTTPConnection.prototype.addEventHandler = function()
{
    var handlers = this.__handlers;
    handlers.add.apply(handlers, arguments);
}

CFHTTPConnection.prototype.executeFormRequest = function(url, headers, params)
{
    var combinedHeaders = {};
    for (var key in headers) {
        combinedHeaders[key] = headers[key];
    }
    combinedHeaders["content-type"] = "application/x-www-form-urlencoded";
    var body = cfURLCreateQueryString(params);
    return this.executeRequest(url, combinedHeaders, body);
}

CFHTTPConnection.prototype.executeRequest = function(url, headers, body)
{
    if (this.getState() != CFHTTP_STATE_IDLE) {
        return cfErrorTrigger("CFHTTPConnection::executeRequest: " +
                              __CFHTTP_ERROR_IN_PROGRESS);
    }
    var transport = this.__transport;
    transport.open(__CFHTTP_REQUEST_METHOD, url, true);
    var combinedHeaders = {};
    var key;
    if (typeof(headers) != "undefined") {
        for (key in headers) {
            combinedHeaders[key] = headers[key];
        }
    }
    for (key in __CFHTTP_DEFAULT_HEADERS) {
        combinedHeaders[key] = __CFHTTP_DEFAULT_HEADERS[key];
    }
    for (key in combinedHeaders) {
        transport.setRequestHeader(key, combinedHeaders[key]);
    }
    if (typeof(body) != "undefined") {
        transport.send(body);
    } else {
        transport.send();
    }
}

CFHTTPConnection.prototype.getResponse = function()
{
    var response = this.__response;
    if (typeof(response) == "undefined") {
        return cfErrorTrigger("CFHTTPConnection::getResponse: " +
                              __CFHTTP_ERROR_RESPONSE);
    }
    return response;
}

CFHTTPConnection.prototype.getState = function()
{
    return this.__transport.readyState;
}

// CFHTTPDialogBox

function CFHTTPDialogBox(id, titleBoxId, contentPaneId, closeButtonId,
                         idleMessage, openMessage, sendMessage,
                         receivingMessage, loadedMessage)
{
    CFDialogBox.call(id, titleBoxId, contentPaneId, closeButtonId);
    this.__idleMessage = idleMessage;
    this.__loadedMessage = loadedMessage;
    this.__openMessage = openMessage;
    this.__receivingMessage = receivingMessage;
    this.__sendMessage = sendMessage;
    __cfHTTPDialogBoxMap[id] = this;
}

////////////////////////////////////////////////////////////////////////////////
// Private functions
////////////////////////////////////////////////////////////////////////////////

function __cfHTTPCreateTransportActiveXObjectMicrosoftXMLHTTP()
{
    return new ActiveXObject("Microsoft.XMLHTTP");
}

function __cfHTTPCreateTransportActiveXObjectMsxml2XMLHTTP()
{
    return new ActiveXObject("Msxml2.XMLHTTP");
}

function __cfHTTPCreateTransportCreateRequest()
{
    // XX: Is this _really_ a viable transport option?
    return window.createRequest();
}

function __cfHTTPCreateTransportUndefined()
{
    return cfErrorTrigger("__cfHTTPCreateTransportUndefined: " +
                          __CFHTTP_ERROR_SUPPORT);
}

function __cfHTTPCreateTransportXMLHttpRequest()
{
    return new XMLHttpRequest();
}

function __cfHTTPDisableConnections(warning)
{
    cfWarningTrigger("[http]: " + warning);
    __cfHTTPCreateTransport = __cfHTTPCreateTransportUndefined;
    __CFHTTP_SUPPORTS_CONNECTIONS = false;
}

////////////////////////////////////////////////////////////////////////////////
// Public API
////////////////////////////////////////////////////////////////////////////////

function cfHTTPDialogBoxCreate(arg)
{
    return new CFHTTPDialogBox(arg.id, arg.titleBoxId, arg.contentBoxId,
                               arg.closeButtonId, arg.idleMessage,
                               arg.openMessage, arg.sendMessage,
                               arg.receivingMessage, arg.loadedMessage);
}

function cfHTTPDialogBoxGet(id)
{
    var box = __cfHTTPDialogBoxMap[id];
    if (typeof(box) == "undefined") {
        return cfErrorTrigger("'" + id + "': " + __CFHTTP_ERROR_DIALOG_BOX_ID);
    }
    return box;
}

function cfHTTPHasConnectionSupport()
{
    return __CFHTTP_SUPPORTS_CONNECTIONS;
}

////////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////////

if (window.XMLHttpRequest) {
    __cfHTTPCreateTransport = __cfHTTPCreateTransportXMLHttpRequest;
} else if (window.ActiveXObject) {
    // XXX: I wish there was a way to figure out which ActiveX objects are
    // available to us without blindly passing class ids to the ActiveXObject
    // constructor.  With such a mechanism, we could ignore situations where
    // exception support is disabled, and avoid creating useless objects for
    // that purpose.
    if (! cfErrorHasExceptionSupport()) {
        __cfHTTPDisableConnections(__CFHTTP_WARNING_ACTIVEX_EXCEPTION);
    } else {
        var f = __cfHTTPCreateTransportActiveXObjectMsxml2XMLHTTP;
        var obj = cfErrorWatch(undefined, f, [], true);
        if (obj) {
            __cfHTTPCreateTransport = f;
            obj = undefined;
        } else {
            f = __cfHTTPCreateTransportActiveXObjectMicrosoftXMLHTTP;
            obj = cfErrorWatch(undefined, f, [], true);
            if (obj) {
                __cfHTTPCreateTransport = f;
                obj = undefined;
            } else {
                __cfHTTPDisableConnections(__CFHTTP_WARNING_ACTIVEX_INTERFACES);
            }
        }
    }
} else if (window.createRequest) {
    __cfHTTPCreateTransport = __cfHTTPCreateTransportCreateRequest;
} else {
    __cfHTTPDisableConnections(__CFHTTP_WARNING_TRANSPORT_UNAVAILABLE);
}
