// Jamritas - Ajax application library
// Copyright (c) 2005,2006, Jun Yamamoto

Jamritas = {}

Jamritas.Template = function(source, config) {
    this.config = config || {};
    var ary = ['identifier', 'document', 'placeholder',
               'ignoreWhitespace', 'ignoreComment',
               'viewEmptyArrayAsFalse', 'recursiveExpantionElement',
               'callGetterMethod'];
    for (var i = 0; i < ary.length; ++i)
        if (ary[i] in this.config == false)
            this.config[ary[i]] = Jamritas.Template[ary[i]];
    this.placeholder = {};
    for (var i = 0; i < this.config.placeholder.length; ++i)
        this.placeholder[this.config.placeholder[i]] = true;
    this.source = new Jamritas.Template.Node(source, this.config);
}

Jamritas.Template.Node = function(source, config) {
    switch (source.nodeType) {
    case 1:
        this.clonable = true;
        var specifiedAttributes = {}, expandedAttributes = {};
        var attrs = Jamritas.DOM.attrs(source);
        for (var name in attrs)
            if (name == config.identifier)
                this.identifier = attrs[name];
            else if (attrs[name].charAt(0) == '$') {
                expandedAttributes[name] = attrs[name].substring(1);
                this.clonable = false;
            } else
                specifiedAttributes[name] = attrs[name];
        var tag = Jamritas.DOM.nodeName(source);
        if (typeof ActiveXObject == 'function' &&
            Jamritas.DOM.isHTMLDocument(config.document) &&
            tag.match(/^input|textarea|option$/))
            this.clonable = false;
        if (this.clonable)
            this.node = Jamritas.DOM.create(tag, specifiedAttributes,
                                            config.document);
        else {
            this.node = config.document.createElement(tag);
            this.specifiedAttributes = specifiedAttributes;
            this.expandedAttributes = expandedAttributes;
        }
        if (this.identifier)
            this.clonable = false;
        if (source.hasChildNodes()) {
            this.children = [];
            for (var child = source.firstChild; child;
                 child = child.nextSibling) {
                if (child.nodeType == 3 && child.data.match(/^\s*$/) &&
                    config.ignoreWhitespace)
                    continue;
                if (child.nodeType == 8 && config.ignoreComment)
                    continue;
                var node = new Jamritas.Template.Node(child, config);
                node.parent = this;
                this.children.push(node);
                this.node.appendChild(node.node);
                if (node.clonable == false)
                    this.clonable = false;
            }
        }
        break;
    case 3:
        this.node = config.document.createTextNode(source.data);
        break;
    case 4:
        this.node = Jamritas.DOM.isHTMLDocument(config.document) ?
            config.document.createTextNode(source.data || ' ') :
                config.document.createCDATASection(source.data);
        break;
    case 8:
        this.node = config.document.createComment(source.data);
        break;
    }
}

Jamritas.Template.Node.prototype = {
    hasExpandedElements: function() {
        if (!this.children)
            return false;
        for (var i = 0; i < this.children.length; ++i) {
            var child = this.children[i];
            if (child.node.nodeType == 1 && !child.clonable)
                return true;
        }
        return false;
    }
}

Jamritas.Template.prototype = {
    expand: function(data) {
        var result = this.createElement(data, this.source);
        if (result != null) {
            if (result.nodeType == 11) {
                root = this.config.document.createElement(this.source.node.tagName);
                root.appendChild(result);
                result = root;
            }
            if (Jamritas.DOM.isHTMLDocument(this.config.document)) {
                var elems = Jamritas.DOM.nodeName(result) == 'textarea' ?
                    [result] : result.getElementsByTagName('textarea');
                for (var i = 0; i < elems.length; ++i) {
                    var textarea = elems[i];
                    if (textarea.defaultValue != textarea.value)
                        textarea.defaultValue = textarea.value;
                    if (typeof opera == 'object')
                        textarea.value = textarea.defaultValue;
                }
            }
        }
        return result;
    },
    createElement: function(data, source) {
        var target;
        if (source.specifiedAttributes && source.expandedAttributes) {
            var attrs = {};
            for (var name in source.specifiedAttributes)
                attrs[name] = source.specifiedAttributes[name];
            for (var name in source.expandedAttributes) {
                var id = source.expandedAttributes[name];
                var value = this.retrieveCorrespondingValue(data, id);
                if (value != null &&
                    value.toString == Object.prototype.toString)
                    for (var key in value)
                        attrs[key] = value[key];
                else
                    attrs[name] = value;
            }
            var handler = {};
            for (var name in attrs)
                if (attrs[name] == null)
                    delete attrs[name];
                else if (name.match(/^on/) && typeof attrs[name] == 'function') {
                    handler[name.substring(2)] = attrs[name];
                    delete attrs[name];
                }
            target = Jamritas.DOM.create(source.node.tagName, attrs,
                                         this.config.document);
            for (var type in handler)
                Jamritas.Event.add(target, type, handler[type]);
        } else
            target = source.node.cloneNode(false);
        if (source.identifier) {
            var value =
                this.retrieveCorrespondingValue(data, source.identifier);
            if (value == null)
                value = false;
            if (this.config.viewEmptyArrayAsFalse && value instanceof Array &&
                value.length == 0)
                value = false;
            if (source.identifier in this.config.recursiveExpantionElement)
                for (var ancestor = source.parent; ancestor;
                     ancestor = ancestor.parent)
                    if (ancestor.identifier ==
                        this.config.recursiveExpantionElement[source.identifier]) {
                        source = ancestor;
                        break;
                    }
            switch (typeof value) {
            case 'boolean':
                if (value == false)
                    return null;
                this.appendChild(target, this.createContent(data, source));
                break;
            case 'function':
                this.appendChild(target, this.createContent(data, source));
                value(target);
                break;
            default:
                if (value instanceof Array)
                    for (var i = 0; i < value.length; ++i)
                        this.appendChild(target,
                                         this.createContent(value[i], source));
                else if (value.ownerDocument == this.config.document &&
                         value.parentNode == null)
                    return value;
                else
                    this.appendChild(target, source.hasExpandedElements() ?
                                     this.createContent(value, source) :
                                     this.config.document.createTextNode(value));
            }
            source = arguments[1];
            if (this.placeholder[Jamritas.DOM.nodeName(source.node)] &&
                !source.specifiedAttributes) {
                var fragment = this.config.document.createDocumentFragment();
                while (target.hasChildNodes())
                    fragment.appendChild(target.firstChild);
                target = fragment;
            }
        } else
            this.appendChild(target, this.createContent(data, source));
        return target;
    },
    retrieveCorrespondingValue: function(data, name) {
        if (this.config.callGetterMethod) {
            var getter = 'get' + name.charAt(0).toUpperCase() +
                name.substring(1);
            if (typeof data[getter] == 'function' &&
                !data.hasOwnProperty(getter))
                return data[getter].call(data);
        }
        var value = data[name];
        if (typeof value == 'function' && !data.hasOwnProperty(name))
            value = value.call(data);
        return value;
    },
    createContent: function(data, source) {
        if (!source.children)
            return null;
        var fragment = this.config.document.createDocumentFragment();
        for (var i = 0; i < source.children.length; ++i) {
            var child = source.children[i];
            if (child.node.nodeType == 1)
                if (child.clonable)
                    fragment.appendChild(child.node.cloneNode(true));
                else
                    this.appendChild(fragment,
                                     this.createElement(data, child));
            else
                fragment.appendChild(child.node.cloneNode(false));

        }
        return fragment;
    },
    appendChild: function(elem, child) {
        if (child)
            elem.appendChild(child);
    }
}

Jamritas.Template.identifier = 'tid';
Jamritas.Template.document = document;
Jamritas.Template.placeholder = ['div', 'span'];
Jamritas.Template.ignoreWhitespace = true;
Jamritas.Template.ignoreComment = true;
Jamritas.Template.viewEmptyArrayAsFalse = false;
Jamritas.Template.recursiveExpantionElement = {};
Jamritas.Template.callGetterMethod = false;

Jamritas.Template.markup = function(html) {
    return function(elem) {
        elem.innerHTML = html;
    };
}

Jamritas.DOM = {};

Jamritas.DOM.append = function(a, b) {
    b.appendChild(Jamritas.DOM.toNode(a, b));
}

Jamritas.DOM.insert = function(a, b) {
    b.parentNode.insertBefore(Jamritas.DOM.toNode(a, b), b);
}

Jamritas.DOM.replace = function(a, b) {
    b.parentNode.replaceChild(Jamritas.DOM.toNode(a, b), b);
}

Jamritas.DOM.remove = function(a) {
    a.parentNode.removeChild(a);
}

Jamritas.DOM.toNode = function(a, b) {
    return typeof a == 'string' ? b.ownerDocument.createTextNode(a) : a;
}

Jamritas.DOM.dom2obj = function(dom, config) {
    var key = config ? config.addKeyProperty :
        Jamritas.DOM.dom2obj.addKeyProperty;
    switch (dom.nodeType) {
    case 1:
        var obj = {};
        if (key)
            obj.$key = Jamritas.DOM.nodeName(dom);
        var attrs = Jamritas.DOM.attrs(dom);
        for (var name in attrs)
            obj['@' + name] = attrs[name];
        if (Jamritas.DOM.isHTMLDocument(dom.ownerDocument) &&
            Jamritas.DOM.nodeName(dom) == 'textarea') {
            obj['textarea'] = dom.value;
            return obj;
        }
        switch (dom.childNodes.length) {
        case 0:
            obj[Jamritas.DOM.nodeName(dom)] = null;
            break;
        case 1:
            obj[Jamritas.DOM.nodeName(dom)] =
                Jamritas.DOM.dom2obj(dom.firstChild, config);
            break;
        default:
            var ary = [], content = {};
            for (var child = dom.firstChild; child;
                 child = child.nextSibling) {
                var tmp = Jamritas.DOM.dom2obj(child, config);
                ary.push(tmp);
                if (child.nodeType == 1) {
                    var tag = Jamritas.DOM.nodeName(child);
                    if (tag in content == false)
                        content[tag] = [];
                    content[tag].push(tmp[tag]);
                }
            }
            for (var name in content)
                ary[name] = content[name].length == 1 ?
                    content[name][0] : content[name];
            obj[Jamritas.DOM.nodeName(dom)] = ary;
        }
        return obj;
    case 3:
        return dom.data;
    case 4:
        var obj = {'#cdata-section': dom.data};
        if (key)
            obj.$key = '#cdata-section';
        return obj;
    case 7:
        var obj = {};
        obj['?' + dom.target] = dom.data;
        if (key)
            obj.$key = '?' + dom.target;
        return obj;
    case 8:
        var obj = {'#comment': dom.data};
        if (key)
            obj.$key = '#comment';
        return obj;
    }
}

Jamritas.DOM.dom2obj.addKeyProperty = false;

Jamritas.DOM.obj2dom = function(obj, config) {
    if (Jamritas.debug && arguments[2] == null)
        Jamritas.findCircularReference(obj);
    var doc = config ? config.document : Jamritas.DOM.obj2dom.document;
    if (typeof obj == 'object') {
        if (obj instanceof Array) {
            var fragment = doc.createDocumentFragment();
            for (var i = 0; i < obj.length; ++i)
                fragment.appendChild(Jamritas.DOM.obj2dom(obj[i], config, true));
            return fragment;
        } else {
            if ('#cdata-section' in obj)
                return doc.createCDATASection(obj['#cdata-section']);
            if ('#comment' in obj)
                return doc.createComment(obj['#comment']);
            var tag, content, attrs = {};
            for (var name in obj)
                if (name.charAt(0) == '@')
                    attrs[name.substring(1)] = obj[name];
                else if (name.charAt(0) == '?')
                    return doc.createProcessingInstruction(name.substring(1),
                                                           obj[name]);
                else {
                    tag = name;
                    content = obj[name];
                }
            var elem = Jamritas.DOM.create(tag, attrs, doc);
            if (content != null)
                elem.appendChild(Jamritas.DOM.obj2dom(content, config, true));
            return elem;
        }
    } else {
        return doc.createTextNode(obj);
    }
}

Jamritas.DOM.obj2dom.document = document;

Jamritas.DOM.dom2text = function(dom) {
    switch (dom.nodeType) {
    case 1:
        var text = '<' + Jamritas.DOM.nodeName(dom);
        var attrs = Jamritas.DOM.attrs(dom);
        for (var name in attrs)
            text += ' ' + name + '="' + Jamritas.DOM.escape(attrs[name]) + '"';
        if (Jamritas.DOM.isHTMLDocument(dom.ownerDocument) &&
            Jamritas.DOM.nodeName(dom) == 'textarea') {
            text += '>' + Jamritas.DOM.escape(dom.value) + '</textarea>';
            return text;
        }
        if (dom.hasChildNodes()) {
            text += '>';
            for (var child = dom.firstChild; child; child = child.nextSibling)
                text += Jamritas.DOM.dom2text(child);
            text += '</' + Jamritas.DOM.nodeName(dom) + '>';
        } else
            text += '/>';
        return text;
    case 3:
        return Jamritas.DOM.escape(dom.data);
    case 4:
        return '<![CDATA[' + dom.data + ']]>';
    case 7:
        return '<?' + dom.target + ' ' + dom.data + '?>';
    case 8:
        return '<!--' + dom.data + '-->';
    }
}

Jamritas.DOM.obj2text = function(obj) {
    if (Jamritas.debug && arguments[1] == null)
        Jamritas.findCircularReference(obj);
    if (typeof obj == 'object') {
        if (obj instanceof Array) {
            var text = '';
            for (var i = 0; i < obj.length; ++i)
                text += Jamritas.DOM.obj2text(obj[i], true);
            return text;
        } else {
            if ('#cdata-section' in obj)
                return '<![CDATA[' + obj['#cdata-section'] + ']]>';
            if ('#comment' in obj)
                return '<!--' + obj['#comment'] + '-->';
            var tag, attrs = '';
            for (var name in obj)
                if (name.charAt(0) == '@')
                    attrs += ' ' + name.substring(1) + '="' +
                        Jamritas.DOM.escape(obj[name]) + '"';
                else if (name.charAt(0) == '?')
                    return '<' + name + ' ' + obj[name] + '?>';
                else
                    tag = name;
            if (obj[tag] == null)
                return '<' + tag + attrs + '/>';
            else
                return '<' + tag + attrs + '>' +
                    Jamritas.DOM.obj2text(obj[tag], true) + '</' + tag + '>';
        }
    } else
        return Jamritas.DOM.escape(obj);
}

Jamritas.DOM.text2dom = function(text) {
    var xmldoc;
    if (typeof DOMParser != 'undefined')
        xmldoc = new DOMParser().parseFromString(text, 'text/xml');
    else {
        if (document.implementation.createDocument)
            xmldoc = document.implementation.createDocument('', '', null);
        else {
            var elem = document.createElement();
            elem.addBehavior('#default#userData');
            xmldoc = elem.XMLDocument;
            xmldoc.validateOnParse = false;
        }
        xmldoc.loadXML(text);
    }
    var xml = xmldoc.documentElement;
    if (xml == null)
        throw Jamritas.createError('can not build xml dom tree');
    return xml;
}

Jamritas.DOM.attrs = function(elem) {
    var attrs = {};
    for (var i = 0; i < elem.attributes.length; ++i) {
        var attr = elem.attributes[i];
        if (attr.specified) {
            var name = Jamritas.DOM.nodeName(attr);
            attrs[name] = attr.value;
            if (attr.name == 'style' &&
                elem.getAttribute('style') == elem.style)
                attrs.style = elem.style.cssText;
            if (Jamritas.DOM.isHTMLDocument(elem.ownerDocument) &&
                Jamritas.DOM.booleanAttribute[name])
                attrs[name] = name;
        }
    }
    if (Jamritas.DOM.isHTMLDocument(elem.ownerDocument))
        switch (Jamritas.DOM.nodeName(elem)) {
        case 'input':
            attrs.value = elem.value;
            attrs.type = elem.type;
            if (elem.type == 'radio' || elem.type == 'checkbox')
                if (elem.checked)
                    attrs.checked = 'checked';
                else
                    delete attrs.checked;
            if (elem.type == 'file')
                delete attrs['html:value'];
            break;
        case 'option':
            if (elem.selected)
                attrs.selected = 'selected';
            else
                delete attrs.selected;
            break;
        case 'button':
            attrs.type = elem.type;
            break;
        }
    return attrs;
}

Jamritas.DOM.booleanAttribute = {
    compact: true, declare: true, defer: true, disabled: true,
    ismap: true, multiple: true, nohref: true, noresize: true,
    noshade: true, nowrap: true, readonly: true
}

Jamritas.DOM.create = function(name, data, document) {
    if (!document)
        document = Jamritas.DOM.create.document;
    if (typeof data == 'object') {
        var elem, tag = name, attrs = data;
        if (typeof ActiveXObject == 'function' &&
            Jamritas.DOM.isHTMLDocument(document)) {
            for (var name in attrs)
                tag += ' ' + name + '="' +
                    Jamritas.DOM.escape(attrs[name]) + '"';
            elem = document.createElement('<' + tag + '>');
        } else {
            elem = document.createElement(tag);
            for (var name in attrs)
                elem.setAttribute(name, attrs[name]);
        }
        return elem;
    } else if (name == '#comment')
        return document.createComment(data);
    else if (name == '#cdata-section')
        return document.createCDATASection(data);
    else if (name.charAt(0) == '?')
        return document.createProcessingInstruction(name.substring(1), data);
}

Jamritas.DOM.create.document = document;

Jamritas.DOM.nodeName = function(node) {
    return Jamritas.DOM.isHTMLDocument(node.ownerDocument) ?
        node.nodeName.toLowerCase() : node.nodeName;
}

Jamritas.DOM.isHTMLDocument = function(document) {
    return 'body' in document;
}

Jamritas.DOM.escape = function(obj) {
    return obj.toString().replace(/[<>&\"]/g, function(c) {
        return {'<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;'}[c];
    });
}

Jamritas.Event = {
    add: function(node, type, handler) {
        var index = Jamritas.Event.holder.length;
        var listener = Jamritas.Event.createListener(index);
        if (node.addEventListener)
            node.addEventListener(type, listener, false);
        else if (node.attachEvent)
            node.attachEvent('on' + type, listener);
        Jamritas.Event.holder.push([node, type, handler, listener]);
    },
    remove: function(node, type, handler) {
        for (var i = 0; i < Jamritas.Event.holder.length; ++i) {
            var ary = Jamritas.Event.holder[i];
            if (ary && ary[0] == node && ary[1] == type && ary[2] == handler) {
                if (node.removeEventListener)
                    node.removeEventListener(type, ary[3], false);
                else if (node.detachEvent)
                    node.detachEvent('on' + type, ary[3]);
                Jamritas.Event.holder[i] = null;
                break;
            }
        }
    },
    dispatch: function(node, type, obj) {
        if (typeof ActiveXObject == 'function') {
            var event = document.createEventObject();
            for (var name in obj)
                event[name] = obj[name];
            node.fireEvent('on' + type, event);
        } else {
            var event;
            if (type == 'click' || type.match(/^mouse/)) {
                event = document.createEvent('MouseEvents');
                event.initMouseEvent(type, true, true, window, 1,
                                     obj.screenX, obj.screenY,
                                     obj.clientX, obj.clientY,
                                     obj.ctrlKey, obj.altKey,
                                     obj.shiftKey, obj.metaKey,
                                     obj.button, obj.relatedTarget);
            } else {
                event = document.createEvent('HTMLEvents');
                event.initEvent(type, true, true);
            }
            node.dispatchEvent(event);
        }
    },
    createListener: function(index) {
        return function(event) {
            var node = Jamritas.Event.holder[index][0];
            var type = Jamritas.Event.holder[index][1];
            var handler = Jamritas.Event.holder[index][2];
            if (typeof ActiveXObject == 'function') {
                var expando = document.expando;
                document.expando = true;
                event.metaKey = event.metaKey || false;
                event.target = event.srcElement || document;
                event.currentTarget = node;
                event.stopPropagation = Jamritas.Event.stopPropagation;
                event.preventDefault = Jamritas.Event.preventDefault;
                var elem = document.compatMode == 'CSS1Compat' ?
                    document.documentElement : document.body;
                event.pageX = event.clientX + elem.scrollLeft;
                event.pageY = event.clientY + elem.scrollTop;
                if (type == 'mouseover')
                    event.relatedTarget = event.fromElement;
                else if (type == 'mouseout')
                    event.relatedTarget = event.toElement;
                document.expando = expando;
            }
            handler.call(node, event);
        };
    },
    stopPropagation: function() {
        window.event.cancelBubble = true;
    },
    preventDefault: function() {
        window.event.returnValue = false;
    },
    holder: []
}

Jamritas.HTTP = {
    encode: function(obj) {
        var ary = [];
        for (var name in obj) {
            var value = obj[name];
            if (value instanceof Array)
                for (var i = 0; i < value.length; ++i)
                    ary.push(encodeURIComponent(name) + '=' +
                             encodeURIComponent(value[i]));
            else
                ary.push(encodeURIComponent(name) + '=' +
                         encodeURIComponent(value));
        }
        return ary.join(Jamritas.HTTP.encode.separator);
    },
    Client: function(config) {
        this.header = {};
        if (!config)
            config = {};
        this.receiver = config.receiver;
        this.username = config.username;
        this.password = config.password;
        this.onrequest = null;
        this.onresponse = null;
    },
    Thread: function(xmlhttp) {
        this.xmlhttp = xmlhttp;
    }
}

Jamritas.HTTP.encode.separator = ';';

Jamritas.HTTP.Client.prototype = {
    get: function(path, query, handler) {
        path = this.joinPathAndQuery(path, query);
        return this.sendRequest('GET', path, null, handler);
    },
    post: function(path, body, handler) {
        if (body.toString == Object.prototype.toString)
            body = Jamritas.HTTP.encode(body);
        return this.sendRequest('POST', path, body, handler);
    },
    head: function(path, query, handler) {
        path = this.joinPathAndQuery(path, query);
        return this.sendRequest('HEAD', path, null, handler);
    },
    joinPathAndQuery: function(path, query) {
        if (query != null) {
            if (query.toString == Object.prototype.toString)
                query = Jamritas.HTTP.encode(query);
            path += '?' + query;
        }
        return path;
    },
    sendRequest: function(method, path, body, handler) {
        var xmlhttp = window.XMLHttpRequest ?
            new XMLHttpRequest : new ActiveXObject('Microsoft.XMLHTTP');
        var async = handler != undefined;
        xmlhttp.open(method, path, async, this.username, this.password);
        for (var name in this.header)
            xmlhttp.setRequestHeader(name, this.header[name]);
        if (method == 'POST' && 'Content-Type' in this.header == false)
            xmlhttp.setRequestHeader('Content-Type',
                                     'application/x-www-form-urlencoded');
        if (this.mimetype && xmlhttp.overrideMimeType)
            xmlhttp.overrideMimeType(this.mimetype);
        if (this.onrequest != null)
            try {
                this.onrequest.call(this.receiver);
            } catch (e) {}
        if (async) {
            var thread = new Jamritas.HTTP.Thread(xmlhttp);
            xmlhttp.onreadystatechange =
                this.createResponseHandler(xmlhttp, handler, this, thread);
            xmlhttp.send(body);
            return thread;
        } else {
            xmlhttp.send(body);
            if (this.onresponse != null)
                try {
                    this.onresponse.call(this.receiver);
                } catch (e) {}
            var response = this.createResponseObject(xmlhttp);
            if (response instanceof Error)
                throw response;
            return response;
        }
    },
    createResponseHandler: function(xmlhttp, handler, client, thread) {
        return function() {
            if (xmlhttp.readyState == 4) {
                if (client.onresponse != null)
                    try {
                        client.onresponse.call(client.receiver);
                    } catch (e) {}
                var response = client.createResponseObject(xmlhttp);
                thread.result = handler.call(client.receiver, response);
                if (Jamritas.debug && response instanceof Error)
                    throw response;
            }
        };
    },
    createResponseObject: function(xmlhttp) {
        try {
            if (/^\d{3}$/.test(xmlhttp.status) ||
                location.protocol == 'file:') {
                var obj = {text: xmlhttp.responseText, status: xmlhttp.status,
                           reason: xmlhttp.statusText, header: {}};
                if (xmlhttp.responseXML &&
                    xmlhttp.responseXML.documentElement)
                    obj.xml = xmlhttp.responseXML.documentElement;
                if (xmlhttp.getAllResponseHeaders()) {
                    var ary = xmlhttp.getAllResponseHeaders().split('\n');
                    for (var i = 0; i < ary.length; ++i)
                        if (ary[i].match(/^(.*?): (.*?)\r?$/))
                            obj.header[RegExp.$1] = RegExp.$2;
                }
                return obj;
            }
        } catch (e) {}
        return Jamritas.createError('no response from server');
    }
}

Jamritas.HTTP.Thread.prototype = {
    abort: function() {
        if (this.xmlhttp.readyState != 4) {
            this.xmlhttp.onreadystatechange = function() {};
            this.xmlhttp.abort();
        }
    }
}

Jamritas.Loader = function(client) {
    this.client = client || new Jamritas.HTTP.Client;
    this.client.mimetype = 'text/xml';
    this.setRandomNumberToQuery = false;
};

Jamritas.Loader.prototype = {
    loadtext: function(path, handler) {
        return this.load(path, handler, this.extractText);
    },
    loadxml: function(path, handler) {
        return this.load(path, handler, this.extractRootElement);
    },
    load: function(path, handler, extract) {
        if (this.setRandomNumberToQuery && path.indexOf('?') == -1)
            path += '?' + String(Math.random()).substring(2);
        if (handler == null) {
            var result = extract(this.client.get(path));
            if (result instanceof Error)
                throw result;
            return result;
        } else
            return this.client.get(path, null,
                                   this.wrapHandler(handler,
                                                    this.client.receiver,
                                                    extract));
    },
    wrapHandler: function(handler, receiver, extract) {
        return function(response) {
            var obj = extract(response);
            var result = handler.call(receiver, obj);
            if (Jamritas.debug && obj instanceof Error)
                throw obj;
            return result;
        };
    },
    extractText: function(response) {
        if (response instanceof Error)
            return response;
        if (response.status == 200 || response.status == 304 ||
            response.status == 0 || response.status == null)
            return response.text;
        else
            return Jamritas.createError(response.reason);
    },
    extractRootElement: function(response) {
        if (response instanceof Error)
            return response;
        if (response.status != 200 && response.status != 304 &&
            response.status != 0 && response.status != null)
            return Jamritas.createError(response.reason);
        if (response.xml)
            return response.xml;
        try {
            return Jamritas.DOM.text2dom(response.text);
        } catch (e) {
            return e;
        }
    }
};

Jamritas.XMLRPC = {
    Client: function(path, methods, client) {
        if (client == null)
            client = new Jamritas.HTTP.Client;
        client.header['Content-Type'] = 'text/xml';
        if (methods == null)
            methods = Jamritas.XMLRPC.createMethod('system.listMethods',
                                                   client, path)();
        for (var i = 0; i < methods.length; ++i) {
            var ary = methods[i].split('.');
            var method = ary.pop();
            var obj = this;
            for (var j = 0; j < ary.length; ++j) {
                if (!obj[ary[j]])
                    obj[ary[j]] = {};
                obj = obj[ary[j]];
            }
            obj[method] =
                Jamritas.XMLRPC.createMethod(methods[i], client, path);
        }
    },
    encode: function(obj) {
        if (Jamritas.debug && arguments[1] == null)
            Jamritas.findCircularReference(obj);
        switch (typeof obj) {
        case 'string':
            return {value: obj};
        case 'number':
            return {value: Math.floor(obj) == obj ?
                {i4: obj} : {'double': obj}};
        case 'boolean':
            return {value: {'boolean': obj ? 1 : 0}};
        case 'object':
            if (obj instanceof Array) {
                var ary = [];
                for (var i = 0; i < obj.length; ++i)
                    ary.push(Jamritas.XMLRPC.encode(obj[i], true));
                return {value: {array: {data: ary}}};
            } else if (obj instanceof Date) {
                var z = function(n, w) {
                    var s = n.toString();
                    while (s.length < w)
                    s = '0' + s;
                    return s;
                };
                var datetime = z(obj.getFullYear(), 4) +
                    z(obj.getMonth() + 1, 2) + z(obj.getDate(), 2) +
                    'T' + obj.toTimeString().substring(0, 8);
                return {value: {'dateTime.iso8601': datetime}};
            } else {
                if (obj.toString != Object.prototype.toString)
                    return Jamritas.XMLRPC.encode(obj.toString(), true);
                var ary = [];
                for (var key in obj) {
                    var value = obj[key];
                    if (value != null && typeof value != 'function')
                        ary.push({member: [{name: key},
                                           Jamritas.XMLRPC.encode(value, true)]});
                }
                return {value: {struct: ary}};
            }
        }
    },
    decode: function(value) {
        if (value == null)
            return '';
        if (typeof value == 'string')
            return value;
        for (var type in value)
            ;
        value = value[type];
        switch (type) {
        case 'string':
            return value || '';
        case 'i4': case 'int':
            return parseInt(value);
        case 'double':
            return parseFloat(value);
        case 'boolean':
            return value == 1;
        case 'dateTime.iso8601':
            if (value.match(/^(\d{4})(\d\d)(\d\d)T(\d\d):(\d\d):(\d\d)$/))
                return new Date(RegExp.$1, parseInt(RegExp.$2, 10) - 1,
                                RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6);
        case 'array':
            var ary = [];
            if (value.data != null) {
                if (value.data.value instanceof Array == false)
                    value.data.value = [value.data.value];
                for (var i = 0; i < value.data.value.length; ++i)
                    ary.push(Jamritas.XMLRPC.decode(value.data.value[i]));
            }
            return ary;
        case 'struct':
            var obj = {};
            if (value != null) {
                if (value.member.name)
                    value.member = [value.member];
                for (var i = 0; i < value.member.length; ++i)
                    obj[value.member[i].name] =
                        Jamritas.XMLRPC.decode(value.member[i].value);
            }
            return obj;
        }
    },
    createMethod: function(method, client, path) {
        return function() {
            var last = arguments.length;
            if (arguments.length > 0 &&
                typeof arguments[last - 1] == 'function')
                var handler = arguments[--last];
            var params = [];
            for (var i = 0; i < last; ++i)
                params.push({param: Jamritas.XMLRPC.encode(arguments[i])});
            var body = Jamritas.DOM.obj2text({methodCall:
                [{methodName: method}, {params: params}]});
            if (handler)
                return client.post(path, body, function(response) {
                    var result = Jamritas.XMLRPC.getResult(response);
                    var value = handler.call(client.receiver, result);
                    if (Jamritas.debug && result instanceof Error)
                        throw result;
                    return value;
                });
            else {
                var result =
                    Jamritas.XMLRPC.getResult(client.post(path, body));
                if (result instanceof Error)
                    throw result;
                return result;
            }
        };
    },
    getResult: function(response) {
        if (response instanceof Error)
            return response;
        if (response.status != 200)
            return Jamritas.createError(response.reason);
        var obj = Jamritas.DOM.dom2obj(response.xml);
        if (obj.methodResponse.fault) {
            var fault =
                Jamritas.XMLRPC.decode(obj.methodResponse.fault.value);
            var error = Jamritas.createError(fault.faultString);
            error.code = fault.faultCode;
            return error;
        }
        return Jamritas.XMLRPC.decode(obj.methodResponse.params.param.value);
    }
}

Jamritas.JSONRPC = {
    Client: function(path, methods, client) {
        if (client == null)
            client = new Jamritas.HTTP.Client;
        client.header['Content-Type'] = 'text/plain';
        if (methods == null)
            methods = Jamritas.JSONRPC.createMethod('system.listMethods',
                                                    client, path)();
        for (var i = 0; i < methods.length; ++i) {
            var ary = methods[i].split('.');
            var method = ary.pop();
            var obj = this;
            for (var j = 0; j < ary.length; ++j) {
                if (!obj[ary[j]])
                    obj[ary[j]] = {};
                obj = obj[ary[j]];
            }
            obj[method] =
                Jamritas.JSONRPC.createMethod(methods[i], client, path);
        }
    },
    encode: function(obj) {
        if (Jamritas.debug && arguments[1] == null)
            Jamritas.findCircularReference(obj);
        if (obj == null)
            return String(obj);
        switch (typeof obj) {
        case 'string':
            return '"' + Jamritas.JSONRPC.escape(obj) + '"';
        case 'number':
        case 'boolean':
            return String(obj);
        case 'object':
            if (obj instanceof Array) {
                var ary = [];
                for (var i = 0; i < obj.length; ++i)
                    ary.push(Jamritas.JSONRPC.encode(obj[i], true));
                return '[' + ary.join(',') + ']';
            } else {
                if (obj.toString == Object.prototype.toString) {
                    var ary = [];
                    for (var key in obj) {
                        var value = obj[key];
                        if (typeof value != 'function' &&
                            typeof value != 'undefined')
                            ary.push(Jamritas.JSONRPC.encode(key, true) + ':' +
                                     Jamritas.JSONRPC.encode(value, true));
                    }
                    return '{' + ary.join(',') + '}';
                } else
                    return Jamritas.JSONRPC.encode(obj.toString(), true);
            }
        }
    },
    escape: function(str) {
        var code = ['5C', '08', '09', '0A', '0B', '0C', '0D', '22', '27'];
        var sequence = ['\\\\', '\\b', '\\t', '\\n',
                        '\\v', '\\f', '\\r', '\\\"', '\\\''];
        for (var i = 0; i < sequence.length; ++i)
            str = str.replace(new RegExp('\\x' + code[i], 'g'), sequence[i]);
        return str;
    },
    createMethod: function(method, client, path) {
        return function() {
            var last = arguments.length;
            if (arguments.length > 0 &&
                typeof arguments[last - 1] == 'function')
                var handler = arguments[--last];
            var params = [];
            for (var i = 0; i < last; ++i)
                params.push(arguments[i]);
            var id = Math.floor(Math.random() * (1 << 30));
            var request = {method: method, params: params, id: id};
            var body = Jamritas.JSONRPC.encode(request);
            if (handler)
                return client.post(path, body, function(response) {
                    var result = Jamritas.JSONRPC.getResult(response, id);
                    var value = handler.call(client.receiver, result);
                    if (Jamritas.debug && result instanceof Error)
                        throw result;
                    return value;
                });
            else {
                var result =
                    Jamritas.JSONRPC.getResult(client.post(path, body), id);
                if (result instanceof Error)
                    throw result;
                return result;
            }
        };
    },
    getResult: function(response, id) {
        if (response instanceof Error)
            return response;
        if (response.status != 200)
            return Jamritas.createError(response.reason);
        var obj = eval('(' + response.text + ')');
        if (obj.error)
            return Jamritas.createError(obj.error);
        if (obj.id != id)
            return Jamritas.createError('Mismatch between request id and response id');
        return obj.result;
    }
}

Jamritas.Cookie = {};

Jamritas.Cookie.load = function(name, bool) {
    var obj = {};
    var cookies = document.cookie.split('; ');
    for (var i = 0; i < cookies.length; ++i) {
        var pair = cookies[i];
        var eq = pair.indexOf('=');
        var k = decodeURIComponent(pair.substring(0, eq));
        var v = decodeURIComponent(pair.substring(eq + 1));
        if (obj[k])
            obj[k].push(v);
        else
            obj[k] = [v];
    }
    if (arguments.length == 0)
        return obj;
    if (obj[name])
        return bool ? obj[name] : obj[name][0];
    if (bool)
        return [];
}

Jamritas.Cookie.save = function(name, value, config) {
    var cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
    config = config || {};
    if (config.expires) {
        var gmt = config.expires.toGMTString();
        if (gmt.length < 29)
            gmt = gmt.substring(0, 5) + '0' + gmt.substring(5);
        gmt = gmt.substring(0, 7) + '-' + gmt.substring(8, 11) + '-' +
            gmt.substring(12);
        if (gmt.substring(26) == 'UTC')
            gmt = gmt.substring(0, 26) + 'GMT';
        cookie += '; expires=' + gmt;
    }
    if (config.domain)
        cookie += '; domain=' + config.domain;
    if (config.path)
        cookie += '; path=' + config.path;
    if (config.secure)
        cookie += '; secure';
    document.cookie = cookie;
}

Jamritas.Cookie.remove = function(name, config) {
    config = config || {};
    Jamritas.Cookie.save(name, '',
                         {expires: new Date(0), domain: config.domain,
                          path: config.path, secure: config.secure});
}

Jamritas.createError = function(message) {
    var error = new Error;
    error.name = 'JamritasError';
    error.message = message;
    return error;
}

Jamritas.findCircularReference = function(obj, stack) {
    if (stack == null)
        stack = [];
    if (typeof obj == 'object' && obj != null) {
        for (var i = 0; i < stack.length; ++i)
            if (stack[i] == obj)
                throw Jamritas.createError('circular reference is found');
        stack.push(obj);
        if (obj instanceof Array)
            for (var i = 0; i < obj.length; ++i)
                Jamritas.findCircularReference(obj[i], stack);
        else
            for (var key in obj)
                Jamritas.findCircularReference(obj[key], stack);
        stack.pop();
    }
}

Jamritas.debug = false;

