window.ABS = window.ABS || {};
window.ABS.View = window.ABS.View || {};

// View-related utility functions
ABS.View.util = (function($) {
    var that = {};

    that.scrollableHeightGuess = function() {
        var otherStuffHeight = 300; // The stuff above and below the scrollable is probably about this high
        return $(window).height() - otherStuffHeight;
    };

    return that;
}(jQuery));

ABS.View.Sidebar = Backbone.View.extend({
    tagName: 'div',
    className: 'fivecol maincolumn',
    panels: {},

    events: {
        "click .tools-nav a" : "iconClick",
        "hover .tools-nav a" : "toggleHover",
        "mouseenter .tools-nav a span" : "removeHover",
        "click #sidebar-login-button" : "showLoginForm",
        "click .actions a" : "iconClick",
        "hover .actions a" : "toggleHover",
        "mouseenter .actions a span" : "removeHover",
        "click .sidebarheader" : "titleClick"
    },

    initialize: function() {
        _.bindAll(this, "render", "addPanel");
    },

    render: function() {
        this.el.innerHTML = ABS.jst.Sidebar();
        this.toolsNav = this.$('.tools-nav');
        this.actions  = this.$('.actions');

        var that = this;
        _.each(this.options.panels, function(args, key) {
            var panel = args[0];
            var options = args[1] || {};
            that.addPanel(panel, key, options);
        });

        // Show the Login button if the user isn't logged in
        if (!window.ABS.bootstrap.loggedin) {
            this.$('#sidebar-login-button').removeClass('hidden');
        }
        // Displays the logout message
        if (window.ABS.bootstrap.notice === 'logout') {
            this.makePanelActive('login');
            this.$('#sidebar-logout-message').removeClass('hidden');
            setTimeout(function() { 
                $('#sidebar-logout-message').fadeOut('slow'); 
            }, 9000);
        }
        else if (window.ABS.bootstrap.notice === 'verified') {
            this.makePanelActive('verified');
        }
        else if (window.ABS.bootstrap.notice === 'notverified') {
            this.makePanelActive('notverified');
        }

        return this;
    },

    addPanel: function(panel, key, options) {
        if (options.primary) {
            this.toolsNav.append(ABS.jst.SidebarTab({name: panel.tabName(), displayname: panel.tabDisplayName(), id: panel.id}));
        }
        if (options.secondary) {
            this.actions.append(ABS.jst.SidebarTab({name: panel.tabName(), displayname: panel.tabDisplayName(), id: panel.id, title: panel.tabDisplayName()}));
        }

        this.panels[key] = panel;
        this.$('.divaside').first().prepend(panel.render().el);
    },

    iconClick : function(ev) {
        var that = this;
        ev.preventDefault();
        // after click we want hover class removed
        $(ev.currentTarget).removeClass('hover');
        var li = $(ev.currentTarget).closest('li');

        // find the panel key based on this id
        _.each(this.panels, function(panel, key) {
            if (panel.id == li.data('panel-id')) {
                // important that iconclick be sent before makePanelActive.
                // iconclick may be used by a panel to do things
                // if it is already active.
                panel.trigger('panel:iconclick');
                that.makePanelActive(key);
            }
        });
    },
    
    // When the title of the sidebar is clicked, something might need to 
    // happen... or not
    // Currently used only for Tags
    titleClick : function(ev) {
        ev.preventDefault();
        var mypanelid = $(ev.currentTarget).closest('.sidebarPanel').attr('id');
        if (mypanelid.match("Tag")) {
            this.panels['tag'].titleClicked();
        }
    },

    // we're not using CSS hover, because we want to unhover when
    // mouse hovers on child element (see removeHover)
    toggleHover : function(ev) {
        $(ev.target).toggleClass('hover', ev.type === 'mouseenter');
    },

    removeHover : function(ev) {
        if (!this.toolsNav.hasClass('tools-nav-holder')) {
            $(ev.target).parent().removeClass('hover');
        }
    },

    // make a sidebar panel active by key in this.panels
    // panel that is being made active will receive a panel:activate event
    // as well as any additional parameters that were sent with madePanelActive
    // call.  All other panels receive panel:deactivate event.
    makePanelActive: function(panelKey) {
        // We're about to activate a new panel, so clear the active style from
        // whatever icons are currently active
        this.$('.tools-nav li.active, .actions li.active').removeClass('active');

        // Make the default panel's icon active on pageload.
        // TODO: Figure out why the "active icon" settings near the bottom 
        // of this function don't activate the browse icon on pageload.
        // Once that is fixed, the following line will be unnecessary.
        this.$('.tools-nav a.' + panelKey).closest('li').addClass('active');

        // get all but first argument
        var activateArgs   = _.rest(arguments);
        var deactivateArgs = _.rest(arguments);
        // add event name to beginning of args
        activateArgs.unshift('panel:activate');
        deactivateArgs.unshift('panel:deactivate');

        // activate panel after sending deactivate events
        var activePanels = [];
        _.each(this.panels, function(panel, key) {
            if (panelKey === key) {
                activePanels.push(panel);
            }
            else {
                panel.trigger.apply(panel, deactivateArgs);
            }
        });
        _.each(activePanels, function(panel) {
            panel.trigger.apply(panel, activateArgs);
            
            // If an icon exists with the panel's ID, style it as active
            panelId = $(panel).attr('id');
            this.$('.tools-nav li[data-panel-id="' + panelId + '"]').addClass('active');
            this.$('.actions li[data-panel-id="' + panelId + '"]').addClass('active');
        });

        // this class shows tab names always
        // remove it to only show names on hover of tab
        var addClass = (panelKey === 'welcome');
        this.toolsNav.toggleClass('tools-nav-holder', addClass);
    },

    showLoginForm: function(ev) {
        ev.preventDefault();
        this.makePanelActive('login');
    },

    showTransferAnimation: function(fromElem) {
        // Show the transfer animation from the given element to
        // the panel area
        var activePanelID = this.$('.active').first().attr('id');
        var options = {
            to: '#' + activePanelID,
            className: 'sidebar-transfer-effect'
        };
        $(fromElem).effect('transfer', options, 450);
    }
});

ABS.View.SidebarPanel = Backbone.View.extend({
    tagName: 'div',
    className: 'sidebarPanel',
    
    initialize: function() {
        this.id = this.options.name.replace(/[^A-Za-z0-9_\-]/g, '_') + _.uniqueId('SidebarPanel_');
        this.el.id = this.id;

        _.bindAll(this, "render", 'activatePanel', 'deactivatePanel', 'iconClick');
        this.bind('panel:activate', this.activatePanel);
        this.bind('panel:deactivate', this.deactivatePanel);
        this.bind('panel:iconclick', this.iconClick);
    },

    tabName: function() {
        return this.options.name;
    },

    tabDisplayName: function() {
        return this.options.displayname;
    },

    render: function() {
        this.el.innerHTML = ABS.jst.SidebarPanel({displayname: this.options.displayname});
        return this;
    },


    iconClick : function() { },

    activatePanel : function() {
        $(this.el).addClass('active');
        $(window).resize();
    },

    deactivatePanel : function() {
        $(this.el).removeClass('active');
    }

});

ABS.View.BrowseView = ABS.View.SidebarPanel.extend({

    types: ['version', 'section', 'book', 'chapter'],
    
    events: {
        'click ul.option-list a'   : 'selectChoice',
        'click #chapterList a'          : 'selectChoice',
        'click ul.choice a'             : 'deselectChoices'
    },

    initialize: function() {
        // call parent initialize
        ABS.View.SidebarPanel.prototype.initialize.call(this);

        _.bindAll(this, "render", 'iconClick', 'selectChoice', 'showLoadingIndicator', 'activateWithOptions');

        this._versions = new ABS.Collection.VersionList(ABS.bootstrap.versions);

        this.bind('panel:activate', this.activateWithOptions);
        this.model = new ABS.Model.Browse();
        this.model.bind('change', this.render);
        this.model.bind('startfetch', this.showLoadingIndicator);
        this.bind('panel:iconclick', this.iconClick);
    },

    render: function() {
        ABS.View.SidebarPanel.prototype.render.call(this);
        this.$('.divsection').first().addClass('list-holder').html(ABS.jst.SidebarBrowse({
            'choices'        : this.model.getChoices(),
            'list'           : this.model.get('list'),
            'isVersionList'  : this.model.get('version') ? false : true,
            'isBookList'     : this.model.get('version') && !this.model.get('book'),
            'isChapterList'  : this.model.get('book') ? true : false,
            'versionsByLang' : this._versions.sortedByLang
        }));
        window.ABS.spi.leftSidebar.makePanelActive('browse');
        $(window).resize();
        return this;
    },

    // if options are passed when activated we can do something with them
    activateWithOptions : function(options) {
        if (options && options.version) {
            this.model.reset();
            var choice = this.model.get('list').get(options.version);
            this.model.push(choice);
        }
    },

    iconClick : function() {
        if ($(this.el).hasClass('active')) {
            this.model.reset();
        }
    },

    selectChoice: function(ev) {
        var link = $(ev.currentTarget), choice;
        
        // allow version-info links to behave as normal
        if (link.is('.version-info')) {
            return;
        }
        
        // otherwise, choose the item
        ev.preventDefault();
        choice = this.model.get('list').get(link.data('id'));

        link.parent().parent().find('a').removeClass('active');
        link.addClass('active');
        if (choice.get('chapter')) {
            var newhash = '#' + ABS.common.osis.toUrl(choice.id);

            if (location.hash === newhash) {
                // The controller only routes on hash changes
                window.ABS.spi.passagePanel.loadFromLocationHash();
            } else {
                location.hash = newhash;
            }
        }
        else {
            // add their choice to the model
            this.model.push(choice);
        }
    },

    deselectChoices: function(ev) {
        ev.preventDefault();
        var link = $(ev.currentTarget);
        this.model.pop(link.data('type'));
    },

    showLoadingIndicator: function() {
        this.$('.loading-spinner-holder').css('display', 'block');
    }
});

ABS.View.NoteSidebarView = ABS.View.SidebarPanel.extend({
    initialize: function() {
        // call parent initialize
        var that = this;
        ABS.View.SidebarPanel.prototype.initialize.call(this);
        _.bindAll(this, 'render', 'showFilteredList', 'iconClicked', 'showList', 'showNote', 'cancel');


        this.bind('panel:activate', this.showFilteredList);
        this.bind('panel:iconclick', this.iconClicked);

    },

    render: function() {
        var that = this;
        $(this.el).empty().html(
            ABS.jst.Note_Sidebar({displayname: this.options.displayname})
        );
        this.noteListView = new ABS.View.NoteListView({
            collection : this.collection,
            el : this.$('.noteList .scrollable')
        });
        this.noteShowView = new ABS.View.NoteShowView({
            el : this.$('.noteShow')
        });

        this.noteListView.bind('note:click', this.showNote);
        this.noteShowView.bind('note:choose', this.showList);
        this.noteShowView.bind('note:cancel', this.cancel);

        return this;
    },

    cancel : function() {
        window.ABS.spi.leftSidebar.makePanelActive('note');
        this.showList();
    },

    showNote : function(note) {
        this.$('.noteList').hide();
        this.noteShowView.show(note);
    },

    showList : function() {
        this.$('.noteList').show();
        this.noteShowView.el.hide();
    },

    showFilteredList : function(options) {
        if (!window.ABS.bootstrap.loggedin) {
            window.ABS.spi.leftSidebar.makePanelActive('login', {
                flashError: 'login_to_note'
            });
            return;
        }
        if (options) {
            if (options.showOnly) {
                this.noteListView.setShowOnly(options.showOnly);
                this.showList();
            }
            else if (options.showNote) {
                this.showNote(options.showNote);
            }
        }
    },

    // override parent method to check for unsaved note
    deactivatePanel : function() {
        if ($(this.el).is(':visible')) {
            this.noteShowView.confirmSave();
        }
        ABS.View.SidebarPanel.prototype.deactivatePanel.call(this);
    },

    iconClicked : function() {
        if ($(this.el).hasClass('active')) {
            this.noteShowView.confirmSave();
            this.showList();
        }
    }
});

ABS.View.NoteCreateView = ABS.View.SidebarPanel.extend({
    initialize: function() {
        // call parent initialize
        ABS.View.SidebarPanel.prototype.initialize.call(this);
        this.bind('panel:activate', this.nowVisible);
        _.bindAll(this, 'showNewNote', 'addRefsToExistingNote', 'nowVisible');
    },

    render : function() {
        this.el.innerHTML = ABS.jst.Note_Sidebar({displayname: this.options.displayname});

        this.noteTitleFilter = new ABS.View.NoteTitleFilter({
            collection : this.collection,
            el : this.$('.noteList .divsection')
        }).render();

        this.noteTitleFilter.bind('note:click', this.addRefsToExistingNote);
        this.noteTitleFilter.bind('note:new', this.showNewNote);
        return this;
    },

    nowVisible : function() {
        if (!window.ABS.bootstrap.loggedin) {
            window.ABS.spi.leftSidebar.makePanelActive('login', {
                flashError: 'login_to_note'
            });
            return;
        }
        this.$('.noteList').show();
    },

    addRefsToExistingNote : function(note) {
        var refs = this.getRefs();
        note.get('references').add(refs);
        note.save();
        window.ABS.spi.leftSidebar.makePanelActive('note', {showNote: note});
    },

    showNewNote : function(note) {
        var refs = new ABS.Collection.ReferenceList(this.getRefs());

        note.set({references: refs});
        note.hasUnsavedChanges = false;
        note.save();

        window.ABS.spi.myNotes.add(note);
        window.ABS.spi.leftSidebar.makePanelActive('note', {showNote: note});
    },

    getRefs : function () {
        // FIXME: provide a better way to get version of selected text
        var versionId = ABS.spi.passagePanel.collection.first().get('version').id;
        return _.map(ABS.verseSelector.getSelectedOsisSpans(), function(ref){ 
            return {
                start : versionId + ':' + ref[0],
                end   : versionId + ':' + ref[1]
            };
        });
    }
});

// provides note title filter list widget
// Note list gets filtered on user input
//
// bubbles events from child NoteListView
// and triggers note:new(note) on form submit
ABS.View.NoteTitleFilter = Backbone.View.extend({
    events : {
        'keyup input#create-note-title' : 'filterList',
        'submit form' : 'newNote',
        'click a.cancel' : 'cancel'
    },

    initialize : function () {
        _.bindAll(this, 'bubble', 'filterList', 'newNote', 'reset', 'cancel');
    },
    render : function () {
        var that = this;
        this.el.empty().html(ABS.jst.NoteTitleFilter());
        this.noteList = new ABS.View.NoteListView({
            collection : this.collection,
            el : this.$('.scrollable'),
            disableAutoSelect: true
        });
        this.noteList.bind('all', this.bubble);

        this.noteList.bind('note:click', this.reset);
        return this;
    },

    reset : function() {
        this.$('#create-note-title').val('');
        this.noteList.setShowOnly([]);
    },

    cancel : function(ev) {
        ev.preventDefault();
        this.reset();
        this.trigger('note:cancel');
    },

    filterList : function(ev) {
        var input = $(ev.currentTarget).val().toLowerCase();
        var matchingIds = _.pluck(this.collection.filter(function(note) {
            return note.get('title').toLowerCase().indexOf(input) !== -1;
        }), 'id');
        if (matchingIds.length === 0) {
            matchingIds = [null];
        }
        this.noteList.setShowOnly(matchingIds);
    },

    newNote : function(ev) {
        ev.preventDefault();
        var title = this.$('#create-note-title');
        this.trigger('note:new', new ABS.Model.Note({
            title : title.val()
        }));
        this.reset();
    },

    bubble : function() {
        this.trigger.apply(this, arguments);
    }

});

ABS.View.NoteView = Backbone.View.extend({
    tagName : "li",
    className : "note",

    events : {
        'click a' : 'noteClick'
    },

    render : function() {
        $(this.el).empty();
        $(this.el).append(ABS.jst.Note_ListItem({
            title : this.model.getTitle()
        }));
        return this;
    },

    noteClick : function(ev) {
        ev.preventDefault();
        this.trigger('note:click', this.model);
    }
});

ABS.View.NoteShowView = Backbone.View.extend({
    events : {
        'click .note-show .scrollable' : 'focusTextArea',
        'click a.close' : 'removeReference',
        'click .btn-save' : 'saveNote',
        'click .heading h2 a' : 'editTitle',
        'click .btn.done' : 'doneEditingTitle',
        'click a.cancel'  : 'cancel'
    },

    initialize : function() {
        _.bindAll(this, 'render', 'show', 'focusTextArea', 'saveNote', 'removeReference', 'editTitle', 'doneEditingTitle', 'trackChanges', 'cancel');
    },

    render : function() {
        var origScrollableHeight = this.$('.scrollable').height();
        if (origScrollableHeight === null) {
            origScrollableHeight = ABS.View.util.scrollableHeightGuess();
        }

        $(this.el).empty();
        // saved < 2 seconds ago
        var mostRecentReference = _.last(this.model.get('references').sortBy(function(ref) {
            return (new Date(ref.get('created_at'))).getTime();
        }));
        var savedRecently = (new Date()) - (new Date(this.model.get('updated_at'))) < 2 * 1000 || (new Date()) - (new Date(mostRecentReference.get('created_at'))) < 2 * 1000;
        $(this.el).append(ABS.jst.Note_Show({
            title   : this.model.get('title'),
            content : this.model.get('content'),
            updated_at : this.model.get('updated_at') ? new Date(this.model.get('updated_at')) : null,
            references : this.model.get('references')
        })); 

        var scrollable = this.$('.scrollable');
        scrollable.height(origScrollableHeight);

        if (savedRecently) {
            var srElement  = this.$('.success-message');
            scrollable.animate({
                height: origScrollableHeight - srElement.height()
            });
            srElement.slideDown();
            _.delay(function() {
                if (srElement.is(':visible')) {
                    scrollable.animate({
                        height: scrollable.height() + srElement.height()
                    });
                    srElement.slideUp();
                }
                else {
                    srElement.hide();
                }
            }, 5000);
        }

        $(window).resize();

        var textarea = this.$('textarea');
        // autoResize plugin is bound to the change event
        // so we trigger it to make sure textarea shows entire content.
        _.defer(function() {
            textarea.autoResize({
                animate: false,
                // for some reason long content comes up short by a bit, add
                // extraSpace to compensate.
                extraSpace: 60,
                limit: 999999
            });
            textarea.trigger('change');
        });
    },

    // it isn't clear to user where textarea starts or ends
    // any click in scrollable area should focus the textarea
    focusTextArea : function(ev) {
        ev.preventDefault();
        this.$('textarea').focus();
    },

    cancel : function(ev) {
        ev.preventDefault();
        this.trigger('note:cancel');
    },

    // this is called when we are going to be dismissed
    // if note has unsaved changes, ask user if they want to save the note
    confirmSave : function() {
        if ($(this.el).is(':visible') && (this.model.hasUnsavedChanges || this.hasFormChanges())) {
            // FIXME: l10n this
            if (confirm(ABS.common.i18n('save_changes_to_note'))) {
                this.saveNote();
            }
        }
    },

    removeReference : function(ev) {
        ev.preventDefault();
        var cid  = $(ev.currentTarget).data('reference-cid');
        var refs = this.model.get('references');
        var ref  = refs.getByCid(cid);
        refs.remove(ref);
        this.model.trigger('change');
    },

    editTitle : function(ev) {
        ev.preventDefault();
        this.$('.btn.done').show();
        this.$('.divheader h2').hide();
        this.$('.divheader .create-note').show();
    },

    doneEditingTitle : function(ev) {
        ev.preventDefault();
        this.$('.divheader h2').show();
        this.$('.divheader .create-note').hide();
        this.$('.btn.done').hide();
        this.model.set({title: this.$('#create-note-title').val()});
    },

    show : function(note) {
        // unbind previous model if it exists
        if (this.model) {
            this.model.unbind('change', this.render);
            this.model.unbind('change', this.trackChanges);
        }
        this.model = note;
        this.model.bind('change', this.render);
        this.model.bind('change', this.trackChanges);
        this.render();
        $(this.el).show();
    },

    // track model changes so we know if there is unsaved data
    trackChanges : function() {
        this.model.hasUnsavedChanges = true;
    },

    hasFormChanges : function() {
        var formValues = this.getAttributesFromForm();
        var hasFormChanges = false;
        var model = this.model;
        _.each(formValues, function(value, key) {
            if (value !== model.get(key)) {
                hasFormChanges = true;
            }
        });
        return hasFormChanges;
    },

    getAttributesFromForm : function() {
        return {
            content : this.$('textarea').val(),
            title : this.$('#create-note-title').val()
        };
    },

    saveNote : function(ev) {
        var that = this;
        if (ev && ev.preventDefault) {
            ev.preventDefault();
        }
        this.model.set(this.getAttributesFromForm());
        var noteSave = this.model.save().request.promise();
        noteSave.done(function(note, statusText, response) {
            that.model.hasUnsavedChanges = false;
            if (response.status === 201) {
                window.ABS.spi.myNotes.add(that.model);
            }
        });
    }

});

ABS.View.NoteListView = Backbone.View.extend({
    showOnly   : [],
    shownNotes : [],

    initialize : function() {
        var that = this;

        _.bindAll(this, 'refresh', 'render', 'bubble');
        this.collection.bind('refresh', this.refresh);
        this.collection.bind('add', this.refresh);
        this.collection.bind('remove', this.refresh);
        this.collection.bind('change', this.refresh);

        this.refresh();
    },

    render : function () {
        var that = this;

        var origScrollableHeight = this.$('.scrollable').height();
        if (origScrollableHeight === null) {
            origScrollableHeight = ABS.View.util.scrollableHeightGuess();
        }

        $(this.el).empty();
        $(this.el).append(ABS.jst.Note_MyNotes()); 
        var list = this.$('.scrollable .list');
        this.$('.scrollable').height(origScrollableHeight);

        this.shownNotes = [];
        _(this._noteViews).each(function(nv) {
            if (that.showOnly.length > 0) {
                if (_.include(that.showOnly, nv.model.id)) {
                    list.append(nv.render().el);
                    that.shownNotes.push(nv.model);
                }
            }
            else {
                list.append(nv.render().el);
            }
        });

        // if we're doing special filtering and only showing one in our list,
        // skip to show the note itself.
        if (this.shownNotes.length === 1 && !this.options.disableAutoSelect) {
            this.trigger('note:click', _.first(this.shownNotes));
        }
        $(window).resize();
    },

    setShowOnly : function(showOnly) {
        this.showOnly = showOnly;
        this.refresh();
    },
    
    refresh : function(notes) {
        this.updateNoteViews(this.collection, this);
        this.render();
    },
    
    updateNoteViews : function (notes, that) {
        that._noteViews = [];

        notes.each(function(note) {
            var noteView = new ABS.View.NoteView({
                model : note
            });
            noteView.bind('all', that.bubble);
            that._noteViews.push(noteView);
        });
    },

    bubble : function() {
        this.trigger.apply(this, arguments);
    }
});

ABS.View.TagCreateView = ABS.View.SidebarPanel.extend({
    events : {
        'submit form' : 'createTag',
        'click a.cancel' : 'cancel'
    },
    initialize: function() {
        // call parent initialize
        ABS.View.SidebarPanel.prototype.initialize.call(this);
        this.bind('panel:activate', this.nowVisible);
        _.bindAll(this, "render", 'nowVisible');
    },
    render: function() {
        ABS.View.SidebarPanel.prototype.render.call(this);
        this.$('.scrollable').empty().html(ABS.jst.Tag_Create());
        return this;
    },

    cancel : function(ev) {
        ev.preventDefault();
        window.ABS.spi.leftSidebar.makePanelActive('tag');
    },

    nowVisible : function() {
        if (!window.ABS.bootstrap.loggedin) {
            window.ABS.spi.leftSidebar.makePanelActive('login', {
                flashError: 'login_to_tag'
            });
            return;
        }
        this.$('input[name="name"]').focus();
    },

    createTag: function(ev) {
        ev.preventDefault();
        var input  = $(ev.target).find('input[name="name"]');
        var button = $(ev.target).find('button');
        if (button.is(':disabled')) { return; }

        // FIXME: provide a better way to get version of selected text
        var versionId = ABS.spi.passagePanel.collection.first().get('version').id;
        var refs = _.map(ABS.verseSelector.getSelectedOsisSpans(), function(ref){ 
            return {
                start : versionId + ':' + ref[0],
                end   : versionId + ':' + ref[1]
            };
        });

        var tagging = new ABS.Model.Tagging({
            tag : {
                name: input.val()
            },
            references: refs
        });
        button.attr('disabled', true);
        var tagSave = tagging.save().request.promise();
        tagSave.done(function(tagging) {
            var tag = window.ABS.spi.myTags.addTagging(tagging);
            // we use this in the view to see if the tag has been saved recently
            tag.set({updated_at: new Date().getTime()});

            button.attr('disabled', false);
            input.val('');
            window.ABS.spi.leftSidebar.makePanelActive('tag', {showTag: tag});
        }).fail(function() {
            button.attr('disabled', false);
        });
        
    }
});
ABS.View.TagSidebarView = ABS.View.SidebarPanel.extend({
    initialize: function() {
        // call parent initialize
        ABS.View.SidebarPanel.prototype.initialize.call(this);
        _.bindAll(this, "render", 'showFilteredList', 'iconClicked', 'titleClicked', 'showTag', 'showList');
        this.bind('panel:activate', this.showFilteredList);
        this.bind('panel:iconclick', this.iconClicked);
    },

    render: function() {
        ABS.View.SidebarPanel.prototype.render.call(this);
        this.$('.scrollable').empty().html(ABS.jst.Tagging_Sidebar());
        this.tagListView = new ABS.View.TagListView({
            collection : this.collection,
            el : this.$('.tagList')
        });
        this.tagShowView = new ABS.View.TagShowView({
            el : this.$('.tagShow')
        });

        this.tagListView.bind('tag:click', this.showTag);
        this.tagShowView.bind('tag:choose', this.showList);
        return this;
    },

    showTag : function(tag) {
        $(this.tagListView.el).hide();
        this.tagShowView.show(tag);
        $(window).resize();
    },

    showList : function() {
        $(this.tagListView.el).show();
        $(this.tagShowView.el).hide();
        $(window).resize();
    },

    iconClicked : function() {
        if ($(this.el).hasClass('active')) {
            this.showList();
        }
    },

    titleClicked : function() {
        if ($(this.el).hasClass('active')) {
            this.showList();
        }
    },
    
    showFilteredList : function(options) {
        if (!window.ABS.bootstrap.loggedin) {
            window.ABS.spi.leftSidebar.makePanelActive('login', {
                flashError: 'login_to_tag'
            });
            return;
        }
        if (options) {
            if (options.showOnly) {
                this.showList();
                this.tagListView.setShowOnly(options.showOnly);
            }
            else if (options.showTag) {
                this.showTag(options.showTag);
            }
        }
    }
});

ABS.View.TagView = Backbone.View.extend({
    tagName : "li",
    className : "tag",

    events : {
        'click a' : 'tagClick'
    },

    render : function() {
        var options = {
            name : this.model.id
        };

        $(this.el).empty();
        $(this.el).append(ABS.jst.Tagging_Tag(options));
        return this;
    },

    tagClick : function(ev) {
        ev.preventDefault();
        this.trigger('tag:click', this.model);
    }
});

ABS.View.TagShowView = Backbone.View.extend({
    events : {
        'click .choice li a' : 'hideClick',
        'click a.tag-reference' : 'referenceClick'
    },

    initialize : function() {
        _.bindAll(this, 'render', 'show', 'hideClick');
    },

    render : function() {
        var savedRecently = (new Date().getTime() - this.model.get('updated_at') < 2 * 1000); // 2 seconds
        $(this.el).empty();
        $(this.el).append(ABS.jst.Tag_Show({
            tag : this.model.id,
            savedRecently: savedRecently,
            references : this.model.getReferences()
        })); 
        var flash = this.$('.flash-ok');
        _.delay(function() {
            flash.fadeOut();
        }, 5 * 1000); // fade out flash after 5 seconds
        $(window).resize();
    },

    show : function (tag) {
        this.model = tag;
        this.render();
        $(this.el).show();
    },

    hideClick : function(ev) {
        ev.preventDefault();
        this.trigger('tag:choose');
    },

    referenceClick : function(ev) {
        if (location.hash === $(ev.currentTarget).attr('href')) {
            // The controller only routes on hash changes
            window.ABS.spi.passagePanel.loadFromLocationHash();
        }
    }
});

ABS.View.TagListView = Backbone.View.extend({
    showOnly : [],
    shownTags : [],

    initialize : function() {
        var that = this;

        _.bindAll(this, 'refresh', 'render', 'bubble');
        this.collection.bind('refresh', this.refresh);
        this.collection.bind('add', this.refresh);
        this.collection.bind('remove', this.refresh);

        this.refresh();
    },

    render : function () {
        var that = this;
        $(this.el).empty();
        $(this.el).append(ABS.jst.Tagging_MyTags()); 
        var list = this.$('.scrollable .list');

        this.shownTags = [];
        _(this._tagViews).each(function(tv) {
            if (that.showOnly.length > 0) {
                if (_.include(that.showOnly, tv.model.id)) {
                    list.append(tv.render().el);
                    that.shownTags.push(tv.model);
                }
            }
            else {
                list.append(tv.render().el);
            }
        });

        // if we're doing special filtering and only showing one in our list,
        // skip to show the tag itself.
        if (this.shownTags.length === 1) {
            this.trigger('tag:click', _.first(this.shownTags));
        }
    },

    setShowOnly : function(showOnly) {
        this.showOnly = showOnly;
        this.refresh();
    },
    
    refresh : function () {
        this.updateTagViews(this.collection, this);
        this.render();
    },

    updateTagViews : function (tags, that) {
        that._tagViews = [];

        tags.each(function(tag) {
            var tagView = new ABS.View.TagView({
                model : tag,
                parentView : that.options.parentView
            });
            tagView.bind('all', that.bubble);
            that._tagViews.push(tagView);
        });
    },

    bubble : function() {
        this.trigger.apply(this, arguments);
    }
});

ABS.View.MainViewerView = Backbone.View.extend({
    tagName: 'div',
    className: 'main-content',
    panels: {},

    events: {
        "click .panel-switcher button" : "switcherClick",
        "click .search-results-nav a" : "searchResultsTabClick"
    },

    initialize: function() {
        _.bindAll(this, "render", "switcherClick", "searchResultsTabClick", "performSearch", "updateUrl", "showSearchResults", "showSearchMessage", "showSearchLoadingIndicators", "searchFromUrl", "hideAllLoadingIndicators", "updateCopyrightNoticeWidth");

        window.ABS.app.bind('route:search', this.searchFromUrl);
 
        this.render();
    },

    render: function() {
        var that = this;
        $(this.el).empty();
        $(this.el).append(ABS.jst.MainViewer());

        var passagePanel = new ABS.View.PassagePanel({
            collection : new ABS.Collection.ChapterList,
            id : 'passage-panel'
        });

        $(passagePanel.el).addClass('active');
        this.$('#panel-holder').append(passagePanel.render().el);
        this.panels['passage'] = passagePanel;

        window.ABS.spi.passagePanel = passagePanel;

        var preSearchResultsPanel = new ABS.View.SearchResultsMessagePanelView({
            type: "info",
            search:  ABS.common.i18n('none'),
            message: ABS.common.i18n('search_message') 
        });

        this.$('#panel-holder').append(preSearchResultsPanel.render().el);
        this.panels['search-results'] = preSearchResultsPanel;

        window.ABS.preSearchResultsPanel = preSearchResultsPanel;
    },

    switcherClick: function (e) {
        e.preventDefault();
        this.makePanelActive($(e.currentTarget).data('panel-id'));
        $(window).resize();
    },

    searchResultsTabClick: function(ev) {
        ev.preventDefault();
        var li = $(ev.currentTarget).closest('li');

        var tabId = li.data('id');

        // Clear all active tabs
        this.$('.search-results-nav.active').removeClass('active');
        this.$('.search-results-panel.active').removeClass('active');

        // Make clicked panel active
        this.$('#' + tabId + '-tab').addClass('active');
        this.$('#' + tabId + '-panel').addClass('active');

        $(window).resize();
    },

    makePanelActive: function(key, options) {
        // First clear all active panels
        this.$('.main-viewer-panel.active').removeClass('active');
        
        // hide any visible loading spinners from all panels
        this.hideAllLoadingIndicators();

        // Set the desired panel to active
        this.$('#' + key + '-panel').addClass('active');
    },

    searchFromUrl: function(query, versionList, deutList) {
        var params = {};
        versionList = versionList || '';
        if (typeof deutList !== "undefined") {
            if (deutList.toLowerCase() == "all") {
                // Exclude all
                params['testament'] = 'OT,NT';
            }
            else if (deutList.toLowerCase() == "none") {
                // Default behavior is include all DCs; do nothing
                null;
            }
            else {
                // A list of versions was given, exclude DCs from those versions
                deutArray = deutList.split(',');
                for(var i in deutArray) {
                    params[deutArray[i] + '_include_deut'] = 'false';            
                }
            }
        }

        window.ABS.spi.searchForm.setupSearch(query, versionList, deutList);
        this.performSearch(query, versionList, params);
    },

    performSearch: function(query, versionList, otherParams) {
        var that = this;
        otherParams || (otherParams = {});

        this.updateUrl(query, versionList, otherParams);
        this.showSearchLoadingIndicators(query);
        window.ABS.spi.searchForm.showSearchLoadingIndicators();

        var scriptureResults = new ABS.Model.SearchResults({
            query: query,
            versionList: versionList,
            data: otherParams
        });
        var scriptureFetch = scriptureResults.fetch().request.promise();

        var tag = new ABS.Model.TagSearchResults({
            name: query
        });
        var tagFetch = tag.fetch().request.promise();

        // Use a hand-written semaphor, because $.when() won't resolve the remaining
        // deferreds if any single one is rejected.
        var firstResponseReceived = false;

        scriptureFetch.done(function() {
            if (!firstResponseReceived) {
                firstResponseReceived = true;
                return;
            }
            that.showSearchResults({
                scripture: scriptureResults,
                tag: tag
            });
        });

        scriptureFetch.fail(function() {
            that.showSearchMessage("alert", query, ABS.common.i18n("search_general_error"));
        });

        tagFetch.done(function() {
            if (!firstResponseReceived) {
                firstResponseReceived = true;
                return;
            }
            that.showSearchResults({
                scripture: scriptureResults,
                tag: tag
            });
        });

        tagFetch.fail(function() {
            if (!firstResponseReceived) {
                firstResponseReceived = true;
                return;
            }
            that.showSearchResults({
                scripture: scriptureResults,
                tag: tag
            });
        });
    },

    // Update the URL after performing a search
    updateUrl: function(query, versionList, otherParams) {
        var versionParams = "";
        var deutParams = "";

        var versions = ABS.bootstrap.versions;
        var searchVersions = versionList.split(',');

        var excludes = [];
        _.each(otherParams, function(val, dc) {
            if (dc.match('_include_deut') && val == 'false') {
                excludes.push(dc.replace('_include_deut', ''));
            }
        });
        var deutList = excludes.join(',');

        var searchAllVersions = true;
        var excludeAllDeuts = true;

        var allDeuts = [];
        _.each(versions, function(ver) {
            if (!_.include(searchVersions, ver.id)) {
                searchAllVersions = false;
            }
            _.each(ver.sections, function(sect) {
                if (sect.id == 'DEUT') {
                    allDeuts.push(sect.version_id);
                }
            });
        });

        _.each(allDeuts, function(dc) {
            if (!_.include(excludes, dc)) {
                excludeAllDeuts = false;
            }
        });

        // Add versions to URL
        if (searchAllVersions) {
            if (deutList) {
                versionParams += '/all';
            }
        } else if (searchVersions) {
            versionParams += '/' + searchVersions;
        }

        if (deutList) {
            if (excludeAllDeuts) {
                deutParams += '/all';
            } else {
                deutParams += '/' + deutList;
            }
        }

        var searchParams = query + versionParams + deutParams;
        window.ABS.app.saveLocation('#search/' + searchParams);
    },

    showSearchResults: function(results) {
        // If we've already shown results before, remove the panel so it
        // can be replaced by the new results
        $('#search-results-panel').remove();

        var searchResultsPanel = new ABS.View.SearchResultsPanelView({
            id:  'search-results-panel',
            collection: new ABS.Collection.SearchResultsList(),
            results: results
        });

        this.$('#panel-holder').append(searchResultsPanel.render().el);
        this.panels['search-results'] = searchResultsPanel;

        window.ABS.searchResultsPanel = searchResultsPanel;
        this.makePanelActive('search-results');

        // Only show the Bibles Products tab if we're on the ABS branded site
        if ($('body').first().hasClass('bibleSearch')) {
            $('#products-search-results-tab').addClass('visible');
        }

        window.ABS.spi.searchForm.removeSearchLoadingIndicators();
        this.$('#panel-holder').addClass('has-search-results');
        $(window).resize();
        if (results.scripture.get('summary').total == 1) {
            if (results.scripture.get('type') == "passages") {
                var info = $('.search-result-reference').first().attr('href').replace('#', '').split('/'); 
                window.ABS.spi.passagePanel.load(info[0],info[1],info[2],info[3]);
            }
        }
        
    },

    showSearchMessage: function(type, search, message) {
        // Clears the contents of the "Search Results" tab
        $('#search-results-panel').remove();

        var searchResultsPanel = new ABS.View.SearchResultsMessagePanelView({
            type: type,
            search: search,
            message: message
        });

        this.$('#panel-holder').append(searchResultsPanel.render().el);
        this.panels['search-results'] = searchResultsPanel;
        this.makePanelActive('search-results');
        window.ABS.spi.searchForm.removeSearchLoadingIndicators();
        $(window).resize();
    },

    showSearchLoadingIndicators: function(searchQuery) {
        this.makePanelActive('search-results');
        $(window).resize();

        var resultsPanel = $(this.panels['search-results'].el);

        // Set the query and remove the pre search message, if it exists
        resultsPanel.find('.heading h2 a strong').text(searchQuery);
        resultsPanel.find('.scrollable .text-holder.search-message').remove();

        // Make the loading spinner visible
        this.$('#search-results-panel .loading-spinner-holder').css('display', 'block');
    },
    
    hideAllLoadingIndicators: function () {
        this.$('.loading-spinner-holder').hide();
    },

    updateCopyrightNoticeWidth: function() {
        // We put this at the mainViewer level so that we can call it
        // from the window.resize() function in ABS.biblesearch.js
        var that = this;
        $('.copyright-notice:visible').each(function(i, notice) {
            // There should be only one of these, but we put it in an each()
            // so the function doesn't get called if there are none visible on the page.
            var noticeDiv = $(notice);
            var textFullWidth = that.$('.text-holder:visible').first().outerWidth(true);
            var noticeWidth = textFullWidth - parseInt(noticeDiv.css('padding-left')) - parseInt(noticeDiv.css('padding-right')) + 1;

            noticeDiv.width(noticeWidth); 
        });
    }
});
 
ABS.View.PassagePanel = Backbone.View.extend({
    tagName : "div",
    className : "main-viewer-panel",
    id : "passage-panel",

    events: {
        'click .deselect-all a' : 'deselectAllVerses',
        'click a.version-selector'   : 'switchVersion',
        'click button.add-tag' : 'addTag',
        'click button.add-note' : 'addNote',
        'click button.fbShare' : 'facebookShare',
        'click button.twShare' : 'twitterShare',
        'click button.mailShare' : 'emailShare',
        'click button.urlShare' : 'urlShare',
        'click .change-version' : 'openCloseVersionSwitcher',
        'click .passage-overlay-content button' : 'closeOverlay',
        'click .circle-c a' : 'openCopyrightNotice',
        'click .copyright-notice div.close-button' : 'closeCopyrightNotice'
    },

    _chapterViews : {},

    _versions : {},

    initialize : function() {
        _.bindAll(this, 'render', 'rerender', 'load', 'chapterViewRender', 'switchVersion', 'updateVersionDropdown', 'selectVerse', 'deselectVerse', 'closeToolbox', 'addTag', 'addChapter', 'openCloseVersionSwitcher', 'versionSwitcherClickAway', 'closeOverlay', 'deselectAllVerses', 'openCopyrightNotice', 'closeCopyrightNotice');

        ABS.verseSelector.initialize({
            'parent' : this.el,
            passageSelector : '.text-holder',
            onSelect : this.selectVerse,
            onDeselect : this.deselectVerse
        });

        this._versions = new ABS.Collection.VersionList(ABS.bootstrap.versions);
        this._versions.bind('refresh', this.updateVersionDropdown);

        // pull out JSON data for page-load chapter and add to collection
        // this must happen before the 'add' event is bound, or rendering won't work right
        if ($('#main-viewer-area').data('chapter')) {
            this.collection.add($('#main-viewer-area').data('chapter'));
        }

        this.collection.bind('change', this.render);
        this.collection.bind('add', this.render);
        this.collection.bind('refresh', this.rerender);

        // make sure onScroll sees this as this View
        this.onScroll = _.bind(this.onScroll, this);
        // call method at most once every 200ms
        this.onScroll = _.throttle(this.onScroll, 200);

        window.ABS.app.bind('route:chapter', this.load);
    },

    closeOverlay : function(ev) {
        ev.preventDefault();
        this.$('.passage-overlay, .passage-overlay-container').hide();
    },

    updateVersionDropdown : function() {
        var selectorHTML = ABS.jst.PassageVersionSelector({
            versionsByLang: this._versions.sortedByLang,
            current:        this.collection.primaryModel.get('version')
        });

        this.$('div.change-version').empty().append(selectorHTML);
    },

    openCloseVersionSwitcher : function(ev) {
        this.$('.change-version').toggleClass('open');
        if (this.$('.change-version').is('.open')) {
            this.$('.change-version ul').scrollTo(
                this.$('.change-version a.current'), {offset: -31}
            );
            this.versionSwitcherClickAway();
        }
    },

    versionSwitcherClickAway : function() {
        $(document).bind('click', function(ev) {
            if (! $(ev.target).parents().hasClass('open')) {
                $('.change-version').removeClass('open');
            }
        });
    },
    
    updateHeader : function(model) {
        this.$('.switcher').empty().append(ABS.jst.PassageSwitcher({
            book:           model.get('book'),
            chapter:        model.get('chapter'),
            nextChapterId:  model.get('nextChapterId'),
            prevChapterId:  model.get('prevChapterId')
        }));
    },

    rerender : function(col, options) {
        options || (options = {});
        options.rerender = true;

        this.render(options);
    },

    render : function(options) {
        var that = this;
        options || (options = {});

        if (this.collection.length == 1 || options.rerender) {
            if (options.primaryId) {
                this.collection.primaryModel = this.collection.get(options.primaryId);
            }

            $(this.el).empty();
            var primaryModel = this.collection.primaryModel;
            this.el.innerHTML = ABS.jst.PassagePanel({
                version:        primaryModel.get('version')
            });
            this.updateHeader(primaryModel);
            this.updateVersionDropdown();

            // the scroll event does not bubble and not delegate-able
            // therefore it can't set in the events hash
            // need to re-bind scroll event on every re-render of scrollable
            this.bindScrollEvent();
        }

        this.collection.each(function(chapter) {
            // add any chapterViews that we don't already have
            if (!(chapter.cid in that._chapterViews)) {
                that._chapterViews[chapter.cid] = new ABS.View.ChapterView({
                    model : chapter
                });

                ABS.common.sa.logRefToServer(chapter.id);

                // bind to custom render event on ChapterView
                that._chapterViews[chapter.cid].bind('render', that.chapterViewRender);
                var newChapter = $(that._chapterViews[chapter.cid].render().el);
                var scrollable = that.$('.scrollable');
                // determine if we should prepend or append this chapter
                if (that.collection.indexOf(chapter) === 0 && that.collection.length > 1) {
                    // we need to determine how much to change scroll
                    // depending on height of this new prepended element.
                    var oldTop = scrollable.scrollTop();
                    scrollable.prepend(newChapter);
                    scrollable.scrollTop(oldTop + newChapter.outerHeight());
                }
                else {
                    scrollable.append(newChapter);
                }
            }
            _.defer(function () {
                ABS.verseSelector.reParseVerses('.text-holder');
                if (options.verse) {
                    that.openToolbox();
                    that.updateSelectedReferences();
                }
            });
        });
        $(window).resize();

        if (options.primaryId) {
            this.$('.scrollable').scrollTo(that._chapterViews[primaryModel.cid].el, 200);
        }

        this.toolbox = this.$('.selection-box');
        if (options.verse) {
            var model = this.collection.primaryModel;
            var verse = options.verse;
            this.scrollToVersesInChapter(model, verse);
            // Store the verse so that versions can keep changing
            // and the verse will keep re-highlighting/scrolling
            this.collection.primaryModel.attributes['verse'] = verse;
        }
        return this;
    },

    load : function(version, book, chapter, verse) {
        var that = this;
        var osisId = version + ":" + book + "." + chapter;
        $('meta[name=description]').attr('content', 'BibleSearch :: ' + book + ' ' + chapter + ':' + verse + ' [' + version + ']');

        if (this.collection.get(osisId)) {
            window.ABS.spi.mainViewer.makePanelActive('passage');
            var model = this.collection.get(osisId);
            this.collection.primaryModel = model;

            this.updateHeader(model);
            this.scrollToVersesInChapter(model, verse);

            // if we're jumping to the last chapter in our collection
            // try to load more for scrolling
            if (model === this.collection.last()) {
                ABS.ChapterQueue.add(model.get('nextChapterId'))
                    .done(function(newChapter) {
                        that.addChapter(newChapter);
                    });
            }
        }
        else {
            // Show the loading spinner
            this.$('.loading-spinner-holder').css('display', 'block');

            ABS.ChapterQueue.add(osisId)
                .done(function(newChapter) {
                    window.ABS.spi.mainViewer.makePanelActive('passage');
                    that.collection.refresh([], {silent: true});
                    that.addChapter(newChapter);
                    that.scrollToVersesInChapter(newChapter, verse);
                });
        }
    },

    loadFromLocationHash : function() {
        var id = ABS.common.osis.fromUrl(location.hash);
        var parts = ABS.common.osis.getComponents(id);

        this.load(parts.version, parts.book, parts.chapter, parts.verse);
    },

    // accepts chapter model, which should exist in the collection
    // and verses.  Finds location and scrolls to first verse.  If no verse,
    // scrolls to chapter.  Highlights all verses specified.
    scrollToVersesInChapter : function(chapter, verses) {
        var chapterEl = $(this._chapterViews[chapter.cid].el);
        var scrollToElem = null;
        if (verses) {
            var versesClasses = ABS.common.osis.toHtmlClasses(chapter.id + '.'+verses);
            _.each(versesClasses, function(className) {
                var verseElems = chapterEl.find('sup.' + className);
                if (verseElems.length > 0) {
                    if (scrollToElem === null) {
                        scrollToElem = verseElems.first();
                    }
                    chapterEl.find('.' + className).addClass('selected');
                }
            });
        }

        if (scrollToElem === null) {
            scrollToElem = chapterEl;
        }

        if (scrollToElem) {
            this.$('.scrollable').scrollTo(scrollToElem, 200);
        }
    },

    // adds chapter and siblings to our collection
    addChapter : function(chapter) {
        this.collection.addConsecutive([
            chapter,
            new ABS.Model.Chapter(chapter.get('nextChapter')),
            new ABS.Model.Chapter(chapter.get('prevChapter'))
        ]);
    },

    // find the chapter that is visible towards top of scrollable area
    findTopMostVisibleChapterView: function() {
        var scrollable, topMostView, padding;
        _.each(this._chapterViews, function(view, cid) {
            var el = $(view.el);
            if (!scrollable) {
                scrollable = el.parent();
            }
            // padding is roughly how much white space is between chapters
            if (!padding) {
                // 1.5 seems to be the right value for increasing this
                // a tad for a bit better usability
                padding = (el.outerHeight(true) - el.height()) * 1.5;
            }
            var heightTop = el.outerHeight() + el.position().top;
            var notAbove = heightTop > 0;
            var notBelow = el.position().top < scrollable.height();
            if (notAbove && notBelow) {
                if (!topMostView) {
                    topMostView = [view, heightTop];
                }
                // get top most view that's visible but not one
                // that only has its bottom whitespace visible
                else if (topMostView[1] > heightTop ||
                         topMostView[1] < padding) {
                    topMostView = [view, heightTop];
                }
            }
        });
        return topMostView[0];
    },

    onScroll : function(ev) {
        var scrollable = $(ev.currentTarget),
            innerDivs  = scrollable.children('.text-holder'),
            that       = this,
            numPixels  = 1000; // how close to top or bottom do we get more

        var innerDivsHeight = 0;
        innerDivs.each(function(i, div) {
            innerDivsHeight += $(div).innerHeight();
        });
        pixelsFromBottom = innerDivsHeight - (scrollable.height() + scrollable.scrollTop());

        if (pixelsFromBottom < numPixels) {
            ABS.ChapterQueue.add(this.collection.last().get('nextChapterId'))
                .done(function(newChapter) {
                    that.addChapter(newChapter);
                });
        }

        if (scrollable.scrollTop() < numPixels) {
            ABS.ChapterQueue.add(this.collection.first().get('prevChapterId'))
                .done(function(newChapter) {
                    that.addChapter(newChapter);
                });
        }
        // update header and URL as user scrolls
        var topMostView = this.findTopMostVisibleChapterView();
        this.updateHeader(topMostView.model);
        ABS.app.saveLocation(topMostView.model.get('version').id + '/' + topMostView.model.get('book').abbr + '/' + topMostView.model.get('chapter'));
        
    },

    bindScrollEvent : function () {
        this.$('.scrollable').scroll(this.onScroll);
    },

    chapterViewRender : function (chapterView) {
        this.collapseOverlappingIcons();
        _.defer(function() {
            ABS.verseSelector.reParseVerses('.' + chapterView.className);
        });
    },

    updateSelectedReferences : function() {
        this.$('.selected-references').text(
            this.collection.getReferencesFromSpans(
                ABS.verseSelector.getSelectedOsisSpans()
            ).join(', ')
        );
    },

    updateSelectedVerse : function() {
        var currentVerse = ABS.verseSelector.getSelectedVerseNumber();
        this.collection.primaryModel.attributes['verse'] = currentVerse;
    },

    selectVerse : function(ev) {
        this.openToolbox();
        this.updateSelectedReferences();
        this.updateSelectedVerse();
    },

    deselectVerse : function(ev) {
        if (ABS.verseSelector.getSelected().length === 0) {
            this.closeToolbox();
        }
        this.updateSelectedReferences();
    },

    deselectAllVerses : function(ev) {
        ev.preventDefault();
        ABS.verseSelector.deselectAll();
        this.updateSelectedReferences();
        this.closeToolbox();
    },

    openToolbox : function(ev) {
        var scrollable = this.$('.scrollable');
        var srElement  = this.toolbox;
        if (srElement.is(':visible')) { return; }
        var origScrollableHeight = scrollable.height();

        scrollable.animate({
            height: origScrollableHeight - (srElement.height() + 2)
        });
        srElement.slideDown();
    },

    closeToolbox : function(ev) {
        if (ev) {
            ev.preventDefault();
        }
        var scrollable = this.$('.scrollable');
        var srElement  = this.toolbox;
        $('#urlShareBox').hide();
        if (srElement.is(':visible')) {
            scrollable.animate({
                height: scrollable.height() + srElement.height()
            });
            srElement.slideUp();
        }
        else {
            srElement.hide();
        }       

        // TODO: if tagCreate or noteCreate panel are active when toolbox is closed, 
        // go back to welcome panel and deactivate nav tools icon.
        // (need to first add logic for checking active panel)
        // window.ABS.spi.leftSidebar.makePanelActive('welcome');
        // $('.tools-nav li.active, .actions li.active').removeClass('active');
    },

    addTag : function(ev) {
        ev.preventDefault();
        window.ABS.spi.leftSidebar.showTransferAnimation(ev.target);
        window.ABS.spi.leftSidebar.makePanelActive('tagCreate');
        $('.tools-nav li a.tags').closest('li').addClass('active');
    },

    addNote : function(ev) {
        ev.preventDefault();
        window.ABS.spi.leftSidebar.showTransferAnimation(ev.target);
        window.ABS.spi.leftSidebar.makePanelActive('noteCreate');
        $('.tools-nav li a.notebook').closest('li').addClass('active');
    },

    collapseOverlappingIcons : function() {
        var padding = 20, // give 20 pixels of padding b/w icons
            passageIcons = this.$('.passage-icon a'),
            height  = passageIcons.first().outerHeight();

        var offsets = $.map(passageIcons, function(icon) {
            return { 'top' : $(icon).offset().top, 'icon' : $(icon) };
        }).sort(function(a,b) { return a.top - b.top;});

        _.each(offsets, function(value, index) {
            while (index+1 < offsets.length && offsets[index+1].top - value.top < height + padding) {
                // remove overlapping element from array
                var overlapper = _.first(offsets.splice(index+1, 1));
                // merge overlapper's data with overlappee
                value.icon.data('tags', _.uniq(value.icon.data('tags').concat(overlapper.icon.data('tags'))));
                value.icon.data('notes', _.uniq(value.icon.data('notes').concat(overlapper.icon.data('notes'))));
                overlapper.icon.remove();
                if ((value.icon.data('tags').length > 1 && value.icon.data('notes').length === 0) || (value.icon.data('notes').length > 1 && value.icon.data('tags').length === 0)) {
                    value.icon.addClass('multi');
                }
                else if (value.icon.data('tags').length > 0 && value.icon.data('notes').length > 0) {
                    // we want a combo icons
                    value.icon.removeClass('multi');
                    value.icon.addClass('small');
                    var newIcon = value.icon.clone();
                    newIcon.attr('class', '');
                    if (value.icon.hasClass('tag')) {
                        newIcon.addClass('note');
                        newIcon.data('tags', []);
                        newIcon.data('notes', value.icon.data('notes'));
                        value.icon.data('notes', []);
                    }
                    else {
                        newIcon.addClass('tag');
                        newIcon.data('notes', []);
                        newIcon.data('tags', value.icon.data('tags'));
                        value.icon.data('tags', []);
                    }
                    newIcon.addClass('small').addClass('second');
                    value.icon.after(newIcon);
                }
            }
        });
    },
    
    switchVersion : function(ev) {
        var that = this;
        ev.preventDefault();
        
        var link = $(ev.currentTarget),
            chosenVer = link.data('version-id'),
            currentPrimary = that.collection.primaryModel;
        
        if (link.is('.current')) {
            return;
        }
        
        // Make the versions menu go away
        this.$('change-version').removeClass('open');

        // Make the loading spinner visible
        var spinner = this.$('.loading-spinner-holder').css('display', 'block');

        // Update the URL when switching versions
        var currentChapter = currentPrimary.attributes.chapter;
        var currentBook = currentPrimary.attributes.book.abbr;
        var newUrl = "#" + chosenVer + "/" + currentBook + "/" + currentChapter;
        window.ABS.app.saveLocation(newUrl);

        var newChapterIds = [];
        var newPrimaryModelId = "";
        this.collection.each(function(chapter){
            var parts = ABS.common.osis.getComponents(chapter.id);
            parts.version = chosenVer;
            var newId = parts.version + ":" + parts.book + "." + parts.chapter;
            newChapterIds.push(newId);

            if (that.collection.primaryModel === chapter) {
                newPrimaryModelId = newId;
            }
        });
        var newChapterList = newChapterIds.join(',');

        var newChapters = new ABS.Collection.ChapterList();
        newChapters.url = '/chapters.json?q=' + newChapterList;

        newChapters.fetch({
            success : function(chapters) {
                // The refresh() function needs an array, not a Backbone Collection
                var chapterArray = [];
                var foundIds = [];
                chapters.each(function(chapter){
                    // Make the version easier to access
                    chapter.version = chapter.get('version');
                    chapterArray.push(chapter);
                    foundIds.push(chapter.id);
                });

                var foundAll = _.intersect(newChapterIds, foundIds).length === newChapterIds.length;
                if (foundAll) {
                    that.collection.refresh(chapterArray, {
                        primaryId: newPrimaryModelId,
                        verse: currentPrimary.attributes.verse
                    });
                }
                else {
                    spinner.hide();
                    that.$('.passage-overlay, .passage-overlay-container').show();
                    window.ABS.spi.leftSidebar.makePanelActive('browse', {version: chosenVer});
                }
            }
        });
    },
    
    facebookShare: function(ev) {
        var verseText = ABS.verseSelector.getSelectedText();
        if (verseText.length > 0) {
            var ref = this.collection.getShortReferencesFromSpans(
                ABS.verseSelector.getSelectedOsisSpans()
            );
            var longRefs = this.collection.getReferencesFromSpans(
                ABS.verseSelector.getSelectedOsisSpans()
            );
            var version = this.collection.first().get('version').id;
            var reference = ' ' + ref.shift();
            var remains = 419 - reference.length;
            
            if (verseText.length > remains) {
                verseText = verseText.substr(0, remains) + '…';
            }
            var url = ABS.common.getShareUrl();
            verseText = verseText + reference;
            FB.ui({ method: 'feed', 
                    name: longRefs.join(', ') + ' (' + version + ')',
                    description: ' ',
                    message: verseText,
                    link: url
            });
        }
        return false;
    },
    
    twitterShare: function(ev) {
        var verseText = ABS.verseSelector.getSelectedText();
        if (verseText.length > 0) {
            var ref = this.collection.getShortReferencesFromSpans(
                ABS.verseSelector.getSelectedOsisSpans()
            );
            var reference = ' ' + ref.shift();
            var remains = 119 - reference.length;
            if (verseText.length > remains) {
                verseText = verseText.substr(0, remains) + '…';
            }
            verseText = '&text=' + verseText + reference;
        }
        var url = ABS.common.getShareUrl();
        
        var newwindow = 
            window.open('http://twitter.com/intent/tweet?url=' + url + verseText,
                        '',
                        'height=300,width=450');
        if (window.focus) {
            newwindow.focus();
        }
        return false;
    },
   
    urlShare: function(ev) {
        var url = ABS.common.getShareUrl();
        $('#urlShareUrl').val(url);
        $('#urlShareBox').toggle('fast', function() {
            $('#urlShareUrl').select();
            $('#urlShareBox .close-button').live('click', function() {
                $('#urlShareBox').hide();
            });  
        });
        return false;
    },
     
    emailShare: function(ev) {
        var verseText = ABS.verseSelector.getSelectedText();
        if (verseText.length > 0) {
            var ref = this.collection.getReferencesFromSpans(
                ABS.verseSelector.getSelectedOsisSpans()
            );
            var reference = ' ' + ref.join(', ');
            var link = window.location.href;
            verseText = verseText + ' - ' + reference;
        }
        var url = '/share/emailShareScripture/';
        
        var remoteFromAjax = $.ajax({
                url: url,
                data: 'js=1&reference='+reference+'&scripture='+verseText+'&url='+link,
                success: function(info){
                    $.fancybox(info);
                }
        });
    },

    openCopyrightNotice: function(ev) {
        ev.preventDefault();

        this.$('.circle-c').hide();
        this.$('.copyright-notice').show();
        window.ABS.spi.mainViewer.updateCopyrightNoticeWidth();
    },

    closeCopyrightNotice: function(ev) {
        ev.preventDefault();

        this.$('.copyright-notice').hide();
        this.$('.circle-c').show();
    }
});

ABS.View.SearchResultsMessagePanelView = Backbone.View.extend({
    tagName: 'div',
    className : "main-viewer-panel",
    id : "search-results-panel",

    initialize: function(options) {
        this.type = options.type;
        this.search = options.search;
        this.message = options.message;
    },

    render: function() {
        var that = this;
        $(this.el).empty();
        $(this.el).append(ABS.jst.SearchResultsMessagePanel({
            messagetype: this.type,
            search: this.search,
            message: this.message
        }));
        return this;
    }
});
 
// FIXME: the nested templates used in this View should be split into
// separate views, so that we can more easily re-render portions of the view
// and move functionality to the proper view.
ABS.View.SearchResultsPanelView = Backbone.View.extend({
    tagName: 'div',
    className:  'main-viewer-panel',
    id: 'search-results-panel',
    events : {
        'click a.more-versions' : 'moreVersionsClick',
        'click a.search-result-reference' : 'referenceClick',
        'click div.expanded-verses a' : 'referenceClick',
        'click .alert-message a.cancel' : 'closeAlertMessage',
        'click a.expand' : 'expandClick',
        'click .relevant li a' : 'relevancyClick',
        'change .sort-options' : 'alterSort',
        'click .circle-c a' : 'openCopyrightNotice',
        'click .copyright-notice div.close-button' : 'closeCopyrightNotice'
    },
    totalScriptureResults: 0,
    runningScriptureRequest: -1,
    results: {},

    initialize: function(options) {
        this.results = options.results;
        _.bindAll(this, "render", "bindScrollEvent", "onScroll", "fetchAndAddNext", 'relevancyClick', 'alterSort', 'openCopyrightNotice', 'closeCopyrightNotice');

        this.collection.add(options.results.scripture);

        // Log the scripture and tag results to the ABS Scripture Analytics server
        var scriptureIDs = [];

        _.each(options.results.scripture.get('passages'), function(passage) {
            if (passage.textstart) {
                scriptureIDs.push(passage.textstart + "-" + passage.textend);
            }
            else {
                scriptureIDs.push(passage.id);
            }
        });

        _.each(options.results.tag.get('references'), function(ref) {
            scriptureIDs.push(ref.osis_id);
        });

        ABS.common.sa.logRefArrayToServer(scriptureIDs);

        this.totalScriptureResults = options.results.scripture.get('summary').total;
        this.runningScriptureRequest = -1;
       
        // make sure onScroll sees this as this View
        this.onScroll = _.bind(this.onScroll, this);
        // call method at most once every 200ms
        this.onScroll = _.throttle(this.onScroll, 200);
    },

    render: function() {
        var scriptureResults = this.results.scripture;
        var votes = scriptureResults.get('votes');
        var summary = scriptureResults.get('summary');
        var passages = scriptureResults.get('passages');
        var versionList = '';

        var moreVersions = 0;
        var copyrightText = {};
        var versionCollection = new ABS.Collection.VersionList(ABS.bootstrap.versions);
        if (summary.versions) {
            // only show first 4 versions
            versionList  = _.first(_.pluck(summary.versions, 'version'), 4).join(', ');
            moreVersions = summary.versions.length - 4;
            _.each(_.first(_.pluck(summary.versions, 'version'), 4), function(ver) {
                copyrightText[ver] = versionCollection._byId[ver];
            });
        }
        var numPassages  = summary.total;
        var errorList = '';
        if (summary.errors) {
            errorList = summary.errors;
        }

        _.each(errorList, function(error) {
            if (error.versions) {
                // Format the list of version abbreviations for display
                error.versionAbbrList = error.versions.replace(/,/g, ', ');

                // Format the list of versions for display, with the full version names
                var fullVers = [];
                var vers = error.versions.split(',');
                _.each(vers, function(ver) {
                    fullVers.push('(' + ver + ') ' + versionCollection._byId[ver].get('name'));
                });
                error.versions = fullVers.join(', ');
            }
        });

        var numTagRefs = 0;
        var totalResults = numPassages;
        var tagRefs = [];

        if (this.results.tag && this.results.tag.id) {
            var tagResults = this.results.tag;
            tagRefs = tagResults.get('references');
            numTagRefs = tagRefs.length;
            totalResults = totalResults + numTagRefs;
        }

        $(this.el).empty();
        $(this.el).append(ABS.jst.SearchResultsPanel({
            query:          summary.query,
            versions:       summary.versions,
            versionList:    versionList,
            moreVersions:   moreVersions,
            passages:       passages,
            tagRefs:        tagRefs,
            numResults:     totalResults,
            numVerses:      numPassages,
            numTagRefs:     numTagRefs,
            copyrightText:  copyrightText,
            errorList:      errorList,
            votes:          votes,
            sortOrder:      scriptureResults.get('data').sort_order,
            showSortBy:     scriptureResults.get('type') == 'verses',
            showExpand:     scriptureResults.get('type') == 'verses',
            showRelevanceButtons:   (scriptureResults.get('type') == 'verses' && (!scriptureResults.get('data').sort_order || scriptureResults.get('data').sort_order == 'relevance'))
        }));

        this.bindScrollEvent();

        return this;
    },

    alterSort : function(ev) {
        ev.preventDefault();
        var scripture = this.results.scripture;
        var data = scripture.get('data');
        if ($(ev.currentTarget).children(':selected').hasClass('sort-by-book-order')) {
            data.sort_order = 'canonical';
        }
        else {
            data.sort_order = 'relevance';
        }
        window.ABS.spi.mainViewer.performSearch(scripture.get('query'), scripture.get('versionList'), data);
    },

    moreVersionsClick : function(ev) {
        ev.preventDefault();
        $('a.open-close').click();
    },

    referenceClick : function(ev) {
        window.ABS.spi.mainViewer.showSearchLoadingIndicators();
        if (location.hash === $(ev.currentTarget).attr('href')) {
            // The controller only routes on hash changes
            window.ABS.spi.passagePanel.loadFromLocationHash();
        }
    },
    
    relevancyClick : function(ev) {
        ev.preventDefault();
        var link = $(ev.target);
        var li   = link.closest('li');
        var ul   = link.closest('ul');
        var existingVote = new ABS.Model.RelevancyVote();
        var newVoteVal = (link.hasClass('no')) ? 0 : 1;
        existingVote.set(ul.data('vote'));

        // deleting an existing vote
        if (parseInt(existingVote.get('vote'), 10) === 1 && link.hasClass('yes') || (parseInt(existingVote.get('vote'), 10) === 0 && link.hasClass('no'))) {
            existingVote.destroy();
            li.removeClass('checked');
            ul.data('vote', '');
        }
        // update an existing vote
        else if (existingVote.id) {
            existingVote.set({vote: newVoteVal});
            existingVote.save();
            ul.find('li.checked').removeClass('checked');
            li.addClass('checked');
            ul.data('vote', existingVote.toJSON());
        }
        // create a new vote
        else {
            var osisParts = ABS.common.osis.getComponents(ul.data('osis-id'));
            var newVote = new ABS.Model.RelevancyVote({
                version : osisParts.version,
                book    : osisParts.book,
                chapter : osisParts.chapter,
                verse   : osisParts.verse,
                query   : ul.data('query'),
                vote    : newVoteVal
            });
            li.addClass('checked');
            newVote.save(null, {
                success : function () {
                    ul.data('vote', newVote.toJSON());
                }
            });
        }
    },
    
    expandClick : function (ev) {
        ev.preventDefault();
        var $link         = $(ev.currentTarget),
            $parent       = $link.closest('li'),
            $expand       = $parent.find('.expanded-verses'),
            textClosed    = $link.data('text-closed'),
            textOpen      = $link.data('text-open'),
            textExpanding = $link.data('text-expanding');
            
        // if we're currently expanding (perhaps a double-click?) don't fire 
        // off another request
        if ($link.data('expanding')) {
            return;
        }
        
        // if we have loaded data already, don't bother re-loading
        if ($expand.length) {
            toggle();
        }
        
        // the expand div doesn't exist, so we need to load the data, populate 
        // it, and then show
        else {
            $link.data('expanding', true).text(textExpanding);
            new ABS.Model.ExpandSearch({
                keyword: this.results.scripture.query,
                passage: $(ev.currentTarget).data('osis-id'),
                versions: _.pluck(
                    this.results.scripture.get('summary').versions, 'version'
                )
            }).bind('change', function (model) {
                reset();
                $expand = $parent.append(
                    ABS.jst.SearchResultExpandedVerses({expand: model})
                );
                logVersesToSAServer(model.get('verses'));
                toggle(true);
            }).fetch({ error: reset });
        }
        
        function reset() {
            $link.data('expanding', false).text(textClosed);
        }
        
        function toggle(which) {
            // straight toggle
            if (which === null) {
                $parent.toggleClass('expanded', !$expand.is(':visible'));
                $expand.toggle();
            }
            
            // specific show/hide
            else {
                $parent.toggleClass('expanded', which);
                $expand.toggle(which);
            }
            
            // set the text
            $link.text($expand.is(':visible') ? textOpen : textClosed);
        }

        function logVersesToSAServer(verses) {
            var ids = [];
            verses.each(function (verse) {
                if (verse.id == verse.get('osis_end')) {
                    ids.push(verse.id);
                }
                else {
                    ids.push(verse.id + "-" + verse.get('osis_end'));
                }
            });
            ABS.common.sa.logRefArrayToServer(ids);
        }
    },

    closeAlertMessage : function(ev) {
        ev.preventDefault();

        $(ev.currentTarget).parent().hide(0, function() {
            $(window).resize();
            // Workaround for IE7
            // The resize() alone isn't enough to get IE7 to re-layout the
            // panel, but for some reason makePanelActive() does it.
            window.ABS.spi.mainViewer.makePanelActive('search-results');
        });
    },

    bindScrollEvent : function () {
        this.$('.search-results-panel.scrollable').scroll(this.onScroll);
    },

    onScroll : function(ev) {
        var scrollable = $(ev.currentTarget),
            innerDivs  = scrollable.children('.text-holder'),
            that       = this,
            numPixels  = 1000; // how close to the bottom do we get more

        var innerDivsHeight = 0;
        innerDivs.each(function(i, div) {
            innerDivsHeight += $(div).innerHeight();
        });
        pixelsFromBottom = innerDivsHeight - (scrollable.height() + scrollable.scrollTop());

        if (pixelsFromBottom < numPixels) {
            this.fetchAndAddNext(this.collection.last());
        }
    },

    fetchAndAddNext : function(currentLast) {
        var that = this;
        var resultsPerFetch = 15;
        var nextOffset = currentLast.offset + resultsPerFetch;

        // Only fetch new results if we haven't already displayed all
        // of them and the request for the next resultset isn't already running
        if (nextOffset <= this.totalScriptureResults &&
            this.runningScriptureRequest != nextOffset) {

            this.runningScriptureRequest = nextOffset;

            var newResults = new ABS.Model.SearchResults({
                query:          currentLast.query,
                versionList:    currentLast.versionList,
                offset:         nextOffset,
                data:           currentLast.data
            });

            newResults.fetch({
                success : function (results) {
                    var newView = new ABS.View.SearchResultSetView({
                        model:  results
                    });

                    var scriptureIDs = [];
                    _.each(results.get('passages'), function(passage) {
                        scriptureIDs.push(passage.id);
                    });
                    ABS.common.sa.logRefArrayToServer(scriptureIDs);

                    that.$('#scripture-search-results-panel').append(newView.render().el);
                    that.collection.add(results);
                    that.runningScriptureRequest = -1;
                }
            });
        }
    },

    openCopyrightNotice: function(ev) {
        ev.preventDefault();

        this.$('.circle-c').hide();
        this.$('.copyright-notice').show();
        window.ABS.spi.mainViewer.updateCopyrightNoticeWidth();
    },

    closeCopyrightNotice: function(ev) {
        ev.preventDefault();
        this.$('.copyright-notice').hide();
        this.$('.circle-c').show();
    }
});

ABS.View.ChapterView = Backbone.View.extend({
    tagName: 'div',
    className: 'text-holder',
    events : {
        'click .passage-icon a' : 'clickPassageIcon'
    },
    myTags:  null,
    myNotes: null,

    initialize: function() {
        this.myTags  = window.ABS.spi.myTags;
        this.myNotes = window.ABS.spi.myNotes;
        _.bindAll(this, "render");

        this.myTags.bind('refresh', this.render);
        this.myTags.bind('add', this.render);
        this.myTags.bind('change', this.render);

        this.myNotes.bind('refresh', this.render);
        this.myNotes.bind('add', this.render);
        this.myNotes.bind('change', this.render);
    },

    clickPassageIcon : function(ev) {
        ev.preventDefault();
        if ($(ev.currentTarget).hasClass('tag')) {
            var tags = $(ev.currentTarget).data('tags');
            if (tags.length === 1) {
                window.ABS.spi.leftSidebar.makePanelActive('tag', {showTag: window.ABS.spi.myTags.get(_.first(tags))});
            }
            else {
                window.ABS.spi.leftSidebar.makePanelActive('tag', {showOnly: tags});
            }
        }
        if ($(ev.currentTarget).hasClass('note')) {
            var notes = $(ev.currentTarget).data('notes');
            if (notes.length === 1) {
                window.ABS.spi.leftSidebar.makePanelActive('noteSubset', {showNote: ABS.spi.myNotes.get(_.first(notes))});
            }
            else {
                window.ABS.spi.leftSidebar.makePanelActive('noteSubset', {showOnly: notes});
            }
        }
    },


    addPassageIcons : function(collection, type) {
        var that = this;
        _.each(collection, function(model, ref) {
            var refComps = ABS.common.osis.getComponents(ref);
            // FIXME: this class finding is janky using book ord
            // FIXME: will not find if verse is within span for a version
            var verseClass = ABS.common.osis.toHtmlClass(ref);
            var modelIds = _.map(model, function(aModel) {
                return aModel.id;
            });

            var options = {
                type  : type
            };
            options[type+'s'] = modelIds;
            options.multiClass = (modelIds.length > 1) ? 'multi' : '';
            var icon = $(ABS.jst.PassageIcon(options));
            // use jQuery to attach data attributes so that we get proper
            // escaping for free
            icon.find('a').attr('data-'+type+'s', JSON.stringify(modelIds));
            that.$('sup.'+verseClass).before(icon);

        });
    },

    render: function() {
        var that = this;
        $(this.el).empty();
        // add version ID as class to this.el for per version CSS
        $(this.el).addClass(
            ABS.common.osis.getComponents(this.model.id).version
        );
        $(this.el).append(ABS.jst.ChapterView({
            text    : this.model.get('text'),
            chapter : this.model.get('chapter')
        }));
        var tags = this.myTags.getByChapter(this.model.id);
        var notes = this.myNotes.getByChapter(this.model.id);
        this.addPassageIcons(tags, 'tag');
        this.addPassageIcons(notes, 'note');
        // parent view is bound to this event for icon collapsing
        this.trigger('render', that);
        return this;
    }

});

ABS.View.SearchResultSetView = Backbone.View.extend({
    tagName: 'div',
    className: 'text-holder',

    initialize: function() {
        _.bindAll(this, "render");
    },

    render: function() {
        $(this.el).empty();
        $(this.el).append(ABS.jst.SearchResultSetView({
            passages : this.model.get('passages'),
            query: this.model.get('summary').query,
            votes: this.model.get('votes'),
            versions: this.model.get('summary').versions,
            showExpand: this.model.get('type') == 'verses',
            showRelevanceButtons: this.model.get('type') == 'verses'
        }));

        return this;
     }
});

ABS.View.ContactUsView = ABS.View.SidebarPanel.extend({
        tagName: 'div',
        
        initialize: function() {
            ABS.View.SidebarPanel.prototype.initialize.call(this);
            _.bindAll(this, "render");
        },

        render: function() {
            var that = this;
            $(this.el).empty();
            var zenform = this.el.innerHTML = ABS.jst.SidebarContactUs();
            return this;
        }
});

ABS.View.WelcomeView = ABS.View.SidebarPanel.extend({

        events: {
            "click #sidebar-register-button" : "showRegisterForm"
        },
        
        initialize: function() {
            ABS.View.SidebarPanel.prototype.initialize.call(this);
            _.bindAll(this, "render");
        },

        render: function() {
            var that = this;

            // TODO: construct a URI for the daily verse
            // so we can link up the reference in the Welcome Panel
            var verseData = ABS.bootstrap.daily_verse;
            var scripture = verseData["scripture"];
            var reference = verseData["scriptureref"];

            $(this.el).empty();
            $(this.el).html(ABS.jst.SidebarWelcome({
                flashError : this.flashError,
                dailyVerseScripture : scripture,
                dailyVerseReference : reference
            }));

            this.flashError = null;

            // Show the Login form if the user isn't logged in
            if (!window.ABS.bootstrap.loggedin) {
                this.$('.biblesearch-preview').addClass('hidden');
                this.$('.login').removeClass('hidden');
            }

            this.$('form').ajaxForm({
                success : function (response) {
                    var txt   = ABS.util.extractResponse(response, 'div.sw');
                    // if the result contains a form, the login form has been 
                    // redisplayed.
                    if (txt.find('#login_form').length) {
                        that.$('#login-error-message')
                            .removeClass('hidden');
                        that.$('#username').focus();
                    }
                    
                    // otherwise, we've logged in so reload current page
                    else {
                        window.location.reload();
                    }
                }
            });
            return this;
        },

        nowVisible : function(options) {
            if (options && options.flashError) {
                this.flashError = options.flashError;
                this.render();
            }
            this.$('#username').focus();
        },
            
        showRegisterForm: function(ev) {
            ev.preventDefault();
            window.ABS.spi.leftSidebar.makePanelActive('register');
            this.$('#first_name').focus();
        }
});

ABS.View.SidebarLoginView = ABS.View.SidebarPanel.extend({
        flashError : null,
        
        events: {
            "click #sidebar-register-button" : "showRegisterForm"
        },
        
        initialize: function() {
            ABS.View.SidebarPanel.prototype.initialize.call(this);
            _.bindAll(this, "render", 'nowVisible');
            this.bind('panel:activate', this.nowVisible);
        },

        render: function() {
            var that = this;
            var loginForm = $(this.el).html(ABS.jst.SidebarLogin({
                flashError : this.flashError
            }));

            this.flashError = null;

            this.$('form').ajaxForm({
                success : function (response) {
                    var txt   = ABS.util.extractResponse(response, 'div.sw');
                    // if the result contains a form, the login form has been 
                    // redisplayed.
                    if (txt.find('#login_form').length) {
                        that.$('#login-error-message')
                            .removeClass('hidden');
                        that.$('#username').focus();
                    }
                    
                    // otherwise, we've logged in so reload current page
                    else {
                        window.location.reload();
                    }
                }
            });
            return this;
        },

        nowVisible : function(options) {
            if (options && options.flashError) {
                this.flashError = options.flashError;
                this.render();
            }
            this.$('#username').focus();
        },
            
        showRegisterForm: function(ev) {
            ev.preventDefault();
            window.ABS.spi.leftSidebar.makePanelActive('register');
            this.$('#first_name').focus();
        }
});

ABS.View.SidebarVerifiedView = ABS.View.SidebarPanel.extend({
        initialize: function() {
            ABS.View.SidebarPanel.prototype.initialize.call(this);
            _.bindAll(this, "render");
        },

        render: function() {
            var that = this;
            $(this.el).empty();
            $(this.el).append(ABS.jst.SidebarVerified());
            return this;
        }
});
ABS.View.SidebarNotVerifiedView = ABS.View.SidebarPanel.extend({
        initialize: function() {
            ABS.View.SidebarPanel.prototype.initialize.call(this);
            _.bindAll(this, "render");
        },

        render: function() {
            var that = this;
            $(this.el).empty();
            $(this.el).append(ABS.jst.SidebarNotVerified());
            return this;
        }
});
ABS.View.SidebarRegisterView = ABS.View.SidebarPanel.extend({
        
        initialize: function() {
            ABS.View.SidebarPanel.prototype.initialize.call(this);
            _.bindAll(this, "render");
        },

        render: function() {
            var that = this;
            $(this.el).empty();
            var registerForm = $(this.el).append(ABS.jst.SidebarRegister());

            this.$('form').ajaxForm({
                success : function (response) {
                    $('#btn-register-loading').hide();
                    $('.btn-register').show();
                    
                    var txt   = ABS.util.extractResponse(response, 'div.sw');
                    // if the result contains a form, the reg form has been 
                    // redisplayed.
                    if (txt.find('#first_name').length) {
                        $('.hidden').each(function(index) {
                            var err = $(this).attr('id');
                            var errid = '#' + err;
                            if (errid !== '#' && txt.find(errid).length) {
                                $(errid).removeClass('hidden');
                            }
                        });
                        that.$('#first_name').focus();
                    }
                    
                    // otherwise, we've logged in so load up the congrats.
                    else {
                        $('.register-form').html(txt);
                    }
                },
                beforeSubmit : function() {
                    $('.btn-register').hide();
                    $('#btn-register-loading').show();
                }
            });
            

            return this;
        }
});

ABS.View.SearchFormView = Backbone.View.extend({
    tagName: 'div',
    className: 'search-form-holder',
 
    events: {
        "click .version-select" : "versionAllClick",
        "click .lang-checkbox" : "selectVersionsByLang",
        "click .version-checkbox" : "updateVersionsDisplay",
        "click #save-my-versions" : "saveMyVersions"
    },

    initialize: function() {
        that = this;
        _.bindAll(this, "render", "selectVersionsByLang", "versionAllClick", "showSearchLoadingIndicators", "updateDesc", "setupSearch", "updateVersionsDisplay", "checkMyVersions", "saveMyVersions", "applyMyVersionsPref", "applyIncludeDeutsPref", "updateVersionCheckboxes");

        var versionsJson = ABS.bootstrap.versions;
        this._versions = new ABS.Collection.VersionList(versionsJson);

        this.render();

        this.$('form.search-form').submit(function(ev) {
            ev.preventDefault(); 
           
            var searchForm = $(ev.currentTarget);
            var searchQuery = searchForm.find('input[name="query"]').val();
            var deutsCheckbox = searchForm.find('input#include-deuts:checkbox');

            var allDeuts = [];

            _.each(versionsJson, function(ver) {
                _.each(ver.sections, function(sect) {
                    if (sect.id == 'DEUT') {
                        allDeuts.push(sect.version_id);
                    }
                });
            });

            var versions = [];
            var deuts = {};
            searchForm.find('input.version-checkbox:checkbox:checked').each(function(index) {
                var osisId = $(this).attr('id');
                versions.push(osisId);

                if (_.include(allDeuts, osisId)) {
                    var deutKey = osisId + '_include_deut';

                    if (!deutsCheckbox.is(':checked')) {
                      deuts[deutKey] = 'false';
                    }
                }
            });
            var versionList = versions.join(',');

            window.ABS.spi.mainViewer.performSearch(searchQuery, versionList, deuts);
        });
    },

    render: function() {
        var that = this;
        this.versionsByLang = this._versions.sortedByLang;

        $(this.el).empty();
        $(this.el).append(ABS.jst.SearchForm({
            versionsByLang:     this.versionsByLang,
            currentTabIndex:    13
        }));

        this.applyMyVersionsPref();
        this.applyIncludeDeutsPref();
        this.updateVersionsDisplay();

        // setup version list for zebra striping
        this.$('.lang-group:odd').addClass('odd');        
    },

    showSearchLoadingIndicators: function() {
        // If the versions section is open, close it
        if (this.$('div.versions').first().hasClass('active')) {
            this.$('.slide-block a.open-close').first().click();
        }

        // Disable the search button
        this.$('.btn-search').first().addClass('grayed-out').attr('disabled', true);
    },

    removeSearchLoadingIndicators: function() {
        this.$('.btn-search').removeClass('grayed-out').removeAttr('disabled');
    },

    setupSearch: function(query, versions, deuts) {
        var that = this;
        this.$('input[name="query"]').val(query);

        if (typeof versions === "undefined" || versions.toLowerCase() == "all") {
            // Default to all versions, which we represent by deselecting all
            this.$('input.version-checkbox:checkbox').each(function(index){
                $(this).attr('checked', false);
            });
        } else {
            var verArray = versions.split(',');
            this.$('input.version-checkbox:checkbox').each(function(index){
                var inVersionList = _.include(verArray, $(this).attr('id'));
                $(this).attr('checked', inVersionList);
            });
        }
        this.updateVersionsDisplay();

        if (typeof deuts === "undefined" || deuts === "") {
            // Default to including all DC books
            this.$('input#include-deuts:checkbox').attr('checked', 'checked');
        } else {
            var deutArray = deuts.split(',');
            this.$('input#include-deuts:checkbox').each(function(index) {
                if (deuts.toLowerCase() == "all") {
                    // Exclude all DC books
                    $(this).attr('checked', false);
                } else if (deuts.toLowerCase() == "none") {
                    // Include all DC books
                    $(this).attr('checked', 'checked');
                }
            });
        }
    },

    selectVersionsByLang: function(ev) {
        var that = this;
        var selected = $(ev.currentTarget);

        _.each(selected, function() {
            var langParent = selected.attr('id');
            var langChild = this.$("#" + langParent + "-check-wrapper .version-checkbox:checkbox");

            if (selected.attr('checked')) {
                langChild.attr('checked', true);
            } else {
                langChild.removeAttr('checked');
            }

            that.updateVersionsDisplay();
        });
    },

    versionAllClick: function(ev) {
        var buttonId = $(ev.currentTarget).attr('id');

        switch(buttonId)
        {
            case "all":
                ev.preventDefault();
                this.$(".lang input:checkbox").attr('checked', true);
                this.$("ul.include-deuts input:checkbox").removeAttr('disabled');
                this.$(".version-select:checkbox").attr('checked', false);
                this.updateDesc("all");
                break;
            case "none":
                ev.preventDefault();
                this.$(".lang input:checkbox").attr('checked', false);
                this.$("ul.include-deuts input:checkbox").attr('disabled', 'disabled');
                this.$(".version-select:checkbox").attr('checked', false);
                this.updateDesc("all");
                break;
            case "my":
                if ($(ev.currentTarget).attr('checked')) {
                    this.$(".lang input:checkbox").attr('checked', false);
                    this.checkMyVersions();
                    this.updateDesc("selected");
                }
                break;
            default:
                break;
        }
    },

    checkMyVersions: function() {
        var that = this;
        var myVersions = window.ABS.spi.myVersions;
        if (myVersions.length > 0) {
            var myVersionIDs = myVersions.pluck("version");

            this.$('input.version-checkbox').each(function(index) {
                var inMyVersions = _.include(myVersionIDs, this.id);
                $('#save-my-versions').hide();
                $(this).attr('checked', inMyVersions);
            });
        }
    },

    saveMyVersions: function(ev) {
        ev.preventDefault();

        $('#save-my-versions').hide(); 
        $('#my-versions-saving-message').show();
        if (this.versions === undefined) {
            this.versions = new ABS.Collection.VersionList(ABS.bootstrap.versions);
        }

        var that = this;
        var allBoxes = this.$("input.version-checkbox:checkbox").filter(":checked");
        
        // Clear the myVersions collection
        window.ABS.spi.myVersions.refresh();
 
        // Add all of the checked versions to the myVersions collection
        allBoxes.each(function(index, elem) {
            var boxID = $(elem).attr("id");
            var checkedVersion = that.versions.find(function(version) {
                return (version.get("version") === boxID);
            });

            window.ABS.spi.myVersions.add(checkedVersion);
        });

        window.ABS.spi.myVersions.save(function() {
            $('#my').attr('checked', 'checked');
            $('#my-versions-saving-message').hide();
            $('#my-versions-save-message').show().delay(2000).fadeOut();
        });
    },

    applyMyVersionsPref: function() {
        if (ABS.bootstrap.loggedin) {
            if (window.ABS.spi.myVersions.length > 0) {
                this.$('input[name="my-versions"]:checkbox').attr('checked', true);
                this.checkMyVersions();
                this.$('#save-my-versions').hide();
                this.updateDesc("selected");
            }
            else {
                this.$(".lang input:checkbox").attr('checked', false);
                this.$('#save-my-versions').show();
            }
        }
    },

    applyIncludeDeutsPref: function() {
        if (ABS.bootstrap.loggedin) {
            if (ABS.bootstrap.include_deuts) {
                this.$("input#include-deuts:checkbox").attr('checked', 'checked');
            }
            else {
                this.$("input#include-deuts:checkbox").removeAttr('checked');
            }
        }
    },

    updateVersionsDisplay: function(checkbox) {
        var allBoxes = this.$("input.version-checkbox:checkbox");
        var checkedBoxes = allBoxes.filter(':checked');

        this.updateVersionCheckboxes(allBoxes, checkedBoxes);

        if (allBoxes.length === checkedBoxes.length) {
            // All the boxes are checked
            this.updateDesc("all");
        }
        else {
            if (checkedBoxes.length === 0) {
                // No boxes are checked, so all are searched
                this.updateDesc("all");
            }
            else {
                // Display a list of versions that are checked
                this.updateDesc("selected");
            }
         }
    },

    updateVersionCheckboxes: function(allBoxes, checkedBoxes) {
        var checkedVersionsByLang = {};
        var checkedVersions = [];

        checkedBoxes.each(function(index, domEl) {
            var checkbox = $(domEl);
            var ver = checkbox.attr('id');
            var lang = checkbox.data('lang');

            if (!checkedVersionsByLang[lang]) {
                checkedVersionsByLang[lang] = [];
            } 

            checkedVersionsByLang[lang].push(ver);
            checkedVersions.push(ver);
        });

        // Check or uncheck the language header boxes
        _.each(this.versionsByLang, function(allVersions, lang) {
            if ((checkedVersionsByLang[lang] !== undefined) && 
                (allVersions.length === checkedVersionsByLang[lang].length)) {
                $('#' + lang).attr('checked', 'checked');
            } else {
                $('#' + lang).removeAttr('checked');
            }
        });

        // Check or uncheck the My Versions box
        var myVersions = window.ABS.spi.myVersions;
        if (myVersions.length > 0) {
            var myVersionIDs = myVersions.pluck("version");
            if ((_.intersect(myVersionIDs, checkedVersions).length === checkedVersions.length) &&
                (myVersionIDs.length === checkedVersions.length)) {
                
                // All of the my_versions boxes are checked and no others
                $('#my').attr('checked', 'checked');
                $('#save-my-versions').hide();
            } else {
                $('#my').removeAttr('checked');
                $('#save-my-versions').show();
            }
        }
    },

    updateDesc: function(selected) {
        var titleLink = $("div.versions div.title .description");
        if (selected == "all") {
            titleLink.text(ABS.common.i18n('all_versions'));
        }
        else if (selected == "eng"){
            titleLink.text(ABS.common.i18n('all_english_versions'));
        }
        else if (selected == "selected"){
            var checkedBoxes = this.$("input.version-checkbox:checked");

            // Display a list of versions that are checked
            var versions = [];
            var moreVersions = 0;
            var maxVersionsListed = 4;

            checkedBoxes.each(function(index){
                if (versions.length >= maxVersionsListed) {
                    moreVersions++;
                } else {
                    versions.push($(this).attr('name'));
                }
            });
            var versionList = versions.join(', ');

            if (moreVersions > 0) {
                versionList = versionList + " + " + moreVersions + " more";
            }

            titleLink.text(versionList);
        }
        else {
            titleLink.text(selected);
        }
    }
});

ABS.View.DeveloperView = ABS.View.SidebarPanel.extend({
    
    tagName: 'div',
    
    initialize: function() {
        ABS.View.SidebarPanel.prototype.initialize.call(this);
        this.collection = new ABS.Collection.DeveloperTools();
        this.selection = null;
        this.bind('panel:iconclick', this.iconClick);
        _.bindAll(this, 'render', 'iconClick', 'reset');
    },

    render: function() {
        ABS.View.SidebarPanel.prototype.render.call(this);
        this.$('.divsection').first()
            .addClass('list-holder').html(ABS.jst.Developer({
                collection: this.collection,
                selection: this.selection
            }));

        $(window).resize();
        return this;
    },

    selectChoice: function (ev) {
        ev.preventDefault();
        this.selection = this.collection.get($(ev.currentTarget).data('id'));
        this.render();
    },
    
    deselectChoice: function (ev) {
        ev.preventDefault();
        this.reset();
    },
    
    iconClick: function () {
        if ($(this.el).is('.active')) {
            this.reset();
        }
    },
    
    reset: function () {
        this.selection = null;
        this.render();
    }
});


