var ABS = ABS || {};
jQuery(function () {
    ABS.biblesearch.initialize();
});

/* extending JS */
Array.prototype.unique = function () {
    var seen = [], ret = [], val;
    for (i = 0; i < this.length; i++) {
        val = this[i];
        if (!seen[val]) {
            ret.push(val);
            seen[val] = true;
        }
    }
    return ret;
};
String.prototype.trim = function () {
    var tmp = this;
    tmp = tmp.replace(/^\s*/, '');
    tmp = tmp.replace(/\s*$/, '');
    return tmp;
};

/* custom jQuery plugins */

// add a 'hint' to an input
jQuery.fn.hint = function (hint) {
    var $ = jQuery;
    
    // could be added to String.prototype, but this keeps the plugin 
    // side-effect free
    function ucfirst(str) {
        return (str.slice(0, 1).toUpperCase() + str.slice(1));
    }
    
    // add a hint to the item
    function hintify() { 
        this.value || $(this).addClass('hint').val($(this).data('hint'));
    }
    
    // remove or select a hint'D input
    function dehintify() {
        var $this = $(this).removeClass('hint');
        if (this.value == $this.data('hint')) {
            $this.val('');
        }
        else {
            $this.select();
        }
    }
    
    // process the hint for each element
    return $(this).each(function () {
        $(this).data('hint', (hint || ucfirst($(this).attr('name'))));
    }).each(hintify).focus(dehintify).click(dehintify).blur(hintify);
};

/* general container for bible search behavior */
ABS.biblesearch = (function ($) {
    var that = {};
    
    /* public functions */
    that.initialize = function () {
        $('#quicksearch').hint('Quick Search');
        $('#searchbox #searchQ')
                .hint('Keyword, Phrase, or Verse');
        $('#newsearch input.query').hint('New Search');
        homepage();
        ABS.share.initialize();
        ABS.utils.initialize();
        ABS.relevancy.initialize();
        ABS.links.initialize();
        ABS.sidebar.initialize();
        ABS.versions.initialize();
        ABS.search.initialize();
        ABS.register.initialize();
        ABS.login.initialize();
        ABS.widget.initialize();
    };
    
    // deal with version selecting
    that.versionSelector = function () {
        // when checking any specific version, auto-check the "specific 
        // versions" radio
        $('#allVersions').click(function (event) {
            var $tgt = $(event.target);
            if ($tgt.is('input[type="checkbox"]') && $tgt.get(0).checked) {
                $('#whichVersions_these').attr('checked', true);
                
                // set the 'selected version' string
                var $selected = $('input[name="itemVersions[]"]:checked'),
                    vals = [], str;
                $selected.each(function () { vals.push(this.value); });
                str = vals.join(', ');
                setVersionString(str);
            }
        });
        
        // when checking 'all', un-check any specific versions to avoid 
        // confusion
        $('#whichVersions_all, #whichVersions_my').click(function () {
            if (this.checked) {
                $('input[name="itemVersions[]"]')
                    .attr('checked', false);
            }
            setVersionString('');
        });
    };
    
    // set the homepage up with special stuff
    function homepage () {
        var $tips = $('#search-tips'), 
            $versions = $('#help-versions'),
            $modal,
            helper;
        
        $('.boxContent div.story:last-child').addClass('last');
        that.versionSelector();
        
        // setup the homepage image rotation
        if ($('#photorotate .images').length) {
            $('#photorotate .images').cycle({
                fx: 'fade',
                timeout: 10000,  // transition every 10 seconds
                speed: 1000      // spend 1 second doing the transition
            });
        }
        
        // setup the homepage video lightbox link
        $(".gallery a[rel^='prettyPhoto']").each(function () {
            $(this).prettyPhoto({
                theme:'dark_rounded',
                default_width: 625,
                default_height: 430
            });
        });
        
        // setup homepage version selection expansion
        versionExpander();
        
        // setup the 'search tips' modal
        if ($tips.length || $versions.length) {
            $modal = ABS.utils.modalMaker('help');
            helper = function () {
                ABS.utils.createOverlay();
                $modal.fadeIn(300)
                    .find('div.modal-content')
                    .empty()
                    .load($(this).attr('href') + ' .copy', function () {
                        ABS.links.initialize();
                        $modal.find('a.register').click(function () {
                            ABS.utils.modalClose();
                            ABS.register.form();
                            return false;
                        });
                    });
                return false;
            };
            $tips.click(helper);
            $versions.click(helper);
        }
    }
    
    function versionExpander() {
        var $div             = $('#versions'),
            $links           = $div.find('#links'),
            $allVersions     = $div.find('#allVersions'),
            $initialVersions = $allVersions.find('#whichVersionList'),
            $moreVersions    = $allVersions.find('#whichVersionListMore'),
            numInitial       = $initialVersions.find('li').length,
            numMore          = $moreVersions.find('li').length,
            total            = numInitial + numMore,
            height           = $div.css('height'),
            threshold        = 7,
            $moreLink, $lesslink, toMove;
        if (!$allVersions.length) {
            return false;
        }
        
        // we can show 7 versions when logged in, and one less when not
        if ($('#whichVersions_my').length) {
            threshold -= 1;
        }
        
        // if our total # of versions is greater than the threshold, then our 
        // threshold is reduced by 1, since we'll need a 'more' link
        if ((numMore > 0) || (numInitial > threshold)) {
            threshold -= 1;
        }
        
        // if we have more versions in our initial list than our threshold 
        // allows, move enough to the 'more' list to keep things pretty
        if (numInitial > threshold) {
            
            // find li items greater than our threshold, remove them from the 
            // list, get the raw array, and reverse that array
            toMove = $initialVersions.find('li:gt(' + (threshold - 1) + ')')
                .remove().get().reverse();
            
            // prepend each item to the 'more' list; the array comes reversed 
            // from its original order, so prepending puts the original order 
            // back
            $.each(toMove, function () { $moreVersions.prepend(this); });
        }
        
        // if we don't have any more versions, nothing more to do
        if (!$moreVersions.find('li').length) {
            return false;
        }
        
        // hide the 'more' versions
        $moreVersions.hide();
        
        // add the 'more' link
        $moreLink = $('<a href="#" id="versionsMore">Show all ' + total + ' translations</a>').click(
            function () {
                $div.css('height', 'auto');
                $moreVersions.slideDown('fast', function () {
                    $lessLink.show();
                    $moreLink.hide();
                });
                return false;
            }
        ).appendTo($links);
        
        // add the 'less' link
        $lessLink = $('<a href="#" id="versionsLess">Hide additional  translations</a>').click(
            function () {
                $div.css('height', height);
                $moreVersions.slideUp('fast', function () {
                    $lessLink.hide();
                    $moreLink.show();
                });
                return false;
            }
        ).hide().appendTo($links);
        
        return true;
    }
    
    function setVersionString(string) {
        var $label  = $('label[for="whichVersions_these"]');
            $strong = $label.find('strong');
        if (!$strong.length) {
            $strong = $('<strong/>');
            $label.append($strong);
        }
        string = string ? ': ' + string : '';
        $strong.html(string);
    }
    
    return that;
}(jQuery));

/* functionality for the 'share' links */
ABS.share = (function () {
    var $        = jQuery,
        that     = {},
        $wrapper = $('<div id="share-header" class="share-modal" />'),
        $modal   = $('<div id="share-inline" class="share-modal" />');
    
    /* private functions */
    
    // open the top share modal (called as a click handler)
    function slider() {
        $wrapper.css({height: '100px'}).slideDown('fast')
            .load(this.href + '?js=1 div.fullpage', 
            function () {
                $wrapper.css({height: 'auto'});
                handleLinks.call($wrapper, closeSlider);
                handleLinks();
            }
        );
        return false;
    }
    
    // open the share window as a modal (called as a click handler)
    function modal() {
        ABS.utils.createOverlay();
        
        // close any other modals
        $('.modal:visible').fadeOut(300);
        
        $modal.html('').load(
            $(this).attr('href') + '&js=1 div.fullpage', null,
                function () {
                    $modal.fadeIn(300);
                    handleLinks.call($modal, function () {
                        ABS.utils.modalClose($modal);
                    });
                }
            );
        return false;
    }
    
    // close the top share modal
    function closeSlider() {
        $wrapper.slideUp('fast', function () { $wrapper.empty(); });
        return false;
    }
    
    // handle links in the share modal
    function handleLinks(closer) {
        var $this   = $(this),
            handler = function () { handleLinks.call($this, closer); };
        
        // if we detect a 'thank you' page, slide the div up after a 
        // few seconds.
        if ($this.find('div.cms').length) {
            setTimeout(closer, 4000);
        }
        
        // attach 'external' behavior
        ABS.links.initialize();
        
        // the 'close' link
        $this.find('#boxtop a').click(closer);
        
        // the tabs for switching share type
        $this.find('#buttons a').click(function () {
            var sep  = (this.href.match(/\?/)) ? '&' : '?',
                href = this.href + sep + 'js=1 div.fullpage';
            $this.load(href, handler);
            return false;
        });
        
        // capture form submit
        $('#taf').submit(function() {
            var data = $(this).formToArray();
            data.push({ name: 'js', value: 1});
            $(this).find('input[type="submit"]')
                .attr('disabled', true).val('Sending...');
            $this.load(this.action + ' div.fullpage', data, handler);
            return false;
        });
        
    };
    
    /* public functions */
    
    // initialize everything
    that.initialize = function () {
        // make the 'share' wrapper
        $wrapper.hide().appendTo($('#topbar_right'));
        
        // click behavior for the head 'share' link
        $('#head-taf').click(slider);
    };
    
    // handle inline share links
    that.inline = function () {
        // make the 'share' modal
        if ($('.shareLink a').length) {
            
            // add the modal to the body
            $modal.appendTo($('body'));
            
            // click behavior for inline 'share' links
            $('.shareLink a').unbind().click(modal);
        }
    };
    
    return that;
}());

/* functionality specific to relevancy links */
ABS.relevancy = (function ($) {
    var that = {},
        $spinner;
    
    /* public functions */
    that.initialize = function () {
        $spinner = $('<img class="spinner" src="/templates/abs/biblesearch/images/site/relevancy-spinner.gif"/>').hide();
        $('body').append($spinner);
        $('div.helpful').click(clickHandler);
    };
    
    /* private functions */
    function clickHandler(event) {
        var $tgt = $(event.target);
        if ($tgt.is('a')) {
            $tgt.html($spinner.show());
            var $div = $(this);
            $.get($tgt.attr('href') + '&inplace=1', {}, function (html) {
                var $repl = $(html);
                $div.empty().append($repl);
                $repl.fadeIn('fast');
            }, 'html');
            return false;
        }
        return true;
    }
    
    return that;
}(jQuery));

/* functionality specific to tags */
ABS.tags = (function ($) {
    var that          = {},
        module        = '.module',
        selectTitle   = 'Click to select this verse to add tags or notes.',
        deselectTitle = 'Click to deselect this verse.',
        regexp        = /^(v(\d+)_(\d+)_)(\d+)$/,
        autoOpened    = false,
        $tagtools, $passage, $inner;
    
    // onload behavior
    $(function () {
        var timeout, width;
        
        $inner   = $('div.passagetagnote div.inner');
        $passage = $('div.passagedisplay');
        
        // we don't want to proceed if we don't have a passage display page
        if (!$passage.length) {
            return;
        }
        
        // get our tag/note tools ready for display
        $tagtools = $inner.css({
            opacity: 0,
            right: $.getScrollbarWidth() + 'px'
        }).show().animate({opacity: 1}, 300, repad).find('div.links');
        
        // attach tag/note form show/hide behavior
        $tagtools.find('a').click(function () {
            var $div = $($(this).attr('hash'));
            clearTimeout(timeout);
            if (!$div.find('div.controls').is(':visible')) {
                showInterface($div);
            }
            else if ($div.is(':visible')) {
                hideInterface($div);
            }
            else {
                showInterface($div);
            }
            return false;
        });
        
        $tagtools.find('span.help a').unbind().click(function () {
            if (!$('div.form:visible').length) {
                clearTimeout(timeout);
                showInterface($($(this).attr('hash')));
            }
            return false;
        });
        
        // add the select / clear functionality
        $('a#clearAll').unbind().click(function () {
            $('.selected').removeClass('selected');
            updateDisplay();
            return false;
        });
        $('a#selectAll').unbind().click(function () {
            $('.selected').removeClass('selected');
            updateDisplay();
            return false;
        });
        
        // attach the tag/note form cancel link behavior
        $tagtools.siblings('div.form').find('a').click(function () {
            $(this).closest('div.form').each(function () { 
                hideInterface($(this));
            });
            return false;
        });
        
        // enable span tagging
        spanTagging();
        
        // add ajax to our tag add form
        $('.passagetagnote form').submit(function () {
            var $form     = $(this),
                data      = $form.formToArray(),
                $div      = $('#' + $form.attr('id').replace('Form', '')),
                $sub      = $form.find(':submit'),
                $success  = $form.find('p.success'),
                $error    = $form.find('p.blank').hide(),
                $controls = $form.find('div.controls'),
                $save     = $form.find('p.save'),
                complete  = true,
                subVal    = $sub.val();
            
            // if we aren't showing the controls, don't do anything
            if (!$save.is(':visible')) {
                return false;
            }
            
            // if we don't have all fields filled out, show an error
            $form.find('input[type="text"], textarea').each(function () {
                complete = complete && $(this).val();
            });
            if (!complete) {
                $error.show();
                return false;
            }
            
            // find our visible tag div for redisplay
            data.push({
                name: 'tagView',
                value: ($('.tagspan:visible').attr('id') || '')
                        .replace('#', '')
            });
            
            // submit form and only load in results from this div
            $sub.val('adding...').attr('disabled', true);
            $div.load(
                $form.attr('action') + ' #' + $div.attr('id'), data, 
                function () {
                    reinitialize();
                    $form.resetForm();
                    $controls.fadeOut('fast', function () {
                        $success.fadeIn('fast');
                    });
                    $sub.val(subVal).attr('disabled', false);
                    timeout = setTimeout(function () {
                        hideInterface($form.closest('.form'));
                    }, 6000);
                }
            );
            
            // Try an omniture post to track the tag add
            s.events="event2";
            s.t();
            
            return false;
        });
        
        reinitialize();
    });
    
    // scroll to the selected slements
    that.scrollToSelected = function () {
        var divOffset = $passage.offset().top,
            vOffset   = $passage.find('.selected:first').offset().top,
            vScroll   = vOffset - divOffset - 
                        parseFloat($inner.outerHeight()) - 8;
        $passage.animate({scrollTop: '+=' + vScroll + 'px'}, 500);
    };
    
    // select a span of verses as defined by the hash.  Public to allow 
    // other modules to use it
    that.selectSpan = function (e) {
        var hash = $(this).attr('hash').replace('#', ''),
            range,
            className,
            start,
            end;
        if (hash === 'none') {
            $('.selected').removeClass('selected');
        }
        else {
            if (!e.shiftKey) {
                $('.selected').removeClass('selected');
            }
            if (hash === 'chapter') {
                className = [];
                $('.selectable').each(function () {
                    className.push($(this).data('verse'));
                });
                $.each(className.unique(), function (k, v) {
                    toggleVerse(v, true);
                });
            }
            else {
                range     = hash.split('-');
                className = $passage.find('.selectable:first').data('verse');
                start     = parseInt(range[0], 10);
                end       = range[1] ? parseInt(range[1], 10) : start;
                for (i = start; i <= end; i++) {
                    toggleVerse(className.replace(regexp, "$1" + i), true);
                }
            }
            if (!$('div.form:visible').length) {
                if ($(this).closest('.module').is('.sidebarNotes')) {
                    showInterface($('#noteForm'));
                }
                else {
                    showInterface($('#tagForm'));
                }
            }
            ABS.sidebar.hide('#searchsummary');
        }
        updateDisplay();
        return false;
    };
    
    that.deleteLinks = function () {
        $('a.delete-tag, a.delete-note').unbind().click(deleteItem);
        $('.deleter').unbind().hover(
            function () { $(this).addClass('hover'); },
            function () { $(this).removeClass('hover'); }
        );
    };
    
    /* private functions */
    
    // reattach any behavior or elements that need to be updated in response 
    // to a new tag or note div being loaded
    function reinitialize() {
        that.deleteLinks();
        $('.report-tag').unbind().click(reportTag);
        $('.select-span').unbind().click(that.selectSpan);
        $('a.switcher').click(function () {
            var $link = $(this);
            $link.closest('div').hide();
            $($link.attr('hash')).show();
            return false;
        });
        
        // a new tag switcher
        $('.tagSwitcher a').unbind().click(function () {
            var $link = $(this),
                $tgt  = $($link.attr('hash'));
            $('.parent').hide();
            $('.container').hide();
            $tgt.show().closest('.container').show();
            return false;
        });
        
        // add in the sidebar help link functionality
        $('.sidebarbody .help a').unbind().click(function () {
            $($(this).attr('hash')).slideToggle('fast');
            return false;
        });
        $('.closeMe').unbind().click(function () {
            $(this).parent().slideToggle('fast');
            return false;
        });
        
        noteEditors();
        ABS.sidebar.modals();
        updateDisplay();
    }
    
    // show the given div (tag/note interface)
    function showInterface($div) {
        $tagtools.siblings('div.form').filter(function () {
            return ($(this).attr('id') !== $div.attr('id'));
        }).fadeOut(300, function () {
            $div.find('.controls').show().end()
                .find('.success').hide().end()
                .find('.blank').hide().end()
                .fadeIn(300, function () {
                    repad();
                    that.scrollToSelected();
                })
                .find('.text:first').focus();
        });
    }
    
    // hide the given div (tag/note interface)
    function hideInterface($div) {
        if (!$div) {
            $div = $('div.form:visible');
        }
        $div.fadeOut(300, repad);
    }
    
    // re-pad our passage display based on the height of the tag div
    function repad() {
        $passage.css('padding-top', $inner.outerHeight() + 'px');
    }
    
    // setup per-verse span tagging
    function spanTagging() {
        var $start = null;
        $passage
            
            // parse our verses out
            .each(parseVerses)
            
            // narrow down to selectable verses and add behavior
            .find('.selectable')
            
            // add classes to tell a whole verse that it's being hovered
            .hover(
                function () {
                    if (!$(this).is('.selected')) {
                        $('.' + $(this).data('verse')).addClass('over');
                    }
                }, 
                function () {
                    $('.' + $(this).data('verse')).removeClass('over');
                }
            )
            
            // add the titles to everything
            .attr('title', selectTitle)
            
            // cancel the mousedown (which triggers text selection) only if 
            // the shift key is pressed; prevents a weird situation where 
            // selecting multiple verses using the shift key also select a 
            // portion of the text
            .mousedown(function (e) {
                if (window.event) {
                    if (window.event.shiftKey) {
                        this.onselectstart = function () { return false; };
                    }
                    else {
                        this.onselectstart = function () { return true; };
                    }
                }
                return (!e.shiftKey);
            })
            
            // when clicking, (de)select the verse, and optionally the verse 
            // span
            .click(function (e) {
                var $tgt   = $(e.target),
                    verses = [],
                    select = !$tgt.is('.selected');
                verses.push($tgt.data('verse'));
                
                // show the first interface if we haven't already auto-opened 
                // it, if we are logged in, and if we are actually selecting a 
                // verse
                if (!autoOpened && select && !$('p.loggedout').length &&
                    !$inner.find('div.form:visible').length)
                {
                    autoOpened = true;
                    showInterface($inner.find('div.form:first'));
                }
                
                // hide the results if we're selecting a verse
                if (select) {
                    ABS.sidebar.hide('#searchsummary');
                }
                
                // if we're holding down shift, select everything from where 
                // we started to here
                if (e.shiftKey) {
                    $.each(selectSpan($start, $tgt), function (k, v) {
                        verses.push(v);
                    });
                }
                
                // track the current thing as the last thing we touched
                $start = $tgt;
                
                // if we have anything, select it
                if (verses.length) {
                    $.each(verses.unique(), function (i, v) {
                        toggleVerse(v, select);
                    });
                    updateDisplay();
                }
                return false;
            });
            
        // select anything we need to initially
        if (typeof selectVerses === 'object' && selectVerses.length > 0) {
            $.each(selectVerses, function(i, n)  { toggleVerse(n, true); });
            updateDisplay();
            that.scrollToSelected();
        }
        
    }
    
    // parse out this element and all its children, adding appropriate verse 
    // data to all elements.  Use closure to keep track of the 'verse' 
    // variable with each subsequent call to 'traverse'; as each call happens, 
    // the element is queried for its verse; if one is found we update our 
    // closure-available variable so that next elements are tagged with the 
    // verse whether or not they have it in their classnames
    function parseVerses() {
        var verse;
        $(this).children().each(function () {
            traverse.call(this, true);
        });
        
        // traverse is to be called via .each(), and assumes the element it's 
        // traversing is avaialable in 'this'
        function traverse(outer) {
            var $el = $(this);
            verse = getVerse($el) || verse;
            if ($el.is('verse-block') || $el.is('.title-passage') || 
                $el.is('br'))
            {
                return;
            }
            
            // if we've made it down to a text node, wrap it in a span (it was 
            // a direct child of a block-level element) so we have something 
            // to target
            if ($el.get(0).nodeType == 3) {
                $el.wrap('<span/>');
                $el = $el.closest('span');
            }
            
            // don't apply selectable to any block-level elements or list 
            // elements; instead, traverse the children (including any text 
            // nodes) and handle them
            if ($el.css('display') == 'block' || $el.is('li')) {
                $el.addClass('verse-block').removeClass('selectable')
                    .contents().each(traverse);
            }
            
            // we have an inline element that is not a text node.  Selectify 
            // it
            else {
                if (!$el.data('verse')) {
                    if (outer === true) {
                        $el.addClass('outer-span');
                    }
                    $el.addClass('selectable').addClass(verse)
                        .data('verse', verse || null)
                        .children().each(traverse);
                }
            }
            
        }
    }
    
    // get a verse out of this element's classes
    function getVerse($el) {
        var className = $el.attr('className');
        if (!className) {
            return false;
        }
        var m = $.grep(className.split(' '), function (c) {
            return c.match(regexp);
        });
        return m.length ? m[0] : false;
    }
    
    // add all the verses defined by the given span (if any) to our 'toSelect' 
    // array
    function selectSpan($start, $end) {
        var ret = [],
            v1, v2, tmp;
        if (!($start && $end)) {
            return ret;
        }
        v1 = $start.data('verse').match(regexp);
        v2 = $end.data('verse').match(regexp);
        start = parseInt(v1[4], 10);
        end   = parseInt(v2[4], 10);
        if (start > end) {
            tmp   = start;
            start = end;
            end   = tmp;
        }
        for (i = start; i <= end; i++) {
            ret.push('v' + v1[2] + '_' + v1[3] + '_' + i);
        }
        return ret;
    } 
    
    // toggle the verse represented by this element
    function toggleVerse(v, dir) {
        if (!v) {
            return;
        }
        var className = (typeof (v) === 'string') ? v : v.data('verse'),
            $verses   = $('.' + className);
        if (typeof(dir) !== 'undefined') {
            $verses.toggleClass('selected', dir);
        }
        else {
            $verses.toggleClass('selected');
        }
        $verses.removeClass('over').attr('title', function () {
            return $(this).is('.selected') ? deselectTitle : selectTitle;
        });
        
        // required to work around an apparent IE bug that prevents subsequent 
        // DOM operations from completing correctly.  The class name is 
        // irrelevant; the .removeClass() call triggers something that works 
        // around the bug.
        $('body').removeClass('IEFIX');
    }
    
    // update our tag/note display based on selected elements
    function updateDisplay() {
        var $sel      = $passage.find('.selected'),
            classes   = [],
            verses    = [],
            spans     = [],
            $input    = $('.passagetagnote input[name="verse"]'),
            $noSelect = $('.passagetagnote p.no-selection'),
            $controls = $('.passagetagnote p.save'),
            start, end, next;
        
        // we've got some selected verses; filter them and update the display
        if ($sel.length) {
            $sel.each(function () { classes.push($(this).data('verse')); });
            $.each(classes.unique(), function (k, v) {
                verses.push(parseInt(v.match(regexp)[4], 10));
            });
            verses = verses.sort(function (a, b) { return a - b; });
            start  = verses[0];
            for (i = 0; i < verses.length; i++) {
                if (next && next != verses[i]) {
                    spans.push(format(start, end));
                    start = verses[i];
                }
                end  = verses[i];
                next = end + 1;
            }
            spans.push(format(start, end));
            
            // update the places that need the new verse span
            $tagtools
                    .find('span.selection')
                        .find('span').html(spans.join(', '))
                    .end().show()
                .end()
                    .find('span.chapter').hide().end()
                    .find('#selectClear span').show();
            
            // show the note/tag sidebar
            ABS.sidebar.show('.sidebarTags, .sidebarNotes');
            
            // if we have only some of the verses selected, track that span
            if ($('.selectable').length !== $('.selected').length) {
                $input.val(spans.join(', '));
            }
            
            // otherwise, empty the input so that we tag/note the entire 
            // chapter
            else {
                $input.val('');
            }
            
            $noSelect.hide();
            $controls.show();
        }
        else {
            $tagtools
                .find('span.selection').hide().end()
                .find('span.chapter').show().end()
                .find('#selectClear span').hide();
            $input.val('');
            $controls.hide();
            $noSelect.show();
        }
        
        function format(start, end) {
            return (start == end) ? start.toString() : start + '-' + end;
        }
    }
    
    function reportTag(event) {
        var $modal = ABS.utils.modalMaker('modal-report-tag');
        ABS.utils.createOverlay();
        $modal
            .find('div.modal-content')
            .html('')
            .load(
                $(this).attr('href') + ' div.cms',
                null,
                function (t) {
                    $modal.fadeIn(300).find('form').each(cleanup);
                }
            );
        return false;
    }
    
    // the note modals
    function noteEditors() {
        var modalID = 'show-note', changed = false;
        
        $('div.sidebarbody a.view-note-link').unbind().click(function () {
            var moduleID = 
                '#' + $(this).closest('div.sidebarNotes').attr('id');
            
            // make the modal and show it
            ABS.utils.createOverlay();
            changed = false;
            ABS.utils.modalMaker(modalID, function () {
                if (changed) {
                    return confirm('You have unsaved changes.  Are you sure you want to close?');
                }
                return true;
            })
            .fadeIn(300)
            
            // find the modal content, empty it, and add the note form to it
            .find('div.modal-content')
                .empty()
                .html(
                    $(this).closest('span').siblings('.view-note-copy').html()
                )
                
                // monitor editable elements for keypresses to track changes
                .find('.editable')
                    .keypress(function () { changed = true; })
                .end()
                
                // add AJAX functionality to the form itself
                .find('form')
                    .ajaxForm({
                        beforeSubmit : function (data, $f) {
                            var $req = $f.find('.required'), complete = 0;
                            $.each($req, function () {
                                if ($(this).val()) {
                                    complete += 1;
                                }
                            });
                            $f.find('.error').hide();
                            if ($req.length != complete) {
                                $f.find('.error').show();
                                return false;
                            }
                            $f.find('input[type="submit"]').attr({
                                disabled : true,
                                value    : 'Saving...'
                            });
                            return true;
                        },
                        success : function (txt) {
                            // update the current sidebar with the sidebar 
                            // from the response
                            $(moduleID).html(
                                ABS.utils.extractResponse(txt, moduleID)
                            );
                            
                            // reinit to re-add all the functionality our 
                            // sidebars need
                            reinitialize();
                            
                            // close the modal
                            ABS.utils.modalClose(modalID);
                            return false;
                        }
                    });
                    
            // end of click handler
            return false;
        });
    }
    
    function cleanup() {
        var $form   = $(this),
            $button = $form.find('#button');
        
        // attach any 'rel="external" behavior
        ABS.links.initialize();
        
        // remove any onclicks from the button and make it submit the form
        $button.unbind().removeAttr('onclick').click(function () {
            $form.submit();
        });
        
        $form.ajaxForm({
            beforeSubmit : function () {
                $button.addClass('disabled').attr({
                    disabled : true,
                    value    : 'Submitting...'
                });
            },
            success : function (txt) {
                $form.closest('div.modal-content')
                    .html(ABS.utils.extractResponse(txt, 'div.cms'))
                    .find('form').each(cleanup);
            }
        });
    }
    
    // click handler for deleting a tag or note
    function deleteItem() {
        var $link = $(this),
            $div  = $link.closest(module),
            href  = $link.attr('href');
            type  = ($link.is('.delete-tag')) ? 'tag' : 'note';
        
        if (confirm('Are you sure you want to remove this ' + type + '?')) {
            
            // immediately hide it as ajax does its thing
            $link.siblings('a').html('...');
            
            // figure out which view to load on redisplay
            if (type === 'tag') {
                href += '&tagView=' + 
                        ($('.tagspan:visible').attr('id') || '')
                        .replace('#', '');
            }
            href += '&go=1';
            
            // if we have a div, load that up
            if ($div.length) {
                $div.load(href + ' #' + $div.attr('id'), null, reinitialize);
            }
            
            // otherwise, hit the page itself
            else {
                document.location.href = href;
            }
        }
        return false;
    }
    
    return that;
}(jQuery));

/* functionality for links */
ABS.links = (function ($) {
    var that = {};
    
    /* public functions */
    that.initialize = function () {
        $("a[rel='external']")
            .unbind().click(function () {
                window.open($(this).attr('href'));
                return false;
            }).addClass('externalLink');
        ABS.share.inline();
    };
    
    return that;
}(jQuery));

/* functionality for sidebar */
ABS.sidebar = (function ($) {
    var that = {},
        $sidebar,
        $body,
        $link,
        $modules,
        $modal;
    
    /* public functions */
    that.initialize = function () {
        $sidebar = $('.leftcolumn');
        $body    = $('.rightcolumn');
        $link    = $('.sidebartoggle');
        $modal   = ABS.utils.modalMaker('sidebar-learn-more');
        
        // toggle the sidebar on/off
        $link.click(function () {
            var hide = $sidebar.is(':visible');
            if (hide) {
                $sidebar.fadeOut('fast', function () {
                    $body.addClass('nosidebar');
                    $link.html($link.html().replace('Hide', 'Show'));
                    ABS.tags.scrollToSelected();
                });
            }
            else {
                $body.removeClass('nosidebar');
                $sidebar.fadeIn('fast');
                $link.html($link.html().replace('Show', 'Hide'));
                ABS.tags.scrollToSelected();
            }
            ABS.utils.setCookie('sidebar', (hide ? '0' : '1'));
            return false;
        });
        that.modules();
        
        // sidebar modals
        that.modals();
    };
    
    // setup modal functionality on the sidebar links
    that.modals = function () {
        
        // modal functionality for the help links
        $('div.sidebarhead a.whitelinks').unbind().click(function () {
            var hash = $(this).attr('hash'),
                href = $(this).attr('href').replace(hash, '') + ' ' + hash;
            ABS.utils.createOverlay();
            $modal.fadeIn(300).find('div.modal-content').empty().load(
                href, null, ABS.links.initialize
            );
            return false;
        });
        
    };
    
    // show a specific sidebar module
    that.show = function (which) {
        $(which).find('.sidebarhead').trigger('click', {show: true});
    };
    
    // hide a specific sidebar module
    that.hide = function (which) {
        $(which).find('.sidebarhead').trigger('click', {hide: true});
    };
    
    /* do the sidebar module click show/hide stuff */
    that.modules = function () {
        var $tohide = $sidebar.find('.module').not(
            '.sidebarTags, #searchsummary'
        );
        
        // hide the sidebar bodies right off the bat, while we do the other 
        // stuff
        $tohide.find('.sidebarbody').hide();
        
        // load the 'off' images, and hide them
        $sidebar.find('h3 img').each(function () {
            var $img = $(this),
                oldsrc = $img.attr('src'),
                newsrc = oldsrc.replace('_on.', '_off.');
            
            if (oldsrc.match(/_on\.gif$/)) {
                $('<img src="' + newsrc + '" class="off" alt="' + 
                  $img.attr('alt') + '" />').hide().insertAfter($img);
                $img.addClass('on');
            }
        });
        
        // attach click behavior to the headings
        $sidebar.find('.sidebarhead').click(function (ev, options) {
            var $head, 
                $body,
                show,
                args = options || {};
            
            // let <a> clicks bubble up
            if ($(ev.target).is('a')) {
                return true;
            }
            
            // find the head/body to swap the images and do the 
            // expand/collapse
            $head = $(this).closest('div.sidebarhead');
            $body = $head.next('div.sidebarbody');
            
            // check for specific show/hide
            if (typeof(args.show) !== 'undefined' && args.show) {
                show = true;
            }
            else if (typeof(args.hide) !== 'undefined' && args.hide) {
                show = false;
            }
            else {
                show = !$body.is(':visible');
            }
            if (show) {
                $head.find('h3 img.off').hide();
                $head.find('h3 img.on').show();
                $body.slideDown('fast');
            }
            else {
                $head.find('h3 img.off').show();
                $head.find('h3 img.on').hide();
                $body.slideUp('fast');
            }
            
            return false;
        });
        
        // expand/collapse the proper modules
        $tohide.each(function () { that.hide(this); });
        
    };
    
    return that;
}(jQuery));

/* functionality for the registration link */
ABS.register = (function ($) {
    var that = {},
        $form, $why, $modal;
    
    that.initialize = function () {
        $modal = ABS.utils.modalMaker('register');
        $form = $('a.createAccount').click(that.form);
        $why  = $('a#why-register').click(that.why);
    };
    
    // show the registration form
    that.form = function () {
        showModal(
            ($form.attr('href') + ' div.sw'), 
            function () {
                $modal.fadeIn(300).find('form').each(cleanup);
            }
        );
        return false;
    };
    
    // show the 'why register' modal
    that.why = function () {
        showModal(
            ($why.attr('href') + ' .copy'),
            function () {
                $modal.find('a[href="/login/createAccount.php"]')
                    .click(that.form);
            }
        );
        return false;
    };
    
    // called to show the modal, optionally fading out first for a transition 
    // effect
    function showModal(href, callback) {
        var show = function () {
            $modal.find('div.modal-content').empty().load(
                href, null, function () {
                    $modal.fadeIn(300);
                    ABS.links.initialize();
                    callback.call();
                }
            );
        };
        ABS.utils.createOverlay();
        if ($modal.is(':visible')) {
            $modal.fadeOut(300, show);
        }
        else {
            show();
        }
        return false;
    }
    
    // unbind the 'onclick' method and override to actually submit the form
    function cleanup () {
        var $form   = $(this),
            $button = $form.find('#button');
        
        // attach any 'rel="external" behavior
        ABS.links.initialize();
        
        // attach the 'why register' link
        $('a.whyRegister').click(that.why);
        
        // remove any onclicks from the button and make it submit the form
        $button.unbind().removeAttr('onclick').click(function () {
            $form.submit();
        });
        
        // mercury needs these inputs, for whatever reason
        $form.find('input[name="ACT_TYPE"]').attr({
            value : 'Register', name : 'Add'
        });
        
        // update the form action and add a submit handler
        $form.attr('action', '/login/' + $form.attr('action')).ajaxForm({
            beforeSubmit : function () {
                $button.addClass('disabled').attr({
                    disabled : true,
                    value    : 'Registering...'
                });
            },
            success : function (txt) {
                var $modal = $form.closest('div.modal-content'),
                    $txt   = 
                        ABS.utils.extractResponse(txt, '#login_container');
                
                // if the result contains a form, the reg. form has been 
                // redisplayed.
                if ($txt.find('form').length) {
                    $modal.html($txt.find('div.sw'))
                        .find('form').each(cleanup);
                }
                
                // otherwise, it's a success message, and we should auto-hide 
                // it
                else {
                    $modal.html($txt.find('div.message'));
                    setTimeout(function () {
                        ABS.utils.modalClose($modal.closest('.modal'));
                    }, 3000);
                }
            }
        });
    }
    
    return that;
})(jQuery);

/* functionality for the 'my account' area */
ABS.account = (function () {
    var that = {},
        $ = jQuery;
        
    $(function () {
        if ($('body').is('.account')) {
            reinitialize();
        }
    });
    
    function reinitialize() {
        ABS.tags.deleteLinks();
    }
    
    return that;
}());

/* functionality for the login link */
ABS.login = (function ($) {
    var that    = {},
        $create;
    
    that.initialize = function () {
        $create = $('.loginLink');
        $create.click(that.show);
    };
    
    that.show = function () {
        var $modal = ABS.utils.modalMaker('login');
        ABS.utils.createOverlay();
        $modal
            .find('div.modal-content')
            .html('')
            .load(
                $create.attr('href') + ' div.sw', 
                null, 
                function (t) { 
                    $modal.fadeIn(300).find('form').each(cleanup);
                }
            );
        return false;
    };
    
    // unbind the 'onclick' method and override to actually submit the form
    function cleanup () {
        //return;
        var $form   = $(this),
            $button = $form.find('#button');
        
        // attach any 'rel="external" behavior
        ABS.links.initialize();
        
        // remove any onclicks from the button and make it submit the form
        $button.unbind().removeAttr('onclick').click(function () {
            $form.submit();
        });

        $form.find('#loginRegisterLink').unbind().click(function() {
            ABS.utils.modalClose($form.closest('.modal'));
            ABS.register.form();
            return false;
        });
        
        // update the form action and add a submit handler
        $form.ajaxForm({
            beforeSubmit : function () {
                $button.addClass('disabled').attr({
                    disabled : true,
                    value    : 'Signing In..'
                });
            },
            success : function (txt) {
                var $modal = $form.parents('div.modal-content'),
                    $txt   = ABS.utils.extractResponse(txt, 'div.sw');
                
                // if the result contains a form, the login form has been 
                // redisplayed.
                if ($txt.find('#login_form').length) {
                    $modal.html($txt).find('form').each(cleanup);
                }
                
                // otherwise, we've logged in so reload current page
                else {
                    window.location.href=window.location.href;
                }
            }
        });
    }
    
    return that;
})(jQuery);

ABS.utils = (function ($) {
    var that = {};
    
    /* public functions */
    that.initialize = function () {
        $('input.hide').hide();
        $('li:first-child').addClass('first-child');
        $('li:last-child').addClass('last-child');
        $('a.tooltip').each(modalize);
    };
    
    that.createOverlay = function () {
        if (!$('div.overlay').length) {
            $('<div class="overlay"/>').prependTo('#wrapper');
        } 
    };
    
    // TODO: store all pref cookie data in one cookie
    that.getCookie = function (c_name) {
        if (document.cookie.length>0) {
            c_start=document.cookie.indexOf(c_name + "=");
            if (c_start!=-1) { 
                c_start=c_start + c_name.length+1; 
                c_end=document.cookie.indexOf(";",c_start);
                if (c_end==-1) c_end=document.cookie.length;
                return unescape(document.cookie.substring(c_start,c_end));
            } 
        }
        return "";
    };
    
    that.setCookie = function setCookie(c_name,value) {
        var expiredays = 7;
        var exdate=new Date();
        exdate.setDate(exdate.getDate()+expiredays);
        document.cookie=c_name+ "=" +escape(value)+
            ((expiredays==null) ? "" : ";expires="+exdate.toGMTString()+"; path=/");
    };
    
    that.modalMaker = function (id, callback) {
        var ident = id + '-modal',
            $modal = $('#' + ident);
        if (!$modal.length) {
            $('body').append('<div id="' + ident + '"" class="modal" style="display: none;"><p class="close"><img src="/templates/abs/biblesearch/images/site/modal_close.gif" class="close" alt="X" /></p><div class="modal-content"></div></div>');
            $modal = $('#' + ident);
        }
        $modal.unbind().click(function (ev) {
            if ($(ev.target).is('.close')) {
                return that.modalClose($modal, callback);
            }
        });
        return $modal;
    };
    
    that.modalClose = function (modal, callback) {
        var $modal = modal, close = true;
        if ((typeof modal) === 'string') {
            if (!modal.match('-modal')) {
                modal = modal + '-modal';
            }
            $modal = $('#' + modal);
        }
        if (!($modal && $modal.length)) {
            $modal = $('.modal:visible');
        }
        
        // callback function to run before closing?
        if ((typeof callback) === 'function') {
            close = callback.call();
        }
        
        if (close) {
            $modal.fadeOut(300);
            $('div.overlay').remove();
        }
        return false;
    };
    
    // given some response HTML, get the DOM element(s) identified by the 
    // given selector
    that.extractResponse = function (txt, selector) {
        return $("<div/>")
            .append(txt.replace(/<script(.|\s)*?\/script>/g, ""))
            .find(selector);
    };
    
    /* private functions */
    function modalize() {
        $(this).unbind().click(function () {
            that.createOverlay();
            that.modalMaker('tooltip').fadeIn(300)
                .find('div.modal-content')
                .empty()
                .html($($(this).attr('href')).html());
            return false;
        });
    }
    
    return that;
}(jQuery));

ABS.versions = (function ($) {
    var that = {};
    
    /* public functions */
    that.initialize = function () {
        var $modal = ABS.utils.modalMaker('compare');
        $('.passagehead .version a').click(function () {
            ABS.utils.createOverlay();
            $modal.fadeIn(300)
                .find('div.modal-content')
                .load($(this).attr('href') + ' #compareList', function () {
                    ABS.biblesearch.versionSelector();
                });
            return false;
        });
    };
    
    /* private functions */
    
    return that;
}(jQuery));


/******************* ABS.Search *******************/
ABS.search = (function ($) {
    var that = {};
    
    /* public functions */
    that.initialize = function () {
        // handle sidebar search options
        advancedModal();
        
        // handle search results page
        searchResults();
    };
    
    /* private functions */
    
    function searchResults () {
        var $helpLinks = $('a.help'),
            $modal;
        
        // attach modal behavior to the help links
        if ($helpLinks.length) {
            $modal = ABS.utils.modalMaker('help');
            $helpLinks.click(function () {
                ABS.utils.createOverlay();
                $modal.fadeIn(300)
                    .find('div.modal-content')
                    .empty()
                    .load($(this).attr('href') + ' .copy', function () {
                        ABS.links.initialize();
                    });
                return false;
            });
        }
        
        // use event delegation to attach the 'show/hide' behavior on links
        $('div.passage:first-child').addClass('first-child');
        $('div.passage:last-child').addClass('last-child');
        $('div#resultsContainer').click(function (ev) {
            var $tgt = $(ev.target),
                $div,
                $a;
            
            // we only care about clicks on the div and on any immediate child 
            // links
            if ($tgt.is('div.otherVersion') || $tgt.is('div.otherVersion>a'))
            {
                // find our link and the div containing it
                if ($tgt.is('a')) {
                    $a   = $tgt;
                    $div = $tgt.closest('div.otherVersion');
                }
                else {
                    $a   = $tgt.find('> a');
                    $div = $tgt;
                }
                
                // are we showing?
                if ($div.attr('id').match(/^expand/)) {
                    expandVersions($div, $a);
                }
                else {
                    collapseVersions($div, $a);
                }
                
                // prevent link behavior
                return false;
            }
        });
    }
    
    function expandVersions($div, $a) {
        var hideID  = $div.attr('id').replace('expand', 'hide'),
            hideSel = 'div#' + hideID,
            text    = $a.html(),
            expand = function () {
                $div.hide('fast', function () { $a.html(text); });
                $(hideSel).show('fast');
            };
        makeDivs($div, hideID);
        
        // if the div is empty, we need to populate it
        if (!$(hideSel).find('*').length) {
            $a.html('Loading...');
            $(hideSel).load(
                $a.attr('href') + '&js=1 div.otherVersion>*', function () {
                    $(hideSel).find('div.passage:first-child')
                        .addClass('first-child');
                    $(hideSel).find('div.passage:last-child')
                        .addClass('last-child');
                    expand.call();
                }
            );
        }
        
        // otherwise, it's probably been shown already; just show it.
        else {
            expand.call();
        }
    }
    
    function collapseVersions($div, $a) {
        var expandID  = $div.attr('id').replace('hide', 'expand'),
            expandSel = 'div#' + expandID,
            text;
        makeDivs($div, expandID);
        
        // if the div is empty, we need to add the link to it
        if (!$(expandSel).find('*').length) {
            text = $div.closest('div.resultitem').find('h2 a')
                  .html().replace(/\s+\([^\)]*\)$/, '');
            text = 
                'Expand to show ' + text + ' in all selected Bible versions';
            $(expandSel).html('<a href="#" class="expand">' + text + '</a>');
        }
        
        $div.hide('fast');
        $(expandSel).show();
    }
    
    function makeDivs($div, newID) {
        var $newDiv = $('div#' + newID);
        if (!$newDiv.length) {
            $('<div id="' + newID + '" class="otherVersion"/>')
                .hide().insertBefore($div);
        }
    }
    
    function advancedModal () {
        var $modal = ABS.utils.modalMaker('advanced');
        $('input#q').each(function () {
            $(this).data('orig', this.value);
        }).change(function () {
            $(this).data(
                'newval', 
                $(this).data('orig') == this.value ? '' : this.value
            );
        });
        $('#searchsummary .refinesearchbox a').click(function () {
            ABS.utils.createOverlay();
            $modal
                .find('div.modal-content')
                .load(
                    $(this).attr('href') + '&skipInit=1 form#advanced', 
                    '', function () {
                        $(this).each(setupForm);
                        $modal.fadeIn('fast');
                    }
                );
            return false;
        });
    }
    
    // hide appropriate lists
    function setupForm() {
        var $this = $(this),
            $q    = $('input#q');
        $('#submit').hide()
            .after('<input type="submit" value="Search the Bible" />');
        $('#clearAll').hide();
        $this.find('input[name="whichVersions"]')
           .each(versions).click(versions);
        $this.find('input[name="whichBooks"]').each(books).click(books);
        $this.find('input[type="checkbox"]').click(children);
        $this.find('fieldset#advancedSearch').attr('id', '');
        if ($q.data('newval')) {
            $this.find('#as_q').val($q.data('newval'));
            $.each(['epq', 'oq', 'eq'], function () {
                $this.find('#as_' + this).val('');
            });
        }
    }
    
    // toggle versions
    function versions() {
        $('#allVersions')
            .toggle($('#whichVersions_these').attr('checked'));
        if ($('#whichVersions_all').attr('checked')) {
            $('input[name="itemVersions[]"]').attr('checked', false);
        }
    }
    
    // toggle books
    function books() {
        $('#whichBookList')
            .toggle($('#whichBooks_these').attr('checked'));
    }
    
    // toggle children
    function children() {
        $(this)
            .closest('label').next('ul')
            .find('input[type="checkbox"]').attr('checked', this.checked);
    }
    
    return that;
}(jQuery));

ABS.widget = (function ($) {
    var that = {},
        autorefresh = false;
    
    /* public functions */
    that.initialize = function () {
        var timeout;
        
        $('#absEmbed #query').hint(' ');
        if ($().farbtastic) {
            
            // no form submits
            $('#widget-code').submit(function () { return false; });
            
            // called when the color picker's color changes
            $('#colorpicker').farbtastic(function (bgcolor) {
                // if this click did not come from a preset, set the custom 
                // color div
                if (!$('#colorpicker').data('pre-set')) {
                    var c = $.farbtastic('#colorpicker').hsl[2] > 0.5 ?
                                'light' : '';
                    
                    // we don't want dragging colors to send boatloads of AJAX 
                    // requests.  So disable autorefreshing code, but 
                    // re-enable it if we haven't done anything for awhile
                    autorefresh = false;
                    clearTimeout(timeout);
                    timeout = setTimeout(function () {
                        autorefresh = true;
                        refreshCode(true);
                    }, 200);
                    
                    // update the custom div and click it
                    $('#pre-selected-colors div.chosen')
                        .html(bgcolor.toUpperCase())
                        .css('background-color', bgcolor)
                        .removeClass('light').addClass(c).click();
                }
            });
            
            // update color if typed into text box
            $('#color').bind('keyup', function() {
                $.farbtastic('#colorpicker').setColor($(this).attr('value'));
                refreshCode();
            });
            
            // change the branding options
            $('input[name="branding"]').click(function () {
                var val = Number(
                    $('input[name="branding"]:checked').val() || 0
                );
                if (val) {
                    $('#pre-selected-colors').removeClass('custom')
                        .find('.custom, .chosen').hide().end()
                        .find('.default').click();
                    togglePicker(true);
                }
                else {
                    $('#pre-selected-colors').addClass('custom')
                        .find('.custom, .chosen').show();
                }
                callWidgets(function () {
                    if (val) {
                        $(this).find('#query').addClass('branded');
                    }
                    else {
                        $(this).find('#query').removeClass('branded');
                    }
                });
                refreshCode();
            }).filter(':checked').click();
            
            // show/hide the appropriate vertical/horizontal layouts
            $('select[name="size"]').change(function () {
                $('.widget-container').hide();
                $('#widget-' + $(this).val()).show();
                refreshCode();
            }).change();
            
            // update color if selected from palette
            $('#pre-selected-colors div')
                .css('background-color', function () {
                    var bg = $(this).text().trim();
                    return (bg.match(/^#[a-zA-Z0-9]+$/)) ? bg : 'transparent';
                })
                
                // clicking on a specific color
                .click(function() {
                    var $this = $(this), color = $this.text();
                    if ($this.is('.chosen') && !$this.text()) {
                        return;
                    }
                    else if ($this.is('.custom')) {
                        return;
                    }
                    $('#pre-selected-colors div').removeClass('cur');
                    $this.addClass('cur');
                    if ($this.is('.transparent')) {
                        setColor('transparent');
                    }
                    else {
                        $('#colorpicker').data('pre-set', true);
                        $.farbtastic('#colorpicker').setColor(color);
                        setColor(color);
                        $('#colorpicker').data('pre-set', false);
                    }
                
                // the custom color chooser
                }).find('a').click(function () {
                    togglePicker();
                    return false;
                })
                
                // show the default color
                .end().filter('.default').click();
            
            // update the font color
            $('[name="font"]').change(function () {
                setColor();
            }).change();
            
            // get a new code when checking this box
            $('[name="include_dc"]').click(refreshCode);
            
            // when the domain name changes, fetch fresh embed code
            $('#domain').change(function () {
                refreshCode(true);
            }).change();
            
            // now that things have been initialized, set a toggle that tells 
            // everything to auto-update the code
            autorefresh = true;
        }
    };
    
    /* private functions */
    function refreshCode(force) {
        if (!(force || autorefresh)) {
            return;
        }
        var domain = $('#domain').val(),
            $embed = $('#embed-code'),
            $none  = $('#no-domain'),
            $disp  = $('#domain-disp');
        if (domain) {
            $embed.attr('disabled', true).show();
            $none.hide();
            $('#widget-code').ajaxSubmit({
                dataType: 'json',
                success: function (resp) {
                    if (resp.domain) {
                        $embed.attr('disabled', false).text(resp.code.trim());
                        $disp.html(resp.domain).closest('p').show();
                    }
                    else {
                        hide();
                    }
                }
            });
        }
        else {
            hide();
        }
        function hide() {
            $disp.html('').closest('p').hide();
            $embed.hide();
            $none.show();
        }
    }
    
    function setColor(bgcolor) {
        var font = $('[name="font"]').val(), bodyClass;
        if (!bgcolor) {
            bgcolor = $('#pre-selected-colors div.cur').text().trim();
            if (!bgcolor.match(/^#[a-zA-Z0-9]+$/)) {
                bgcolor = 'transparent';
            }
        }
        if (font == 'auto') {
            color = $.farbtastic('#colorpicker').hsl[2] > 0.5 ?
                            '#000000' : '#FFFFFF';
        }
        else {
            color = font;
        }
        $('#color').attr('value', bgcolor);
        bodyClass = (color === '#000000') ? 'light' : 'dark';
        callWidgets(function () {
            $(this).find('body').andSelf().css({
                'background-color': bgcolor
            }).removeClass('light').removeClass('dark').addClass(bodyClass);
        });
        refreshCode();
    }
    
    function togglePicker(off) {
        var $picker = $('#colorpicker'),
            $link   = $('#pre-selected-colors div.copy a'),
            copy    = $picker.is(':visible') ? 'custom...' : 'close';
        if (off) {
            copy = 'custom...';
        }
        $link.html(copy);
        if (off) {
            $picker.hide();
        }
        else {
            $picker.slideToggle('fast');
        }
    }
    
    function callWidgets(fn) {
        $('.widget-container').each(function () {
            var f     = $(this).attr('id').replace('widget', 'preview'),
                frame = window.frames[f],
                t;
            if (!runIt(true)) {
                t = setInterval(function () {
                    if (runIt()) {
                        clearInterval(t);
                    }
                }, 300);
            }
            function runIt(force) {
                if (frame.jQuery) {
                    
                    // if we're forcing the function to be run (first try), 
                    // run it explicitly
                    if (force) {
                        frame.jQuery('body').each(fn);
                    }
                    
                    // otherwise, attach it as a load event to be run later
                    else {
                        frame.jQuery(fn);
                    }
                    return true;
                }
                return false;
            }
        });
    }
    
    return that;
}(jQuery));
