/**
 * JavaScript library for use with HTML_AJAX
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @category    HTML
 * @package    Ajax
 * @author    Joshua Eichorn <josh@bluga.net>
 * @author    Arpad Ray <arpad@php.net>
 * @author    David Coallier <davidc@php.net>
 * @author    Elizabeth Smith <auroraeosrose@gmail.com>
 * @copyright    2005 Joshua Eichorn, Arpad Ray, David Coallier, Elizabeth Smith
 * @license    http://www.opensource.org/licenses/lgpl-license.php  LGPL
 */

/**
 * HTML_AJAX static methods, this is the main proxyless api, it also handles global error and event handling
 */
var HTML_AJAX = {
    version: '@package_version@',
    defaultServerUrl: false,
    defaultEncoding: 'JSON',
    queues: false,
    clientPools: {},
    // get an HttpClient, supply a name to use the pool of that name or the default if it isn't found
    httpClient: function(name) {
        if (name) {
            if (this.clientPools[name]) {
                return this.clientPools[name].getClient();
            }
        }
        return this.clientPools['default'].getClient();
    },
    // Pushing the given request to queue specified by it, in default operation this will immediately make a request
    // request might be delayed or never happen depending on the queue setup
    // making a sync request to a non immediate queue will cause you problems so just don't do it
    makeRequest: function(request) {
        if (!HTML_AJAX.queues[request.queue]) {
            var e = new Error('Unknown Queue: '+request.queue);
            if (HTML_AJAX.onError) {
                HTML_AJAX.onError(e);
                return false;
            }
            else {
                throw(e);
            }
        }
        else {
            var qn = request.queue;
            var q = HTML_AJAX.queues[qn];

            HTML_AJAX.queues[request.queue].addRequest(request);
            return HTML_AJAX.queues[request.queue].processRequest();
        }
    },
    // get a serializer object for a specific encoding
    serializerForEncoding: function(encoding) {
        for(var i in HTML_AJAX.contentTypeMap) {
            if (encoding == HTML_AJAX.contentTypeMap[i] || encoding == i) {
                return eval("new HTML_AJAX_Serialize_"+i+";");
            }
        }
        return new HTML_AJAX_Serialize_Null();
    },
    fullcall: function(url,encoding,className,method,callback,args, options) {
        var serializer = HTML_AJAX.serializerForEncoding(encoding);

        var request = new HTML_AJAX_Request(serializer);
        if (callback) {
            request.isAsync = true;
        }
        request.requestUrl = url;
        request.className = className;
        request.methodName = method;
        request.callback = callback;
        request.args = args;
        if (options) {
            for(var i in options) {
                request[i] = options[i];
            }
            if (options.grab) {
                if (!request.args || !request.args.length) {
                    request.requestType = 'GET';
                }
            }
        }

        return HTML_AJAX.makeRequest(request);
    },
    callPhpCallback: function(phpCallback, jsCallback, url) {
        var args = new Array();
        for (var i = 3; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        if (HTML_AJAX_Util.getType(phpCallback[0]) == 'object') {
            jsCallback(phpCallback[0][phpCallback[1]](args));
            return;
        }
        if (!url) {
            url = HTML_AJAX.defaultServerUrl;
        }
        HTML_AJAX.fullcall(url, HTML_AJAX.defaultEncoding,
            false, false, jsCallback, args, {phpCallback: phpCallback});
    },
    call: function(className,method,callback) {
        var args = new Array();
        for(var i = 3; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        return HTML_AJAX.fullcall(HTML_AJAX.defaultServerUrl,HTML_AJAX.defaultEncoding,className,method,callback,args);
    },
    grab: function(url,callback,options) {
        if (!options) {
            options = {grab:true};
        }
        else {
            options['grab'] = true;
        }
        return HTML_AJAX.fullcall(url,'Null',false,null,callback, '', options);
    },
    post: function(url,payload,callback,options) {
        var serializer = 'Null';
        if (HTML_AJAX_Util.getType(payload) == 'object') {
            serializer = 'Urlencoded';
        }
        return HTML_AJAX.fullcall(url,serializer,false,null,callback, payload, options);
    },
    replace: function(id) {
        var callback = function(result) {
            HTML_AJAX_Util.setInnerHTML(HTML_AJAX_Util.getElement(id),result);
        }
        if (arguments.length == 2) {
            // grab replacement
            HTML_AJAX.grab(arguments[1],callback);
        }
        else {
            // call replacement
            var args = new Array();
            for(var i = 3; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            HTML_AJAX.fullcall(HTML_AJAX.defaultServerUrl,HTML_AJAX.defaultEncoding,arguments[1],arguments[2],callback,args, {grab:true});
        }
    },
    append: function(id) {
        var callback = function(result) {
            HTML_AJAX_Util.setInnerHTML(HTML_AJAX_Util.getElement(id),result,'append');
        }
        if (arguments.length == 2) {
            // grab replacement
            HTML_AJAX.grab(arguments[1],callback);
        }
        else {
            // call replacement
            var args = new Array();
            for(var i = 3; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            HTML_AJAX.fullcall(HTML_AJAX.defaultServerUrl,HTML_AJAX.defaultEncoding,arguments[1],arguments[2],callback,args, {grab:true});
        }
    }, 
    // override to add top level loading notification (start)
    Open: function(request) {
    },
    // override to add top level loading notification (finish)
    Load: function(request) {
    },
    /*
    // A really basic error handler 
    onError: function(e) {
        msg = "";
        for(var i in e) {
            msg += i+':'+e[i]+"\n";
        }
        alert(msg);
    },
    */
    // Class postfix to content-type map
    contentTypeMap: {
        'JSON':            'application/json',
        'Null':            'text/plain',
        'Error':        'application/error',
        'PHP':            'application/php-serialized',
        'HA' :            'application/html_ajax_action',
        'Urlencoded':    'application/x-www-form-urlencoded'
    },
    // used internally to make queues work, override Load or onError to perform custom events when a request is complete
    // fires on success and error
    requestComplete: function(request,error) {
        for(var i in HTML_AJAX.queues) {
            if (HTML_AJAX.queues[i].requestComplete) {
                HTML_AJAX.queues[i].requestComplete(request,error);
            }
        }
    },
    
    // turns a form into a urlencoded string
    formEncode: function(form, array_format) {
        form = HTML_AJAX_Util.getElement(form);
        var el, inpType, value, name;
        var out = (array_format) ? {} : '';
        var inputTags = form.getElementsByTagName('INPUT');
        var selectTags = form.getElementsByTagName('SELECT');
        var buttonTags = form.getElementsByTagName('BUTTON');
        var textareaTags = form.getElementsByTagName('TEXTAREA');
        var arrayRegex = /(.+)%5B%5D/;

        var validElement = function (element) {
            if (!element || !element.getAttribute) {
                return false;
            }
            el = element;
            name = HTML_AJAX_Util.encodeUrl(el.getAttribute('name'));
            if (!name) {
                // no element name so skip
                return false;
            }
            if (element.disabled) {
                return false;
            }
            
            if (!array_format) {
                value = HTML_AJAX_Util.encodeUrl(el.value);
            } else {
                value = el.value;
            }
            
            inpType = el.getAttribute('type');
            return true;
        }
        
        inputLoop:
        for (var i=0; i < inputTags.length; i++) {
            if (!validElement(inputTags[i])) {
                continue;
            }
            if (inpType == 'checkbox' || inpType == 'radio') {
                if (!el.checked) {
                    // unchecked radios/checkboxes don't get submitted
                    continue inputLoop;
                }
                var arr_var = arrayRegex.exec(name); 
                if (array_format && arr_var) {
                    if (!out[arr_var[1]]) {
                        out[arr_var[1]] = new Array();
                    }
                    out[arr_var[1]].push(value);
                    continue inputLoop;
                }
            }
            // add element to output array
            if (array_format) {
                out[name] = value;
            } else {
                out += name + '=' + value + '&';
            }
        } // end inputLoop

        selectLoop:
        for (var i=0; i<selectTags.length; i++) {
            if (!validElement(selectTags[i])) {
                continue selectLoop;
            }
            var options = el.options;
            for (var z=0; z<options.length; z++){
                var option=options[z];
                if(option.selected){
                    if (array_format) {
                        if (el.type == 'select-one') {
                            out[name] = option.value;
                            //only one item can be selected
                            continue selectLoop;
                        } else {
                            if (!out[name]) {
                                out[name] = new Array();
                            }
                            out[name].push(option.value);
                        }
                    } else {
                        out += name + '=' + option.value + '&';
                        if (el.type == 'select-one') {
                            continue selectLoop;
                        }
                    }
                }
            }
        } // end selectLoop

        buttonLoop:
        for (var i=0; i<buttonTags.length; i++) {
            if (!validElement(buttonTags[i])) {
                continue;
            }
            // add element to output array
            if (array_format) {
                out[name] = value;
            } else {
                out += name + '=' + value + '&';
            }
        } // end buttonLoop

        textareaLoop:
        for (var i=0; i<textareaTags.length; i++) {
            if (!validElement(textareaTags[i])) {
                continue;
            }
            // add element to output array
            if (array_format) {
                out[name] = value;
            } else {
                out += name + '=' + value + '&';
            }
        } // end textareaLoop
        
        return out;
    },
    // submits a form through ajax. both arguments can be either DOM nodes or IDs, if the target is omitted then the form is set to be the target
    formSubmit: function (form, target, options)
    {
        form = HTML_AJAX_Util.getElement(form);
        if (!form) {
        // let the submit be processed normally
            return false;
        }

        var out = HTML_AJAX.formEncode(form);
        target = HTML_AJAX_Util.getElement(target);
        if (!target) {
            target = form;
        }
        try
        {
            var action = form.attributes['action'].value;
        }
        catch(e){}
        if(action == undefined)
        {
            action = form.getAttribute('action');
        }

        var callback = false;
        if (HTML_AJAX_Util.getType(target) == 'function') {
            callback = target;
        }
        else {
            callback = function(result) {
                // result will be undefined if HA_Action is returned, so skip the replace
                if (typeof result != 'undefined') {
                    HTML_AJAX_Util.setInnerHTML(target,result);
                }
            }
        }

        var serializer = HTML_AJAX.serializerForEncoding('Null');
        var request = new HTML_AJAX_Request(serializer);
        request.isAsync = true;
        request.callback = callback;

        switch (form.getAttribute('method').toLowerCase()) {
        case 'post':
            var headers = {};
            headers['Content-Type'] = 'application/x-www-form-urlencoded';
            request.customHeaders = headers;
            request.requestType = 'POST';
            request.requestUrl = action;
            request.args = out;
            break;
        default:
            if (action.indexOf('?') == -1) {
                out = '?' + out.substr(0, out.length - 1);
            }
            request.requestUrl = action+out;
            request.requestType = 'GET';
        }

        if(options) {
            for(var i in options) {
                request[i] = options[i];
            }
        }

        if (request.isAsync == false) {
            return HTML_AJAX.makeRequest(request);
        }
        else {
            HTML_AJAX.makeRequest(request);
            return true;
        }
    }, // end formSubmit()
    makeFormAJAX: function(form,target,options) {
        form = HTML_AJAX_Util.getElement(form);
        var preSubmit = false;
        if(typeof form.onsubmit != 'undefined') {
            preSubmit = form.onsubmit;
            form.onsubmit = function() {};
        }
        form.HAOptions = options;
        var handler = function(e) {
            var form = HTML_AJAX_Util.eventTarget(e);

            var valid = true;
            if (preSubmit) {
                valid = preSubmit();
            }
            if (valid) {
                HTML_AJAX.formSubmit(form,target,form.HAOptions);
            }
            // cancel submission in IE
            e.returnValue = false;
            // cancel submission in FF
            if (e.preventDefault) {
                e.preventDefault();
            }
        }
        HTML_AJAX_Util.registerEvent(form,'submit',handler);
    }
}




// small classes that I don't want to put in there own file

function HTML_AJAX_Serialize_Null() {}
HTML_AJAX_Serialize_Null.prototype = {
    contentType: 'text/plain; charset=utf-8',
    serialize: function(input) {
        return new String(input).valueOf();
    },
    
    unserialize: function(input) {
        return new String(input).valueOf();    
    }
}

function HTML_AJAX_Serialize_XML() {}
HTML_AJAX_Serialize_XML.prototype = {
    contentType: 'application/xml; charset=utf-8',
    serialize: function(input) {
        var xml = '';
        if(typeof(input) == 'object' && input)
        {
            for (var i = 0;i<input.length;i++)
            {
                xml += new XMLSerializer().serializeToString(input[i]);
            }
        }
        return xml;
    },

    unserialize: function(input) {
        return input;
    }
}

// serialization class for JSON, wrapper for JSON.stringify in json.js
function HTML_AJAX_Serialize_JSON() {}
HTML_AJAX_Serialize_JSON.prototype = {
    contentType: 'application/json; charset=utf-8',
    serialize: function(input) {
        return HTML_AJAX_JSON.stringify(input);
    },
    unserialize: function(input) {
        try {
            return eval('('+input+')');
        } catch(e) {
            // sometimes JSON encoded input isn't created properly, if eval of it fails we use the more forgiving but slower parser so will at least get something
            return HTML_AJAX_JSON.parse(input);
        }
    }
}

function HTML_AJAX_Serialize_Error() {}
HTML_AJAX_Serialize_Error.prototype = {
    contentType: 'application/error; charset=utf-8',
    serialize: function(input) {
        var ser = new HTML_AJAX_Serialize_JSON();
        return ser.serialize(input);
    },
    unserialize: function(input) {
        var ser = new HTML_AJAX_Serialize_JSON();
        var data = new ser.unserialize(input);

        var e = new Error('PHP Error: '+data.errStr);
        for(var i in data) {
            e[i] = data[i];
        }
        throw e;
    }
}

// Processing Queues

// simple queue, just processes the request immediately
function HTML_AJAX_Queue_Immediate() {}
HTML_AJAX_Queue_Immediate.prototype = {
    request: false,
    addRequest: function(request) {
        this.request = request;
    },
    processRequest: function() {
        var client = HTML_AJAX.httpClient();
        client.request = this.request;
        return client.makeRequest();
    }
}



// create a default queue, has to happen after the Queue class has been defined
HTML_AJAX.queues = new Object();
HTML_AJAX.queues['default'] = new HTML_AJAX_Queue_Immediate();

