/***************************************************************************
 *   4I AJAX framework
 *   -----------------------------------------------------------------------
 *   Copyright (C) 2008 by Ondrej Fischer
 *   ondrej.fischer@4internet.cz
 ***************************************************************************/

function d(o)
{
    alert(o);
    return o;
}

/**
 *  Class Package provides simple grouping of objects into packages
 */
function Package()
{
}

/**
 *    Method package creates a Package member with name package. When called
 * statically, it creates top level packages in window namespace.
 *
 * @param String package
 */
function package(package)
{
        return this[package] = new Package();
}

/**
 *    Assigning of package method to Package instances
 */
Package.prototype.package = package;


package('I4').package('Ajax');


/**
 *   Exception thrown in browsers not supporting AJAX
 */
I4.Ajax.AjaxUnsupportedException = function AjaxUnsupportedException()
{
        this.message = 'AJAX is unsupported by this browser';
}

/**
 *   Helper function to create URL encoded parameters from an object aka
 *   associative array
 *
 * @param  Object params
 * @return String
 */
function joinParams(params)
{
        var parts = [];
        for(var i in params) parts.push(escape(i) + '=' + escape(params[i]));
        return parts.join('&amp;');
}


/***************************************************************************
 *   Class I4AjaxRequest
 *   -----------------------------------------------------------------------
 *   Class implementing cross platform, clean and easy to use low level layer
 *   to AJAX interface.
 *
 * @param boolean async
 */
I4.Ajax.Request = function Request(async)
{

        /**
         *   Instance initializer
         *
         * @param Array handlers
         * @return I4AjaxRequest
         */
        this.RequestInit = function RequestInit()
        {

                function emptyHandler(request) {}

                var handlers = [emptyHandler, emptyHandler, emptyHandler, emptyHandler, emptyHandler];

                /**
                 *   Method to set state handler for concrete ready state.
                 *   Since handlers must be in closure, because of passing
                 *   functions to other objects, it is created on the fly.
                 *
                 * @param int state
                 * @param Function(request) handler
                 * @return I4.Ajax.Request
                 */
                this.setStateHandler = function setStateHandler(state, handler)
                {
                        handlers[state] = handler;
                        return this;
                }

                /**
                 *   Method to unset handler for concrete state (reset to
                 *   emptyHandler).
                 *
                 * @param int state
                 * @return I4AjaxRequest
                 */
                this.unsetStateHandler = function unsetStateHandler(state)
                {
                        return this.setStateHandler(state, emptyHandler);
                }

                /**
                 *   Method to get the correct state handler for state in which
                 *   the request currently is.
                 *
                 * @param int state
                 * @return Function(request)
                 */
                this.getStateHandler = function getStateHandler(state)
                {
                        return handlers[state];
                }

                return this;

        }


        /**
         *   Simplification of request creation and handling. Due to not reusable
         *   API in MSIE, new instance of API implementation is created for each
         *   request, but still the same state handlers are passed to it.
         *
         * @param String method
         * @param String uri
         * @param any data
         * @param Object headers
         * @return XMLHttpRequest
         */
        this.request = function request(method, uri, data, headers)
        {
                var ref = this;
                var request = this.implementation();
                request.onreadystatechange = function() {ref.getStateHandler(request.readyState)(request);};
                request.open(method, uri, async);
                if(headers) for(var i in headers) request.setRequestHeader(i, headers[i]);
                request.send(data);
                return request;
        }

}



/**
 * Here we handle creating right prototype, providing AJAX API.
 */
I4.Ajax.Request.prototype.implementation = function UnsupportedImplementation()
{
        throw new I4.Ajax.AjaxUnsupportedException();
}

if(window.ActiveXObject) I4.Ajax.Request.prototype.implementation = function ActiveXImplementation()
{
        return new ActiveXObject("Microsoft.XMLHTTP");
}

if(window.XMLHttpRequest) I4.Ajax.Request.prototype.implementation = function XMLHttpRequestImplementation()
{
        return new XMLHttpRequest();
}


/**
 * Let's define methods to have easier usage of AJAX primitives.
 */

/**
* Mehtod get() is a simple GET request.
*/
I4.Ajax.Request.prototype.get = function get(uri, headers)
{
        return this.request('GET', uri, null, headers);
}

I4.Ajax.Request.prototype.getParams = function getParams(uri, params, headers)
{
        return this.request('GET', uri + '?' + joinParams(params), null, headers);
}

/**
* Mehtod post() is a generic POST request able to send data of any type.
*/
I4.Ajax.Request.prototype.post = function post(uri, data, contentType, headers)
{
        if(!headers) headers = new Object();
        headers['Content-Type'] = contentType;
        return this.request('POST', uri, data, headers);
}

/**
 * Mehtod postXML() is a POST request for sending XML document.
 */
I4.Ajax.Request.prototype.postXML = function postXml(url, data, headers)
{
        return this.post(url, data, 'text/xml', headers);
}

I4.Ajax.Request.prototype.postRawData = function postRawData(url, data, headers)
{
        return this.post(url, data, 'text/plain', headers);
}

I4.Ajax.Request.prototype.postParams = function postParams(url, params, headers)
{
        return this.post(url, joinParams(params), 'application/x-www-form-urlencoded', headers);
}



I4.Ajax.Request.prototype.setLoadingHandler = function setLoadingHandler(handler)
{
        return this.setStateHandler(1, handler);
}

I4.Ajax.Request.prototype.setHeaderLoadedHandler = function setLoadedHandler(handler)
{
        return this.setStateHandler(2, handler);
}

I4.Ajax.Request.prototype.setDataChunkHandler = function setLoadedHandler(handler)
{
        return this.setStateHandler(3, handler);
}

I4.Ajax.Request.prototype.setDataLoadedHandler = function setReadyHandler(handler)
{
        return this.setStateHandler(4, handler);
}



/***************************************************************************
 *   Class I4.Ajax.AsyncRequest
 *   -----------------------------------------------------------------------
 *   Class implementing asynchronous AJAX requests (concretization of
 *   I4.Ajax.Request)
 *
 */
I4.Ajax.AsyncRequest = function AsyncRequest()
{
}
I4.Ajax.AsyncRequest.prototype = new I4.Ajax.Request(true);


/**
 *   I4.Ajax.AsyncRequest initializer method's purpose is only to call parent's
 *   initializer.
 *
 * @return I4.Ajax.AsyncRequest
 */
I4.Ajax.AsyncRequest.prototype.AsyncRequestInit = function AsyncRequestInit()
{
        return this.RequestInit();
}



/***************************************************************************
 *   Class I4.Ajax.SyncRequest
 *   -----------------------------------------------------------------------
 *   Class implementing synchronous AJAX requests (concretization of
 *   I4AjaxRequest)
 *
 * @param boolean async
 */
I4.Ajax.SyncRequest = function SyncRequest()
{
}
I4.Ajax.SyncRequest.prototype = new I4.Ajax.Request(false);


/**
 *   I4.Ajax.SyncRequest initializer method's purpose is only to call parent's
 *   initializer.
 *
 * @return I4.Ajax.SyncRequest
 */
I4.Ajax.SyncRequest.prototype.SyncRequestInit = function SyncRequestInit()
{
        return this.RequestInit();
}


/**
 *   Helper function to assign values of parameters to their names by order,
 *   to be sent in GET/POST request by name.
 *
 * @param Array names
 * @param Array values
 * @return Object
 */
function mapParams(names, values)
{
        var ret = {}
        for(var i = 0; i < names.length; i++) ret[names[i]] = values[i] ? values[i] : '';
        return ret;
}


/***************************************************************************
 *   Class I4.Ajax.Message
 *   -----------------------------------------------------------------------
 *   Class implementing interface factory for encapsulated AJAX data transfers.
 *   Instance of this class holds an instance of I4AjaxRequest, and assigns
 *   handlers to it. Methods withX are creating concrete functions to be used
 *   for AJAX initialization and dispatch.
 *
 */
I4.Ajax.Message = function Message()
{
}

/**
 *   Initializer method of I4AjaxMessage.
 *
 * @param I4AjaxRequest request - instance that dispatches all messages on this uri
 * @param String uri
 * @param Object headers
 * @return I4AjaxMessage
 */
I4.Ajax.Message.prototype.MessageInit = function MessageInit(request, uri, headers)
{

        /**
         *   Method that generates interface to GET/POST with urlencoded
         *   parameters
         *
         * @param String method - method to be called on I4AjaxRequest - one of: 'getParams' and 'postParams'
         * @param Array names - defines names of the parameters
         * @return Function
         */
        this.params = function(method, names)
        {
                return function() {return request[method](uri, mapParams(names, arguments), headers);}
        }

        /**
         *   Method that generates interface to POST method with raw, or XML data.
         *
         * @param String method - method to be called on I4AjaxRequest - one of: 'postRawData' and 'postXML'
         * @return Function
         */
        this.data   = function(method)
        {
                return function() {return request[method](uri, data, headers);}
        }

}


/**
 *   Method generates interface to GET method with additional GET parameters
 *   (encoded in URL).
 *
 * @params String names of parameters
 * @return Function(...)
 */
I4.Ajax.Message.prototype.withGetParams = function withGetParams()
{
        return this.params('getParams', arguments);
}

/**
 *   Method generates interface to POST method with additional POST parameters
 *   (encoded in URL).
 *
 * @params String names of parameters
 * @return Function(...)
 */
I4.Ajax.Message.prototype.withPostParams = function withPostParams()
{
        return this.params('postParams', arguments);
}

/**
 *   Method generates interface to POST method with raw data sent as content
 *
 * @params String names of parameters
 * @return Function()
 */
I4.Ajax.Message.prototype.withPostRawData = function withPostRawData()
{
        return this.data('postRawData');
}

/**
 *   Method generates interface to POST method with XML data sent in content
 *
 * @params String names of parameters
 * @return Function()
 */
I4.Ajax.Message.prototype.withPostXML = function withPostXML()
{
        return this.data('postXML');
}



/***************************************************************************
 *   Class I4AjaxSyncMessage
 *   -----------------------------------------------------------------------
 *   Synchronous message generator. Here result is normally returned after AJAX
 *   request is completed.
 *
 * @param String uri
 * @param Object headers
 */
I4.Ajax.SyncMessage = function SyncMessage(uri, headers)
{
        this.MessageInit(
                new I4.Ajax.SyncRequest().SyncRequestInit(),
                uri
        );
}
I4.Ajax.SyncMessage.prototype = new I4.Ajax.Message();



/***************************************************************************
 *   Class I4AjaxAsyncMessage
 *   -----------------------------------------------------------------------
 *   Asynchronous message generator. Processing of result is defered to
 *   readystatechange event of the request. Request is returned directly as well,
 *   but not yet containing the data.
 *   Complete result is passed as parameter to callback function handler, which
 *   is invoked, when request finishes.
 *
 * @param String uri
 * @param Function(request) handler
 * @param Object headers
 */
I4.Ajax.AsyncMessage = function AsyncMessage(uri, handler, headers)
{
        this.MessageInit(
                new I4.Ajax.AsyncRequest().AsyncRequestInit().setDataLoadedHandler(handler),
                uri
        );
}
I4.Ajax.AsyncMessage.prototype = new I4.Ajax.Message();



I4.Ajax.ResponseException = function ResponseException(request)
{
        this.request = request;
}


I4.Ajax.ResponseException.prototype.getMessage = function getMessage()
{
        return 'Response returned HTTP code ' + this.request.status;
}

I4.Ajax.XMLResponseException = function XMLResponseException(request)
{
}


/***************************************************************************
 * Class I4.Ajax.Response
 * -------------------------------------------------------------------------
 * Class implementing interface factory for encapsulated AJAX data transfers.
 * Instance of this class holds an instance of I4AjaxRequest, and assigns
 * handlers to it. Methods withX are creating concrete functions to be used
 * for AJAX initialization and dispatch.
 *
 */
I4.Ajax.Response = function Response(request)
{
        if(request.status < 400) return request;
        throw new I4.Ajax.ResponseException(request);
}


I4.Ajax.XMLResponse = function XMLResponse(request)
{
        with(Response(request)) if(responseXML) return responseXML;
        throw new I4.Ajax.XMLResponseException(request);
}


I4.Ajax.TextResponse = function TextResponse(request)
{
        return I4.Ajax.Response(request).responseText;
}


I4.Ajax.JSONResponse = function JSONResponse(request)
{
        return eval('(' + I4.Ajax.TextResponse(request) + ')');
}

