/**

*/

// Create a few new tag function
forEach(['dl','dd','dt'], function(tag) {
    window[tag.toUpperCase()] = createDOMFunc(tag);
});


if (!XoxoOutliner) XoxoOutliner = {};
XoxoOutliner.Conversions = {

    /**
    */
    OPMLtoDOMConverters: {
    
        /**
        */
        'link': function(item, type, attrs) {
            var node = LI({'class':'link'}, A({'href':attrs.url.value}));
            node.firstChild.innerHTML = attrs.text.value;
            return node;
        },

        /**
        */
        '__default__': function(item, type, attrs) {

            var text_node = SPAN({});
            text_node.innerHTML = attrs.text.value;

            var data_node = DL({});
            forEach(attrs, function(attr) {
                if (attr.name == 'text') return;
                appendChildNodes(data_node, 
                    DT({}, attr.name),
                    DD({}, attr.value)
                );
            });

            var node = LI({}, text_node, data_node);
            return node;
        }

    },

    /**
        Decode attributes defined as a <dl> DOM structure
        into a JS object.

        TODO: What about tagNameNS?
        TODO: Use MochiKit scrapeText() in lieu of innerHTML?
    */
    fromDLtoObject: function(dl) {
        var obj = {};
        var name = null;
        for (var i=0, node; node=dl.childNodes[i]; i++) {
            if ('dt' == node.tagName.toLowerCase())
                name = node.innerHTML;
            if ('dd' == node.tagName.toLowerCase() && name) 
                obj[name] = node.innerHTML;
        }
        return obj;
    },

    /**
        Encode a simple JS object as a <dl> DOM structure.

        TODO: Handle more complex JS objects, only handles hashes.
    */
    fromObjectToDL: function(obj) {
        var data_node = DL({});
        for (key in obj) {
            appendChildNodes(data_node, [ 
                DT({}, key), DD({}, obj[key]) 
            ]);
        }
        return data_node;
    },

    /**
        Parse OPML into a DOM-based outline.
    */
    fromOPMLtoDOM: function(doc) {
        var _this = this;
        var conv  = this.OPMLtoDOMConverters;
        var ol    = UL();
        
        // If the given doc node is not an outline node, assume it is
        // an OPML document root node and look for the body.
        if (doc.tagName != "outline") 
            doc = doc.getElementsByTagName('body')[0];

        // Process all the <outline /> nodes contained in the doc node.
        forEach(doc.childNodes, function(item) {
            if (item.tagName == 'outline') {
                
                var attrs = item.attributes;
                var type  = (attrs.type) ? attrs.type.value : '';
                var fn    = conv[type] ? conv[type] : conv['__default__'];
                var node  = fn(item, type, attrs);

                if (node) {
                    appendChildNodes(ol, node);
                    if (item.childNodes) {
                        var sub_ol = _this.fromOPMLtoDOM(item);
                        if (sub_ol && sub_ol.childNodes.length) 
                            appendChildNodes(node, sub_ol);
                    }
                }

            }

        });
        
        return ol;
    },

    /**
        Serialize a DOM-based outline into XOXO.
    */
    fromDOMtoXOXO: function(node) {

        switch (node.nodeType) {

            case Node.TEXT_NODE:
                return node.nodeValue;

            case Node.ELEMENT_NODE:
                var node_name = node.nodeName.toLowerCase();
                
                var out = '';
                out += '<' + node_name;

                // Process element attributes, performing some filtering.
                for (var i=0, attr; attr=node.attributes[i]; i++) {
                    var name    = attr.name;
                    var lc_name = name.toLowerCase();
                    var value   = attr.value;

                    // HACK: Seems like some browsers like to uppercase some significant attributes.
                    ['name', 'class', 'id', 'xmlns'].forEach(function(n) {
                        if (lc_name == n) name = lc_name;
                    });
    
                    // Filter out all "outliner_*" classes.
                    if ("class" == lc_name) {
                        var prefix = "outliner_";
                        value = value.split(" ").filter(function(cn) {
                            
                            // HACK: Translate outliner collapse into compact attribute.
                            if ('outliner_item_collapsed' == cn) out += ' compact="compact"';
                            return prefix != cn.substring(0, prefix.length);

                        }).join(" ");
                    }

                    // Skip any inline styles or JS handlers.
                    if (lc_name == "style") continue;
                    if (/^on/.test(lc_name)) continue;

                    // Finally, drop in the filtered attribute.
                    if (value) out += ' '+name+'="'+this.encodeAttr(value)+'"';
                }

                var sub_out = '';
                for (var i=0, child; child=node.childNodes[i]; i++) {

                    // HACK: Lots of head elements that aren't useful to save.  Might be better to do in XSL?
                    if (/head/i.test(child.parentNode.nodeName)) {
                        if (/script/i.test(child.nodeName)) continue;
                        if (/link/i.test(child.nodeName) && 
                            /stylesheet/i.test(child.getAttribute('rel'))) continue;
                        if (/meta/i.test(child.nodeName) && 
                            child.getAttribute('http-equiv') ) continue
                    }

                    // Skip elements marked as outliner injected elements.
                    if (child.className == "outliner_chrome") continue;
                    if (/^outliner/i.test(child.id)) continue;

                    sub_out += this.fromDOMtoXOXO(child);
                }

                if (sub_out)
                    return out + '>' + sub_out + '</' + node_name + '>';
                else 
                    return out + '/>';

            default:
                return '';
        }
    
    },

    /**
        Serialize a DOM-based outline into OPML.

        TODO: Handle the head & title better.
    */
    fromDOMtoOPML: function(node, indent_level) {

        // Default to outline root node on no parameters
        if (!indent_level) {
            var out = '<?xml version="1.0" encoding="UTF-8"?>\n';
            out    += '<!-- OPML generated by XOXO Outliner -->\n';
            out    += '<opml version="1.1">\n';
            out    += '    <head>\n';
            out    += '        <title>untitled</title>\n';
            out    += '    </head>\n';
            out    += '    <body>\n';
            out    +=          this.fromDOMtoOPML(node, 2);
            out    += '    </body>\n';
            out    += '</opml>';
            return out;
        }

        var indent = '';
        for (var i=0; i<indent_level; i++) indent += '    ';

        var out = '';
        for (var i=0, cn; cn = node.childNodes[i]; i++) {
            // Only process <li/> children of <ul/> and <ol/>
            if (cn.nodeName.toLowerCase() != 'li') continue;

            var node_out = indent + '<outline ';
            var text_out = 'text="';
            var sub_out  = '';
            var attrs    = {};

            for (var j=0, scn; scn = cn.childNodes[j]; j++) {
                switch (scn.nodeType) {

                    case Node.TEXT_NODE:
                        text_out += this.encodeAttr(scn.nodeValue);
                        break;

                    case Node.ELEMENT_NODE:
                        var name = scn.nodeName.toLowerCase();
                        if ('ul' == name || 'ol' == name)
                            sub_out += this.fromDOMtoOPML(scn, indent_level+1);
                        else if ('dl' == name)
                            attrs = this.fromDLtoObject(scn);
                        else if ('span' == name && !/handle/.test(scn.className) )
                            text_out += this.encodeAttr(scn.innerHTML);
                        break;
                }
            }

            node_out += text_out + '"';

            // Serialize the rest of the <dl> attributes found in the XOXO node.
            for (k in attrs)
                node_out += ' ' + k + '="'+ this.encodeAttr(attrs[k]) +'"';

            if (sub_out.length)
                node_out += '>' + "\n" + sub_out + indent  + '</outline>';
            else
                node_out += '/>';

            out += node_out + "\n";
        }
        return out;
    
    },

    /**
        Given a string, encode it for use as an attribute value.
    */
    encodeAttr: function(str) {
        str = strip(str);
        str = str.replace(/\n\r/g, "\n");
        str = str.replace(/\n/g,   "&#xA;");
        str = str.replace(/&/g,    "&amp;");
        str = str.replace(/"/g,    "&quot;");
        str = str.replace(/</g,    "&lt;");
        str = str.replace(/>/g,    "&gt;");
        return str;
    }

}


