var instant_toc = {
    items: [],
    toc_div: false,
    ref_div: false,
    the_element: false,
    thedoc: false,
    stack: [],
    list_type: 'ul',
    list_type_ref: 'ol',
//    nodes_to_find: {H1: '1', H2: '2', H3: '3', H4: '4', H5: '5', H6: '6'},
    nodes_to_find: {H2: '2', H3: '3', H4: '4', H5: '5', H6: '6'},
    protocols: ['http','ftp','https'],

    /**
     * Script flow controller
     *
     * @param {string} start_id      The ID of the element that contains the content.
     *                                  Default is the BODY element
     * @param {string} output_id     ID of the element that will contain
     *                                  the result TOC
     * @param {string} output_ref_id ID of the element that will contain the result
     *                                  list of external links
     * @param {int}    heading_level From which heading level to start (1 to 6),
     *                                  not yet implemented
     */
    parse: function (start_id, output_id, output_ref_id, heading_level)
    {
        // initialize the environment pass all parameters
        this.init(start_id, output_id, output_ref_id, heading_level);

        // if the content is found, run through it to extract the headings
        if (this.the_element) {
            this.traverse();
        }
        // run through the extracted headings and generate TOC
        this.generateTOC();

        // add the TOC to the element specified
        if (this.toc_div) {
            this.toc_div.appendChild(this.stack[0].list);
        }

        // run through all the links and list them
        if (this.ref_div) {
            this.appendReferences();
        }
    },

    /**
     * Initialization
     */
    init: function(start_id, output_toc_id, output_ref_id, heading_level)
    {

        if (start_id) {
            this.thedoc = document.getElementById(start_id);
        } else {
            this.thedoc = document.getElementsByTagName('td').item(0);
        }
        this.the_element = this.thedoc.firstChild;

        if (output_toc_id) {
            this.toc_div = document.getElementById(output_toc_id);
        } else {
            if (document.getElementById('instant-toc')) {
                this.toc_div = document.getElementById('instant-toc');
            } else {
                // insert the element if needed - @todo
            }
        }

        if (output_ref_id) {
            this.ref_div = document.getElementById(output_ref_id);
        } else {
            if (document.getElementById('suddenly-structured-references')) {
                this.ref_div = document.getElementById('suddenly-structured-references');
            } else {
                // insert the element if needed - @todo
            }
        }

        // not implemented - @todo
        if (!heading_level || isNaN(heading_level) || heading_level < 1 || heading_level > 6) {
            heading_level = 1;
        }
    },

    /**
     * Generates a random ID, unique for the document
     */
    getRandomId: function ()
    {
        var rand = Math.round((Math.random() * 99999)+10000);
        var id = 'instant-toc-item-' + rand;
        if (document.getElementById(id)) {
            return this.getRandomId();
        }
        return id;
    },
    /**
     * Navigates throught the document and populates this.items
     * with all H1-H6. Also adds IDs to those headings that miss them.
     */
    traverse: function()
    {
        if (this.nodes_to_find[this.the_element.nodeName]) {
            var element_level = this.nodes_to_find[this.the_element.nodeName];
            if (!this.the_element.id) {
                this.the_element.id = this.getRandomId();
            }

            this.items[this.items.length] = {
                title: this.the_element.innerHTML, // it should be textContent, you IE! arrrgh
                id: this.the_element.id,
                level: this.nodes_to_find[this.the_element.nodeName]
            }
        }

        if (this.the_element.nextSibling) {
            this.the_element = this.the_element.nextSibling;
            this.traverse();
        }
    },
    /**
     * Generats lists and items for the TOC
     */
    generateTOC: function ()
    {
        for (var i = 0, max = this.items.length; i < max; i++) {
            var last = this.stack.length-1;
            if (
                this.stack.length == 0
             || this.stack[last].level < this.items[i].level
            ) {
                var new_list = document.createElement(this.list_type);
                this.stack.push({list: new_list, level: this.items[i].level});
            } else if (
                this.stack[last].level > this.items[i].level
            ) {
                while(this.stack[this.stack.length-1].level > this.items[i].level) {
                    this.stack[this.stack.length-2].list.appendChild(this.stack[this.stack.length-1].list);
                    this.stack.pop();
                }
            }
            this.stack[this.stack.length-1].list.appendChild(this.getListItem(i));
        }

        if (this.stack.length) {
            for (var i = this.stack.length-1; i > 0; i--) {
                this.stack[i-1].list.appendChild(this.stack[i].list);
                this.stack.pop();

            }
        }

    },
    /**
     * Returns one TOC item - a LI element
     */
    getListItem: function (index)
    {
        var text = document.createTextNode(this.items[index].title);
        var a = document.createElement('a');
        a.appendChild(text);
        a.href = '#' + this.items[index].id;
        var li = document.createElement('li');
        li.appendChild(a);
        return li;
    },

    /**
     * Navigates through all the links, creates a list the external ones
     */
    appendReferences: function()
    {
        var protocols = this.protocols.toString().replace(/,/g,'|');
        var re = new RegExp('^' + protocols);

        var links = this.thedoc.getElementsByTagName('a');

        var listing = document.createElement(this.list_type_ref);
        for(var i=0, max = links.length; i < max; i++) {
            if (re.test(links[i].href) && links[i].host != location.host) {
                listing.appendChild(this.getLinkItem(links[i]));
            }
        }
        if (listing.hasChildNodes()) {
            this.ref_div.appendChild(listing);
        }
    },

    /**
     * Creates an A within a LI for use in the references list
     */
    getLinkItem: function (link)
    {
        var text = document.createTextNode(link.href);
        var a = document.createElement('a');
        a.href = link.href;
        a.appendChild(text);
        var li = document.createElement('li');
        li.appendChild(a);
        if (link.title) {
            text = document.createTextNode(' - ' + link.title);
            li.appendChild(text);
        }
        return li;
    }


}