(function($) {
    $.extend({
        toc: function(options) {

            var defaults = {
                attachTo: 'body',
                attachType: 'prepend',
                backToTop: {
                	anchor: '',
                	css: '',
                	enabled: false,
                	text: 'Back to Top'
                },
                css: '',
                ignore: '',
                number: {
                    toc: true,
                    header: true
                },
                prefix: 'heading_',
                separator: '.',
                title: '',
                type: 'ul'
            };

            var config = $.extend(true, {}, defaults, options);

            var headers = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
            var index = 0;
            var indexes = {
                h1: (config.ignore.match('h1') == null && $('h1').length > 0) ? ++index : 0,
                h2: (config.ignore.match('h2') == null && $('h2').length > 0) ? ++index : 0,
                h3: (config.ignore.match('h3') == null && $('h3').length > 0) ? ++index : 0,
                h4: (config.ignore.match('h4') == null && $('h4').length > 0) ? ++index : 0,
                h5: (config.ignore.match('h5') == null && $('h5').length > 0) ? ++index : 0,
                h6: (config.ignore.match('h6') == null && $('h6').length > 0) ? ++index : 0
            };

            var table_of_contents_wrapper = $(document.createElement('div')).addClass(config.css);
            var table_of_contents = $(document.createElement(config.type));

            $(':header').not(config.ignore).each(function(i, object) {
                var $this = $(this);
                var tID = config.prefix + (i + 1);
                var tmpHtml;
                if ($this.is('h6')) {
                    execute_toc_update($this, tID, table_of_contents, headers, indexes, 'h6', headers.h6, indexes.h6, config.number, config.type, config.separator, config.backToTop);
                } else if ($this.is('h5')) {
                    execute_toc_update($this, tID, table_of_contents, headers, indexes, 'h5', headers.h5, indexes.h5, config.number, config.type, config.separator, config.backToTop);
                } else if ($this.is('h4')) {
                    execute_toc_update($this, tID, table_of_contents, headers, indexes, 'h4', headers.h4, indexes.h4, config.number, config.type, config.separator, config.backToTop);
                } else if ($this.is('h3')) {
                    execute_toc_update($this, tID, table_of_contents, headers, indexes, 'h3', headers.h3, indexes.h3, config.number, config.type, config.separator, config.backToTop);
                } else if ($this.is('h2')) {
                    execute_toc_update($this, tID, table_of_contents, headers, indexes, 'h2', headers.h2, indexes.h2, config.number, config.type, config.separator, config.backToTop);
                } else if ($this.is('h1')) {
                    execute_toc_update($this, tID, table_of_contents, headers, indexes, 'h1', headers.h1, indexes.h1, config.number, config.type, config.separator, config.backToTop);
                }
            });

            $(table_of_contents_wrapper).append(config.title);
            $(table_of_contents_wrapper).append(table_of_contents);
            
            switch(config.attachType)
            {
            	case 'after':
            		$(config.attachTo).after(table_of_contents_wrapper);
            	break;
            	
            	case 'before':
            		$(config.attachTo).before(table_of_contents_wrapper);
            	break;
            	
            	case 'append':
            		$(config.attachTo).append(table_of_contents_wrapper);
            	break;
            	
            	default:
            		$(config.attachTo).prepend(table_of_contents_wrapper);
            	break;
            }

        }
    });

    function execute_toc_update(obj, id, toc, headers, indexes, header_tag, header, index, displayNumber, htmlType, separator, backToTop) {
        if (header_tag != 'h1')
            update_container(header, toc, htmlType);
        update_header_index(headers, header_tag);
        var tocHtml = displayNumber.toc ? prepend_index_to_header(headers, header_tag, id, obj.text(), separator) : obj.text();
        var headHtml = displayNumber.header ? prepend_index_to_header(headers, header_tag, id, obj.text(), separator) : obj.text();	
        obj.html('<a id="' + id + '">' + headHtml + '</a>');
        if (backToTop.enabled) {
        	obj.prepend(
        		$('<a></a>')
        			.addClass(backToTop.css)
        			.attr({
        				'href' : backToTop.anchor,
        				'title' : backToTop.text
        			})	
        			.html('<span>(' + backToTop.text +')</span>')
        	);
        }
        append_to_table_of_contents(toc, index, id, tocHtml, headers, htmlType);
    }

    /*
    * Checks if the last node is an 'ul' element.
    * If not, a new one is created.
    */
    function update_container(header_count, toc, type) {
        if (header_count == 0 && !toc.find(':last').is(type)) {
            toc.find('li:last').append($(document.createElement(type)));
        }
    };

    /*
    * Updates headers index numbers.
    */
    function update_header_index(headers, header) {
        $.each(headers, function(i, val) {
            if (i == header) {
                ++headers[i];
            } else if (i > header) {
                headers[i] = 0;
            }
        });
    };

    /*
    * Prepends the index to a heading.
    */
    function prepend_index_to_header(headers, header, id, text, separator) {
        var numeration = '';

        $.each(headers, function(i, val) {
            if (i <= header && headers[i] > 0) {
                numeration += headers[i] + separator;
            }
        });

        return numeration + ' ' + text;
    };

    function append_to_table_of_contents(toc, index, id, text, headers, type) {
        var parent = toc;

        for (var i = 1; i < index; i++) {
            if (parent.find('> li:last > ' + type).length === 0) {
                parent = parent.append('<li><' + type + '></' + type + '></li>');
            }
            parent = parent.find('> li:last > ' + type);
        }

        parent.append('<li><a href="#' + id + '" title="' + text + '">' + text + '</a></li>');
    }

})(jQuery);
