// TODO: split this up into two parts: one to create the HTML, and the other to 
// manage the events. That way the full expanded HTML can be created using other
// methods (i.e. if a more obtrusive method of creating the HTML is desired).
// Also it would be useful when different formats at used, such as vertical
// sets of tabs.

(function($) {
    //
    // plugin definition
    //
    $.fn.tabber = function(options) {
        // build main options before element iteration
        var opts = $.extend({}, $.fn.tabber.defaults, options);

        // Expand wildcards - reference another property name in {curly brackets} in any
        // other string option. Only one pass, but we support multiple matches.
        // TODO: expansion not working in IE8. Damnit - IE8 handles splits differently.
        // TODO: support multi-pass. Continue while the numbers of substitutions go down.
        for (var optName in opts) {
            if (typeof opts[optName] == 'string' && opts[optName].match(/{[a-z]+}/i)) {
                // We cannot use a RE split, as IE is broken in this department.
                optNameSplit = opts[optName].replace('}','{').split('{');

                // Treat every second string as a property name.
                // e.g. '{classMain}tab{classNav}' splits to ['', 'classMain', 'tab', 'classNav']
                // with the property names being elements 1 and 3
                for(var i=1; i<optNameSplit.length; i+=2) {
                    if (typeof opts[optNameSplit[i]] == 'string') optNameSplit[i] = opts[optNameSplit[i]];
                }
                opts[optName] = optNameSplit.join('');
            }
        }

        // TODO: check whether 'selector' and 'context' can be used to avoid the following hack.

        // If the selector failed to match anything, then stop immediately.
        if (this.length == 0) return this;

        // If no selector was passed in, then use the default
        // Prevent endless loops if the default happens to be the document too.
        if (this[0].nodeName == '#document') {
            // If no default selector, or the selector did not match anything, then stop.
            if (typeof opts.classMain != 'string' || opts.classMain.length == 0 || opts.classMain == '#document') return this;

            // Start again with the default selector.
            return $('.' + opts.classMain).tabber(options);
        }

        // Loop for each set of tabs.
        return this.each(function() {
            // A complete tab set.
            var tabset = $(this);

            // Store the options against the wrapper div.
            $.data(tabset, 'opts', opts);

            var navanchor;
            var tabIndex = 0;
            var defaultIndex = 0;
            var aId;

            // Put an unordered list at the start of the tabs wrapper and then add a li for
            // each tab we require.
            tabset.prepend('<ul class="' + opts.classNav + '"></ul>').children(':first').each(function() {
                // The 'ul' navblock that we have just created.
                navblock = $(this);

                // Store the options against the navigation block for access by the events.
                $.data(this, 'opts', opts);

                tabset.children('.' + opts.classTab).each(function(){
                    // The div content of a tab.
                    tabcontent = $(this);

                    // Create a tab element.
                    navanchor = document.createElement('a');

                    if (this.title) {
                        // If there is a title attribute then use it.
                        tabtext = this.title;
                    } else {
                        // Fetch the text from the first listed tag in the content.
                        tabtext = '';

                        // Loop for each element in which we are going to look for the title text.
                        for (i=0; i < opts.titleElements.length; i++) {
                            tabcontent.find(opts.titleElements[i] + ':first').each(function() {
                                // Set a class on this element, in case we want to hide it.
                                if (opts.classTabTitle) $(this).addClass(opts.classTabTitle);

                                // Grab the inner HTML of this element for the tab.
                                tabtext = this.innerHTML;

                                // Strip HTML if required.
                                if (opts.titleElementsStripHTML) {
                                    tabtext = tabtext.replace(/<br[\s\/]*>/gi, ' ');
                                    tabtext = tabtext.replace(/<[^>]+>/g, '');
                                }

                                // End the loop early, since we have found a matching title tag.
                                i = opts.titleElements.length;
                            });
                        }
                    }

                    // Add a few attributes to the anchor.
                    navanchor.title = tabtext;
                    navanchor.href = '#';

                    // Add a unique ID if required.
                    // TODO: add the/a unique ID to the tab list item (the overmost element of each tab).
                    if (opts.addLinkId && opts.linkIdFormat) {
                        aId = opts.linkIdFormat;
                        aId = aId.replace(/<tabberid>/gi, tabset[0].id);
                        aId = aId.replace(/<tabid>/gi, this.id);
                        aId = aId.replace(/<tabnumberzero>/gi, tabIndex);
                        aId = aId.replace(/<tabnumberone>/gi, tabIndex+1);
                        aId = aId.replace(/<tabtitle>/gi, tabtext.replace(/[^a-zA-Z0-9\-]/gi, ''));

                        navanchor.id = aId;
                    }

                    // If this is the default tab, then record this fact.
                    if (tabcontent.hasClass(opts.classDefault)) defaultIndex = tabIndex;

                    // Add custom index property to make it easier to identify which tab was clicked.
                    $.data(navanchor, 'tabberIndex', tabIndex);
                    tabIndex += 1;

                    // Add a reference of the content element onto the anchor, so we don't need to go
                    // searching for it later.
                    $.data(navanchor, 'tabcontent', tabcontent);


                    // Take the title off the tab body so it does not show on mouse hover.
                    if (opts.removeTitle) this.title = '';

                    // Set the event triggers, all on the anchor.
                    for (i=0; i < opts.eventNames.length; i++) {
                        $(navanchor).bind(opts.eventNames[i], function(e) {
                            // It is important to use 'currentTarget' rather than 'target' otherwise
                            // the target is the tag immediately under the cursor - a child of
                            // the anchor - rather than the anchor itself.
                            // FIX: use 'this' instead of e.currentTarget for IE8 compatibility.
                            $.fn.tabber.tabShow(this);
                            return false;
                        });
                    }

                    // Add the individual li tab.
                    var li = navblock.append('<li>').children(':last').append(navanchor);
                    $(navanchor).text(tabtext);
                    // Wrap the text inside the anchor
                    if (opts.wrapText) $(navanchor).wrapInner(opts.wrapText);
                    // Wraps around the anchor
                    if (opts.wrapAnchor) li.wrapInner(opts.wrapAnchor);
                    // Before the anchor
                    if (opts.preAnchor) li.prepend(opts.preAnchor);
                    // After the anchor
                    if (opts.postAnchor) li.append(opts.postAnchor);
                });
            }).end();
            
            // Make the tab set 'live'.
            tabset.removeClass(opts.classMain).addClass(opts.classMainLive);

            // Activate the default tab.
            // TODO: we could use cookies to save the state when we are moving around pages.
            $.fn.tabber.tabShowIndex(tabset, defaultIndex);
        });
    };

    //
    // Show a tab content, given its tab anchor element.
    //
    $.fn.tabber.tabShow = function(tabAnchor) {
        // Get the options.
        // A copy is stored against the navigation block unordered list.
        var opts = $.data($(tabAnchor).closest('ul')[0], 'opts');

        // TODO: perform some checks, revert to index if necessary.
        $.data(tabAnchor, 'tabcontent').removeClass(opts.classTabHide).siblings('.' + opts.classTab).addClass(opts.classTabHide);

        // Set the active class, and remove it from the other tabs.
        // The tab is the li that wraps around the anchor (through as many wrapping tags as exist).
        $(tabAnchor).closest('li').addClass(opts.classNavActive)
            .siblings().removeClass(opts.classNavActive);
    };

    //
    // Show a tab content, given its zero-based index and the wrapper div element.
    //
    $.fn.tabber.tabShowIndex = function(tabber, tabIndex) {
        // Get the options.
        // A copy is stored against the tabber outermost wrapper.
        var opts = $.data(tabber, 'opts');

        // Show the selected tab content.
        tabber.children('.' + opts.classTab + ':eq('+tabIndex+')').each(function() {
            $(this).removeClass(opts.classTabHide).siblings('.' + opts.classTab).addClass(opts.classTabHide);
        });

        // Highlight the selected tab.
        tabber.children('.' + opts.classNav)
            .children(':eq(' + tabIndex + ')').addClass(opts.classNavActive)
            .siblings().removeClass(opts.classNavActive);
    };

    //
    // plugin defaults
    //
    $.fn.tabber.defaults = {
        // Source tags
        // Outermost DIV wrapper that forms a tab set
        classMain: 'tabber',
        // Outermost DIV wrapper after it has been manipulated
        classMainLive: '{classMain}live',
        // A single tab content (applied to a DIV wrapper)
        classTab: '{classMain}tab',
        // The default tab to be made active
        classDefault: '{classMain}tabdefault',

        // Elements to search for if a tab does not have a title attribute
        titleElements: ['h2','h3','h4','h5','h6'],

        // Generated source tags */
        // Applies to the navigation list (ul) for a tab set
        classNav: '{classMain}nav',
        // Non-selected tab content, normally hidden in a browser view
        classTabHide: '{classMain}tabhide',
        // The currently-selected tab navigation list item (li)
        classNavActive: '{classMain}active',
        // Class we give to an element used to derive the tab title.
        classTabTitle: '{classMain}title',

        // Behaviour options.
        // Add an ID to each navigation link
        addLinkId: false,
        // If addLinkId is true, then you can set a format for the ids.
        // Available replacement strings for linkIdFormat are:
        //    <tabberid> the id of the main tabber div
        //    <tabnumberzero> the number, zero-indexed
        //    <tabnumberone> tab number, one-indexed
        //    <tabtitle> the tab title, with non-alphanumeric characters removed
        //    <tabid> the id of the tab that the navigation anchor links to
        linkIdFormat: '<tabberid>nav<tabnumberone>',
        // Remove HTML from the title before using it as a tab text.
        titleElementsStripHTML: true,
        // Remove the title attribute from the tab content div.
        removeTitle: true,

        // Triggers.
        // The events that a tab will respond to.
        eventNames: ['mouseover', 'click'],

        // Additional tags for the tabs, useful when styling.
        // <li>{preAnchor/}{wrapAnchor}<a>{wrapText}TEXT LABEL{/wrapText}</a>{/wrapAnchor}{postAnchor/}</li>

        // Wrap the text inside the anchor, i.e. the visible label.
        wrapText: '',
        // Wraps around the anchor in the tab.
        wrapAnchor: '',
        // Before the anchor
        preAnchor: '',
        // After the anchor
        postAnchor: ''
    };

    //
    // end of closure
    //
})(jQuery);
