window.ABS = window.ABS || {};
ABS.verseSelector = (function ($) {
    var that = {},
        selectTitle   = ABS.common.i18n('click_to_select_verse'),
        deselectTitle = ABS.common.i18n('click_to_deselect_verse'),
        regexp        = /^(v(\d+)_([\dA-Z]+)_)(\d+)$/,
        passageSelector, onSelect, onDeselect, onClick, allowSelection, $parent;

    that.initialize = function(opts) {
        opts            = opts || {};
        onSelect        = opts.onSelect        || null;
        onDeselect      = opts.onDeselect      || null;
        onClick         = opts.onClick         || null;
        $parent         = $(opts.parent)       || null;
        passageSelector = opts.passageSelector || '.passagedisplay';
        if (typeof(opts.allowSelection) !== 'undefined') {
            allowSelection = opts.allowSelection;
        }
        else {
            allowSelection = true;
        }
        delegateParentEvents();
        that.reParseVerses(passageSelector);
    };

    that.reParseVerses = function(selector) {
        $parent.find(selector).each(parseVerses);
    };

    that.deselectAll = function() {
        $parent.find('.selected').removeClass('selected');
    };

    that.getSelected = function() {
        // get our selected elements, removing any that have a *parent* that 
        // is selected, to build just a list of the top-level selected 
        // elements (so that we don't select individual verses and verse 
        // numbers along with their containers)
        return $parent.find('.selected').filter(function () {
            return $(this).parents('.selected').length === 0;
        });
    };

    that.getSelectable = function() {
        return $parent.find('.selectable');
    };
   
    that.getSelectedText = function() {
        return $.trim(
            
            // clone our selected verses, removing any tag/note icons
            that.getSelected().clone().find('.passage-icon').remove().end()
            
            // now fetch the text, filtering it for nicer display
            .text().replace(/\n/g, ' ').replace(/\s+/g, ' ')
        );
    };

    that.getSelectedVerseNumber = function () {
        var $sel = that.getSelected(),
            classes  = []
            verses   = [];
        $sel.each(function () { classes.push($(this).data('verse')); });
        _.each(_.uniq(classes), function(clas) {
            var match = regexp.exec(clas);
            verses.push(match[4]);
        });
        var verse = verses.join(',');
        return verse;
    };
    
    that.getSelectedOsisSpans = function () {
        var $sel = that.getSelected(),
            classes  = [],
            spans    = [],
            osis     = [],
            selected = [],
            start, end, next;
        $sel.each(function () { classes.push($(this).data('verse')); });
        _.each(_.uniq(classes), function(clas) {
            var match = regexp.exec(clas);
            selected.push({book: match[2], chapter: match[3], verse: match[4]});
        });
        selected = selected.sort(function(a,b) {
            if (a.book === b.book) {
                if (a.chapter === b.chapter) {
                    if (a.verse === b.verse) {
                        return 0;
                    }
                    else {
                        return a.verse - b.verse;
                    }
                }
                else {
                    return a.chapter - b.chapter;
                }
            }
            else {
                return a.book - b.book;
            }
        });

        start = selected[0];
        for (var i = 0; i < selected.length; i++) {
            if (next && (next.book != selected[i].book || next.chapter != selected[i].chapter || next.verse != selected[i].verse)) {
                spans.push([start, end]);
                start = selected[i];
            }
            end  = selected[i];
            next = { book: end.book, chapter: end.chapter, verse: parseInt(end.verse, 10) + 1 };
        }
        if (start) {
            spans.push([start, end]);
        }

        _.each(spans, function(span) {
            osis.push([
                ABS.common.osis.fromBookOrd(span[0].book) + '.' + span[0].chapter + '.' + span[0].verse,
                ABS.common.osis.fromBookOrd(span[1].book) + '.' + span[1].chapter + '.' + span[1].verse
            ]);
        });
        return osis;
    };

    that.getSelectedSpans = function() {
        var $sel = that.getSelected(),
            classes = [],
            verses  = [],
            spans   = [],
            start, end, next;
        $sel.each(function () { classes.push($(this).data('verse')); });
        $.each(_.compact(_.uniq(classes)), 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;
        }
        if (start) {
            spans.push(format(start, end));
        }

        function format(start, end) {
            return (start == end) ? start.toString() : start + '-' + end;
        }

        return spans;
    };
    
    // select a span of verses as defined by the hash.  Public to allow 
    // other modules to use it
    that.selectSpan = function (e, showForm) {
        var hash;
        if (typeof e === 'object') {
            hash = $(this).attr('hash').replace('#', '');
        }
        else if (typeof e === 'string') {
            hash = e.replace('#', '');
        }
        var range,
            className,
            start,
            end;
        showForm = (showForm === undefined) ? true : showForm;
        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(_.uniq(className), function (k, v) {
                    toggleVerse(v, true);
                });
            }
            else {
                var ranges = hash.split(/\s*,\s*/);
                for (j = 0; j < ranges.length; j++) {
                    range     = ranges[j].split('-');
                    className = $parent.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 (onSelect && typeof onSelect == 'function') {
            onSelect();
        }
        return false;
    };


    // delegate click on parent element
    function delegateParentEvents() {
        var $start = null;
        
        if (allowSelection) {
            $parent.delegate('.selectable', 'mousedown', function (e) {
                // 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
                if (window.event) {
                    if (window.event.shiftKey) {
                        this.onselectstart = function () { 
                            return false; 
                        };
                    }
                    else {
                        this.onselectstart = function () { return true; };
                    }
                }
                return (!e.shiftKey);
            })
            .delegate('.selectable', 'click', function (e) {
                // when clicking, (de)select the verse, and optionally the 
                // verse span
                var $tgt   = $(e.target),
                    verses = [],
                    select = !$tgt.is('.selected');
                if (!$tgt.is('.selectable')) {
                    e.preventDefault();
                    return true;
                }
                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 (onClick && typeof onClick == 'function') {
                    onClick(e);
                }
                    
                // if we're holding down shift, select everything from 
                // where we started to here
                if (e.shiftKey) {
                    $.each(selectVerseSpan($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(_.uniq(verses), function (i, v) {
                        toggleVerse(v, select);
                    });
                    if (onSelect && typeof onSelect == 'function' && select) {
                        onSelect(e);
                    }
                    else if (onDeselect && typeof onDeselect == 'function' && !select) {
                        onDeselect(e);
                    }
                }
                return false;
            });
        }
    }
    
    // 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 we've already selectified this, skip it
            if ($el.data('verse')) {
                return;
            }
            if ($el.is('verse-block') || $el.is('.title-passage') || 
                $el.is('.area') || $el.is('br') || $el.is('h1,h2,h3,h4,h5,h6'))
            {
                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) {
                if ($.trim($el.text()) === '') {
                    return;
                }
                $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)
                        .attr('title', selectTitle)
                        .hover(
                            function () {
                                if (!$(this).is('.selected')) {
                                    $('.' + $(this).data('verse')).addClass('over');
                                }
                            },
                            function () {
                                $('.' + $(this).data('verse')).removeClass('over');
                            }
                        )
                        .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 selectVerseSpan($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');
    }

    return that;
}(jQuery));

