/*
 * Manages collecting, sending, and receiving JSON-RPC requests via XmlHttpRequest with the backend server.  This is the javascript side of Generic::JavaScript::jsonrpc_handler
 *
 */

//TODO: Implement the combine and when fields

var GenericJsonRpc = new Object();

Object.extend(GenericJsonRpc, {
    
    url: '/jsonrpc.json?ngextredir=1',
    timeout: 30, // seconds
    jsonrpc_form: 'jsonrpc',
    
    nextid: 1,
    pending: new Object(),
    
    addAction: function(args) {
        var request = GenericClass.newWithArgs(GenericJsonRpcAction, args);
        this.pending[request.id] = request;
        return request;
    },
    
    process: function(when) {
        if (when == 'submit') { return this.process_submit(when); }
        else { return this.process_immediate(when); }
    },
    
    process_immediate: function(when) {
        this.processing = this.pending;
        this.pending = new Object();
        delete this.response;

        var processing = $H(this.processing).values();
        if (processing.length == 0) { return false; }
        this.processing_res = new GenericEventResponders(processing);
        var rpc_request_array = processing.invoke('toJsonRpc');
        var rpc_request_string = JSON.stringify(rpc_request_array);
        this.request = new Ajax.Request( this.url, {
            method: 'post',
            postBody: $H({JSONRPC: rpc_request_string}).toQueryString()
        });
        this.onStart(this.request);
        this.request.jsonrpc = this;
        var me = this;
        me.timer = setTimeout(function() {
            me.onTimeout(me.request);
        }, this.timeout * 1000);
        return true;
    },

    process_submit: function(when) {
        this.processing = this.pending;
        this.pending = new Object();
        delete this.response;

        var f = document[GenericJsonRpc.jsonrpc_form];
        if (!f) {
            new Insertion.Bottom('person', '<form name="jsonrpc"><input type="hidden" name="JSONRPC"></form>');
            f = document.jsonrpc;
        }
        
        if (this.success_url) f.SUCCESS_URL.value = this.success_url;
        if (this.error_url) f.ERROR_URL.value = this.error_url;
        f.action = '/jsonrpc' + f.SUCCESS_URL.value;
        
        var processing = $H(this.processing).values();
        if (processing.length == 0) { return false; }
        this.processing_res = new GenericEventResponders(processing);
        var rpc_request_array = processing.invoke('toJsonRpc');
        var rpc_request_string = JSON.stringify(rpc_request_array);
        f.JSONRPC.value = rpc_request_string;
        f.submit();
        return true;
    },
    
    _getResponse: function(request) {
        var json_in;
        try { json_in = eval('(' + request.transport.responseText + ')');
        } catch (e) { }
        var jsonrpc = new Object();
        $A(json_in).each(function(resp) {
            jsonrpc[resp.id] = resp;
        });
        this.response = jsonrpc;
    },
    
    responseById: function(id) {
        return this.response[id];
    },

    onStart: function(request) {
        this.processing_res.tell('onStart', request);
    },    
    onCreate: function(request) {
        this.processing_res.tell('onCreate', request);
    },
    onComplete: function(request) {
        clearTimeout(this.timer);
        this._getResponse(request);
        this.processing_res.tell('onComplete', request);
    },
    onSuccess: function(request) {
        clearTimeout(this.timer);
        this._getResponse(request);
        this.processing_res.tell('onSuccess', request);
    },
    onException: function(request) {
        clearTimeout(this.timer);
        this.processing_res.tell('onException', request);
    },
    onFailure: function(request) {
        clearTimeout(this.timer);
        this.processing_res.tell('onFailure', request);
    },
    onTimeout: function(request) {
        request.transport.abort();        
        clearTimeout(this.timer);
        this.processing_res.tell('onTimeout', request);        
    },
    
    
    
    // Specify a single action via form submit and runs it all in one step, with reasonable defaults
    doSubmitAction: function(args) {
        if (args.jsonrpc_form) this.jsonrpc_form = args.jsonrpc_form;
        if (args.success_url) this.success_url = args.success_url;
        if (args.error_url) this.error_url = args.error_url;
        var req = GenericJsonRpc.addAction({
            action: args.method,
            when: 'submit',
            params: args.params,
            observers: args.observers
        });
        this.process('submit');
    }
    
});

Ajax.Responders.register(GenericJsonRpc);


/*
 * Defines an invidual json-rpc request.  Requests may be multiplexed
 * @param action The name of the server side action to run
 * @param observers A list of objects with event handlers that will be called in response to progress of the AJAX call.  The events are those defined in prototype.js
 * @param params An array of parameters to pass to the server action
 * @param combine TODO: rules for automatic combining multiple pending events
 * @param when TODO: when to send this event to the server
 */


var GenericJsonRpcAction = Class.create();

Object.extend(GenericJsonRpcAction.prototype, {
    _class: 'GenericJsonRpcAction',
    
    initialize: function(args) {
        this.action = args.action;
        this.observers = new GenericEventResponders(args.observers);
        this.params = args.params;
        this.combine = args.combine || 'none'; // none, addargs, mergeargs
        this.when = args.when || 'immediate'; // immediate, timed, submit
        this.id = GenericJsonRpc.nextid++;
    },
    
    toJsonRpc: function() {
        return {method: this.action, params: this.params, id: this.id};
    },

    onStart: function(request) {
        this.observers.tell('onStart', this);
    },    
    onCreate: function(request) {
        this.observers.tell('onSuccess', this);
    },
    onComplete: function(request) {
        var response = GenericJsonRpc.responseById(this.id);
        if (!response) { 
            this.onFailure(request);
            return;
        }
        this.result = response.result;
        this.error = response.error;
        this.request = request;
        this.observers.tell('onComplete', this);
    },
    onSuccess: function(request) {
        this.observers.tell('onSuccess', this);
    },
    onException: function(request) {
        this.observers.tell('onException', this);
    },
    onFailure: function(request) {
        this.observers.tell('onFailure', this);
    },
    onTimeout: function(request) {
        this.observers.tell('onTimeout', this);
    }
    
});

/*
var GenericWaitObserver = new Object();
Object.extend(GenericWaitObserver, {
    onCreate: function(jsonrpc) {
        if (!this.element) {
            var elem = document.createElement('div');
            elem.id = 'GenericWaitObserver_element';
            elem.className = 'GenericWaitObserver';
            styler(elem, {
                    
            });
            document.body.appendChild(elem);
            this.element = elem;
        }
    }
        
        
});
*/
