var global = this;
global.templess = new function templess() {
    /* client-side version of 'templess', a simple XML templating language

       this is a JavaScript implementation of a Python library called Templess,
       a very simple, XML-compliant XML templating language (so the templates
       themselves are valid XML)

       it tries to follow the Python version as much as possible, although
       there are some JS issues (of course ;) that make it somewhat different
    */
    var templess = this;

    var XMLNS = 'http://johnnydebris.net/xmlns/jstempless';

    var Template = this.Template = function Template(root, prefix) {
        if (root) {
            this._init(root, prefix);
        };
    };

    Template.prototype._init = function _init(root, prefix) {
        this.root = root;
        this.prefix = prefix;
    };

    Template.prototype.convert = function convert(context) {
        /* convert the root node

            context is an associative array (object) to use for interpolation
        */
        var node = new templess.TemplessNode(this.root.cloneNode(true),
                                             this.prefix);
        return node.convert(context);
    };

    var TemplessNode = this.TemplessNode = function TemplessNode(el, prefix) {
        if (el) {
            this._init(el, prefix);
        };
    };

    TemplessNode.prototype._init = function _init(el, prefix) {
        this.element = el;
        this.prefix = prefix;
    };

    TemplessNode.prototype.convert = function convert(context) {
        if (!this._handle_cond(context)) {
            if (this.element.parentNode) {
                this.element.parentNode.removeChild(this.element);
            };
            return; /* no return value -> remove node */
        };
        this._handle_attrs(context);
        var replacekey = this._getattr('replace');
        var contentkey = this._getattr('content');
        var cvalue = null;;
        if (replacekey) {
            cvalue = context[replacekey];
        } else if (contentkey) {
            cvalue = context[contentkey];
        };

        if (cvalue === null) {
            for (var i=0; i < this.element.childNodes.length; i++) {
                this._convert_child(this.element.childNodes[i], context);
            };
            return this.element;
        } else if (cvalue instanceof Array) {
            var ret = [];
            for (var i=0; i < cvalue.length; i++) {
                var currvalue = cvalue[i];
                if (typeof currvalue == 'object') {
                    // perform sub-interpolation - only for t:content
                    var clone = this.element.cloneNode(true);
                    for (var j=0; j < clone.childNodes.length; j++) {
                        this._convert_child(clone.childNodes[j], currvalue);
                    };
                    if (this.element.parentNode && contentkey) {
                        this.element.parentNode.insertBefore(clone,
                                                             this.element);
                    } else if (this.element.parentNode && replacekey) {
                        while (clone.hasChildNodes()) {
                            var firstChild = clone.firstChild;
                            this.element.parentNode.insertBefore(
                                firstChild, this.element);
                        };
                    };
                    ret.push(clone);
                } else {
                    var node = this._insert_value(currvalue, !!replacekey);
                    if (node) {
                        if (node.parentNode &&
                                node.parentNode == this.element.parentNode) {
                            this._convert_child(node, context);
                        };
                        ret.push(node);
                    };
                };
            };
            if (this.element.parentNode) {
                this.element.parentNode.removeChild(this.element);
            };
            return ret;
        } else {
            var node = this._insert_value(cvalue, !!replacekey);
            if (replacekey && node.parentNode &&
                    node.parentNode == this.element.parentNode) {
                for (var i=0; i < node.childNodes.length; i++) {
                    this._convert_child(node.childNodes[i], context);
                };
            };
            if (this.element.parentNode) {
                this.element.parentNode.removeChild(this.element);
            };
            return node;
        };
    };

    TemplessNode.prototype._insert_value = 
            function _insert_value(value, replace) {
        if (replace) {
            if (this.element.parentNode) {
                try {
                    this.element.parentNode.insertBefore(value, this.element);
                } catch(e) {
                    this.element.parentNode.insertBefore(
                        this.element.ownerDocument.createTextNode(value),
                        this.element);
                };
            };
            return value;
        } else {
            var clone = this.element.cloneNode(false);
            try {
                clone.appendChild(value);
            } catch(e) {
                clone.appendChild(clone.ownerDocument.createTextNode(value));
            };
            if (this.element.parentNode) {
                this.element.parentNode.insertBefore(clone, this.element);
            };
            return clone;
        };
    };

    TemplessNode.prototype._convert_child =
            function _convert_child(el, context) {
        if (el.nodeType != 1) {
            return el;
        };
        var node = new templess.TemplessNode(el, this.prefix);
        node.convert(context);
    };

    TemplessNode.prototype._handle_cond = function _handle_cond(context) {
        var condkey = this._getattr('cond');
        if (condkey) {
            return (!!context[condkey]);
        };
        var notkey = this._getattr('not');
        if (notkey) {
            return (!context[notkey]);
        };
        return true;
    };

    TemplessNode.prototype._getattr = function _getattr(name) {
        if (this.element.getAttributeNS) {
            var value = this.element.getAttributeNS(XMLNS, name);
            if (value) {
                this.element.removeAttributeNS(XMLNS, name);
                return value;
            };
        };
        var value = this.element.getAttribute(this.prefix + name);
        if (value) {
            this.element.removeAttribute(this.prefix + name);
        };
        return value;
    };

    TemplessNode.prototype._handle_attrs = function _handle_attrs(context) {
        var attrpairs = this._getattr('attr');
        if (!attrpairs) {
            return;
        };
        var pairs = attrpairs.split(';');
        for (var i=0; i < pairs.length; i++) {
            var kvpair = string.strip(pairs[i]).split(' ');
            if (kvpair.length != 2) {
                var msg = 'unexpected value for t:attr="' + attrpairs +
                          '" (' + pairs[i] + ')';
                if (global.exception) {
                    throw(new exception.AssertionError(msg));
                } else {
                    throw(msg);
                };
            };
            var attrname = kvpair[0];
            var ckey = kvpair[1];
            var value = context[ckey];
            if (value === null || value === undefined) {
                var msg = 'key ' + ckey + ' not found in context ' + context;
                if (global.exception) {
                    throw(new exception.KeyError(msg));
                } else {
                    throw(msg);
                };
            } else if (value === false) {
                // value is false -> don't place the attribute
                continue;
            };
            this.element.setAttribute(attrname, value);
        };
    };
}();


