/***************************************************************************
 *   Package framework
 *   -----------------------------------------------------------------------
 *   Copyright (C) 2008 by Ondrej Fischer
 *   ondrej.fischer@4internet.cz
 *   -----------------------------------------------------------------------
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


/**
 *  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 name
 */
function package(name)
{
        if(!(this[name] instanceof Package)) this[name] = new Package();
        return this[name];
}

/**
 *    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('&');
}


/***************************************************************************
 *   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(responseType)
{

        /**
         *   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];
                }

                this.setResponseType = function setResponseType(type)
                {
                        this.responseType = type;
                        return this;
                }

                this.setResponseType(responseType);

                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, async, 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 async ? request : this.responseType(request);
        }

		this.RequestInit();
}



/**
 * 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, async, headers)
{
        return this.request('GET', async, uri, null, headers);
}

I4.Ajax.Request.prototype.getParams = function getParams(uri, async, params, headers)
{
        return this.request('GET', async, 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, async, data, contentType, headers)
{
        if(!headers) headers = new Object();
        headers['Content-Type'] = contentType;
        return this.request('POST', uri, async, data, headers);
}

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

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

I4.Ajax.Request.prototype.postParams = function postParams(url, async, params, headers)
{
        return this.post(url, async, 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, function(request) {handler(request.responseType(request));});
}

/**
 *   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(responseType, uri, headers)
{
        this.MessageInit(new I4.Ajax.Request(responseType), uri, headers);
}

/**
 * Initializer method of I4.Ajax.Message.
 *
 * @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()
                {
                        var async = arguments.length > names.length;
                        if(async) request.setDataLoaded(arguments[names.length]);
                        return request[method](uri, async, 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()
                {
                        var async = arguments.length > names.length;
                        if(async) request.setDataLoaded(arguments[names.length]);
                        return request[method](uri, async, 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');
}

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) + ')');
}


I4.Ajax.setType = function(type, api)
{
        return function() {return type(api.apply(window, arguments));}
}


