function BibleReaderController(host)
{
  var _self = this;
  var _db = new BibleDatabase();
  var _host = $(host);
  var _install = $(document.createElement("div"));
  var _navigator = $(document.createElement("div"));
  var _container = $(document.createElement("div"));

  _install.attr("id", "install");
  _navigator.attr("id", "navigator");
  _container.attr("id", "container");

  var nav = new BibleNavigator(_navigator, "http://" + location.host + "/styles/reader/themes/default/images/biblescale.png");

  var tabData = [
      { "name": "Read", "css": "icon", "title": "Read Bible chapters", "argument":0 },
      { "name": "Search", "css": "icon", "title": "Search for words and phrases", "argument":1 },
      { "name": "Concordance", "css": "icon", "title": "Look up a word in the concordance", "argument":2 },
      { "name": "Notes", "css": "icon", "title": "Make notes on a passage", "argument":3 },
      { "name": "Configure", "css": "icon", "title": "Configure your Offline Bible", "argument":4 }
    ];

  var paneTools = new PaneControl("tools");
  var resizeTools = new ResizeControl("toolsResize");
  var paneReader = new PaneControl("reader");
  var interface = new ContainerControl("interface");
  var host = new MultiViewControl("host");
  var viewRead = new ContainerControl("vwRead");
  var viewSearch = new ContainerControl("vwSearch");
  var viewConcordance = new ContainerControl("vwConcordance");
  var viewNotes = new ContainerControl("vwNotes");
  var viewConfigure = new ContainerControl("vwConfigure");
  var read = new ReadControl("bibleDisplay");
  var search = new SearchControl("searchDisplay", "Search");
  var concordance = new ConcordanceControl("concordanceDisplay", "Concordance");
  var notes = new NotesControl("notesDisplay", "Notes");
  var configure = new ConfigureControl("configureDisplay", "Configure");
  var tabControl = new TabControl("tabs", tabData, 0);
  var srchSidebar = new MiniSearchControl("sidebarSearch", function(e) { return false; } );
  var bookSidebar = new BookmarkControl("bookmarks", 6);
  var noteSidebar = new NotepadControl("pad", 5);

  this.load = function()
  {
    _host.empty();

    _host.append(_install);
    _host.append(_navigator);
    _host.append(_container);

    viewRead.appendChild(read.getNode());
    viewSearch.appendChild(search.getNode());
    viewConcordance.appendChild(concordance.getNode());
    viewNotes.appendChild(notes.getNode());
    viewConfigure.appendChild(configure.getNode());

    host.appendView(viewRead.getNode());
    host.appendView(viewSearch.getNode());
    host.appendView(viewConcordance.getNode());
    host.appendView(viewNotes.getNode());
    host.appendView(viewConfigure.getNode());

    interface.appendChild(tabControl.getNode());
    interface.appendChild(host.getNode());
    paneReader.appendChild(interface.getNode());
    paneTools.appendChild(srchSidebar.getNode());
    paneTools.appendChild(bookSidebar.getNode());
    paneTools.appendChild(noteSidebar.getNode());

    _container.append(paneTools.getNode());
    _container.append(resizeTools.getNode());
    _container.append(paneReader.getNode());

    var notePadExpander = new VerticalExpander(noteSidebar.getNode(), paneTools.getContentNode(), $(noteSidebar.getNode()).children('ol').height());
    var interfaceExpander = new VerticalExpander(interface.getNode(), paneReader.getContentNode());
    var multiViewExpander = new VerticalExpander(host.getNode(), interface.getNode(), 10);
    var readExpander = new VerticalExpander(viewRead.getNode(), host.getNode());
    var searchExpander = new VerticalExpander(search.getNode(), host.getNode());
    var searchContentExpander = new VerticalExpander(search.getContentNode(), search.getNode());

    var readExpander2 = new FillExpander(read.getNode(), viewRead.getNode());
    var readExpander3 = new VerticalExpander(read.getContentNode(), read.getNode());

    $(window).bind("resize", function (e)
      {
        paneReader.expand(e);
        resizeTools.expand(e);
        paneTools.expand(e);
        notePadExpander.expand(e);
        interfaceExpander.expand(e);
        multiViewExpander.expand(e);
        searchExpander.expand(e);
        searchContentExpander.expand(e);
        readExpander.expand(e);
        readExpander2.expand(e);
        readExpander3.expand(e);
      }
    );

    read.textSize(_db.getConfig("textSize", "medium"));
    read.layoutMode(_db.getConfig("layoutMode", "inline"));
    read.flowMode(_db.getConfig("flowMode", "vertical"));
    read.notesEnabled(_db.getConfig("notesEnabled", false));
    read.paragraphsEnabled(_db.getConfig("paragraphsEnabled", true));
    this.setPassage(BibleBookmark.FromJson(_db.getConfig("currentPassage", "[1, 1, 1]")), true);
    this.setView(null, parseInt(_db.getConfig("activeTab", 0)));
    noteSidebar.selectedPage(parseInt(_db.getConfig("notesPage", 1)));

    for (var i=1; i<=6; i++)
    {
      var bookmark = _db.getConfig("bookmark" + i);
      if (bookmark) { this.setBookmark(i, BibleBookmark.FromJson(bookmark)); }
    }
  }

  this.setBookmark = function(index, bookmark)
  {
    _db.setConfig("bookmark" + index.toString(), BibleBookmark.ToJson(bookmark));
    bookSidebar.showBookmark(index, bookmark);
    nav.bookmark(index - 1, bookmark.position(), true);
  }

  this.deleteBookmark = function(index)
  {
    _db.delConfig("bookmark" + index);
    bookSidebar.hideBookmark(index);
    nav.bookmark(index - 1, 0, false);
  }

  this.setPassage = function(bookmark, surpressViewSwitch)
  {
    if (!bookmark) { return; }
    if (!surpressViewSwitch)
    {
      _self.setView(null, 0);
    }
    _db.setConfig("currentPassage", BibleBookmark.ToJson(bookmark));
    var text = "";
    var rs = null;
    try
    {

      rs = _db.executeSql(BibleDatabase.SPROCS.GetVersesByChapterId(), [bookmark.chapterId()]);
      while (rs.isValidRow())
      {
        var verse_num = parseInt(rs.field(0));
        text = text + rs.field(1);
        rs.next();
      }
    }
    catch (err)
    {
      if (rs)
      {
        alert(err.message + " - " + rs.field(1));
      }
      else
      {
        alert(err.message);
      }
    }
    finally
    {
      if (rs) { rs.close(); }
    }

    var startContent =  { "bookmark": bookmark,  "info": [{"content": "Bookmark &raquo;", "css": null, "title": "Click here to bookmark this passage"}], "html": text };
    read.content(startContent);
    nav.marker(bookmark.position());
  }

  this.search = function(text, offset, limit, filterType, filterData)
  {
    var reference = VerseReference.fromString(text);
    if (reference && reference.chapterId() > 0)
    {
      _self.setPassage(reference);
    }
    else
    {
      search.searchText(text);
      srchSidebar.searchText(text);

      if (text == null || text == "") { return; }
      _self.setView(null, 1);

      var terms = text.split(" ");

      for (var i=0; i< terms.length; i++)
      {
        terms[i] = terms[i].toLowerCase();
      }

      var sproc = BibleDatabase.SPROCS.KeywordSearchAll(terms.length);
      var sprocParams = terms.concat([limit, offset]);

      var matchSproc = null;
      var matchSprocParams = null;

      if (filterType)
      {
        switch (filterType)
        {
          case "book":
          {
            sproc = BibleDatabase.SPROCS.KeywordSearchFilterBook(terms.length);
            sprocParams = terms.concat([filterData, limit, offset]);

            matchSproc = BibleDatabase.SPROCS.KeywordSearchMatchesBook(terms.length);
            matchSprocParams = terms.concat([filterData]);
            break;
          }
          case "testament":
          {
            if (filterData == "old")
            {
              sproc = BibleDatabase.SPROCS.KeywordSearchFilterOT(terms.length);
              matchSproc = BibleDatabase.SPROCS.KeywordSearchMatchesOT(terms.length);
              matchSprocParams = terms;
            }
            else
            {
              sproc = BibleDatabase.SPROCS.KeywordSearchFilterNT(terms.length);
              matchSproc = BibleDatabase.SPROCS.KeywordSearchMatchesNT(terms.length);
              matchSprocParams = terms;
            }
            break;
          }
        }
      }

      var rs = null;
      var hits = new Array();
      var occurs = 0;
      var verses = 0;
      var distribution = new Array();
      var startMilliseconds = (new Date()).getTime();
      try
      {
        rs = _db.executeSql(sproc, sprocParams);
        while (rs.isValidRow())
        {
          hits.push([rs.field(0),rs.field(1),rs.field(2)]);
          rs.next();
        }
      }
      catch (err)
      {
        alert(err);
      }
      finally
      {
        if (rs) { rs.close(); }
      }

      try
      {
        rs = _db.executeSql(BibleDatabase.SPROCS.KeywordSearchOccurences(terms.length), terms);
        while (rs.isValidRow())
        {
          verses += parseInt(rs.field(1));
          occurs += parseInt(rs.field(2));
          distribution.push([parseInt(rs.field(0)), parseInt(rs.field(1)), parseInt(rs.field(2))]);
          rs.next();
        }
      }
      catch (err)
      {
        alert(err);
      }
      finally
      {
        if (rs) { rs.close(); }
      }

      matches = verses;

      if (matchSproc != null)
      {
        try
        {
          rs = _db.executeSql(matchSproc, matchSprocParams);
          while (rs.isValidRow())
          {
            matches = parseInt(rs.field(0));
            rs.next();
          }
        }
        catch (err)
        {
          alert(err);
        }
        finally
        {
          if (rs) { rs.close(); }
        }
      }

      var endMilliseconds = (new Date()).getTime();

      var results = {
          "term": text,
          "data": hits,
          "distribution": distribution,
          "verses": verses,
          "occurs": occurs,
          "matches": matches,
          "time": (endMilliseconds - startMilliseconds) / 1000,
          "offset": offset,
          "limit": limit
        };
      search.displayResults(results);
    }
  }

  this.setTextSize = function(size)
  {
    _db.setConfig("textSize", size);
  }

  this.setLayoutMode = function(mode)
  {
    _db.setConfig("layoutMode", mode);
  }

  this.setFlowMode = function(mode)
  {
    _db.setConfig("flowMode", mode);
  }

  this.setNotesEnabled = function(flag)
  {
    _db.setConfig("notesEnabled", flag.toString());
  }

  this.setParagraphsEnabled = function(flag)
  {
    _db.setConfig("paragraphsEnabled", flag.toString());
  }

  this.saveNote = function(pageIndex, text)
  {
    _db.setConfig("notesPageContent" + pageIndex, text);
  }

  this.loadNote = function(pageIndex)
  {
    _db.setConfig("notesPage", pageIndex);
    noteSidebar.text(_db.getConfig("notesPageContent" + pageIndex, null));
  }

  this.setView = function(e, argument)
  {
    host.activeViewIndex(argument);
    tabControl.activeTabIndex(argument);
    _db.setConfig("activeTab", argument);
    $(window).trigger("resize");
  }

  this.BibleDatabase = function() { return _db; }

  // EVENT HANDLERS
  // Hook up events to appropriate controller methods.
  tabControl.onTabChanged(this.setView);
  read.onTextSizeChanged(this.setTextSize);
  read.onLayoutChanged(this.setLayoutMode);
  read.onFlowChanged(this.setFlowMode);
  read.onNotesEnabledChanged(this.setNotesEnabled);
  read.onParagraphsEnabledChanged(this.setParagraphsEnabled);
  read.onPreviousBook(this.setPassage);
  read.onPreviousChapter(this.setPassage);
  read.onNextChapter(this.setPassage);
  read.onNextBook(this.setPassage);
  noteSidebar.onPageChanged(this.loadNote);
  noteSidebar.onNoteSave(this.saveNote);
  nav.onTextRequested(function(pos) { return BibleBookmark.FromPosition(pos).text(); } );
  nav.onPositionChanged(this.setPassage);
  bookSidebar.onBookmarkRequest( function() { return read.bookmark(); } );
  bookSidebar.onBookmarkCreated(this.setBookmark);
  bookSidebar.onBookmarkNavigate(this.setPassage);
  bookSidebar.onBookmarkDeleted(this.deleteBookmark);
  srchSidebar.onSearch(this.search);
  search.onSearch(this.search);
  search.onResultBookmark(function (ref) {alert("Bookmark " + ref);} );
  search.onResultNavigate(this.setPassage);
}

