// ================================================================================
// BibleStudio controller

// TODO low HS 2009-12-31 workaround for Log() object used in scripts copied from ParolEdit
var Log;
if (!Log) {
  Log = {
    debug: function() {},
    info : function() {},
    error: function() {}
  };
}

function Controller()
{
  try {
    
    // === Create MVC components ===
    this._Model          = new BibleStudioModel();
    this._FirstPageView  = new FirstPageView(this._Model, this);
    this._FirstView      = new ParolView("Parol1");
    this._SecondPageView = new SecondPageView(this._Model, this);
    this._SecondView     = new ParolView("Parol2");

    this._BibleChapterView = new BibleChapterView("BibleText", "BibleChapter", this._Model);

    this._FilteredParolView = new FilteredParolView(this._Model, this);
    this._FirstStatusView = new FirstStatusView(this._Model, this, "numberNone", "numberFew", "numberFewEval");

    this._FirstSL              = document.getElementById("CiteParol1");
    this._StatusLine           = document.getElementById("statusLine");
    this._FilterForParol       = document.getElementById("filterForParol");
    this._TabFilteredParolHint = document.getElementById("tabFilteredParolHint");
    this._BtnDeletePair        = document.getElementById("btnDeletePair");
    this._AddPair              = document.getElementById("btnAddPair");
    this._SelectSeries         = document.getElementById("Series");
    this._BibleRef             = document.getElementById("bibleRef");
    this._btnFindBibleRef      = document.getElementById("btnFindBibleRef");

    this._UserName             = document.getElementById("username");
    this._Email                = document.getElementById("email");
    this._btnGotoLogin         = document.getElementById("btnGotoLogin");
    this._btnLogin             = document.getElementById("btnLogin");
    this._Welcome              = document.getElementById("Welcome");
    this._btnLogout            = document.getElementById("btnLogout");
    this._AcceptLicense        = document.getElementById("acceptLicense");
    this._AutoLogin            = document.getElementById("autoLogin");

    // ensure input controls are enabled/disabled after reload
    this._UserName.disabled = false;
    this._Email.disabled = false;

    // config (for now)
    this._TargetYear = 2013;
  }
  catch (e) {
    alert("Controller: " + e.name + ": " + e.message);
  }
}


Controller.prototype.toString = function()
{
  return "Controller";
}

Controller.prototype.init = function()
{
  try {
    this.showState("Loading...");
    this._Model.observe("loadInitialDone",               this, Controller.prototype.receiveInitial);
    this._Model.observe("loginDone",                     this, Controller.prototype.receiveLogin);
    this._Model.observe("logoutDone",                    this, Controller.prototype.receiveLogout);
    this._Model.observe("loadBibleChapter",              this, Controller.prototype.receiveBibleChapter);
    this._Model.observe("loadParolEditBibleChapter",     this, Controller.prototype.receiveParolEditBibleChapter);
    this._Model.observe("loadAllSecondForFirstQualList", this, Controller.prototype.receiveAllSecondForFirstQualList);
    this._Model.observe("loadAllParol",                  this, Controller.prototype.receiveAllParol);
    this._Model.observe("postUserPairRate",              this, Controller.prototype.receivePostUserPairRate);
    this._Model.observe("loadUserPairRateList",          this, Controller.prototype.receiveLoadUserPairRateList);
    this._Model.observe("postAddPairFound",              this, Controller.prototype.postAddPairFound);
    this._Model.observe("postAddPair",                   this, Controller.prototype.receivePostAddPair);
    this._Model.observe("readPairsInfo",                 this, Controller.prototype.readPairsInfo);
    this._Model.observe("postDeletePair",                this, Controller.prototype.receiveDeletePair);
    this._Model.observe("parolEditPostEdit",             this, Controller.prototype.receiveParolEditPostEdit);
    this._Model.observe("parolEditPostNew",              this, Controller.prototype.receiveParolEditPostNew);
    this._Model.observe("parolEditDelete",               this, Controller.prototype.receiveParolEditDelete);
    // errors
    this._Model.observe("errorLogin",                    this, Controller.prototype.showError);
    this._Model.observe("errorLoadBibleChapter",         this, Controller.prototype.showError);
    this._Model.observe("errorDeletePair",               this, Controller.prototype.showError);
    this._Model.observe("errorParolEditPost",            this, Controller.prototype.showError);
    this._Model.observe("errorParolEditDelete",          this, Controller.prototype.showError);

    this._Model.setCollectionName("TheWordFirst" + this._TargetYear);
    this._Model.setBibleName("Schlachter2000");

    this._BibleChapterUndoStack = [];
    this._BibleChapterRedoStack = [];

    // handle auto-login =: [autoLogin]
    // get cookie
    var userName = jQuery.trim(this.getCookie("userName"));
    var email    = jQuery.trim(this.getCookie("email"));
    if (userName && email) {
      // store into model
      this._Model.setUserName(userName);
      this._Model.setEmail(email);
      // check the checkbox => avoid loosing auto-login setting when user logs out
      this._AutoLogin.checked = true;
    }

    var TargetYear = document.getElementById("targetYear");
    if (TargetYear) {
      TargetYear.innerHTML = this._TargetYear;
    }

    this.updateCommands(); // before loading
    this._Model.loadInitial();
  }
  catch (e) {
    alert("Controller.init: " + e.name + ": " + e.message);
  }
}

Controller.prototype.updateCommands = function() 
{
  try {
    /* Update button sensitivity.
       Do not update sensitivity that depends on login/logout here, this is done directly there.
       Do not update sensitivity for ParolEdit area here, this is done directly there.
    */

    var firstParolQual = this._FirstPageView.getCurParolQual();
    var secondParolQual = this._SecondPageView.getCurParolQual();

    var firstIndex = this._FirstPageView.getCurIndex();
    var firstLen   = this._Model.getSeries() && this._Model.getSeries().length;

    document.getElementById("nav_first").disabled = !firstLen || firstIndex == 0;
    document.getElementById("nav_prev").disabled  = !firstLen || firstIndex == 0;
    document.getElementById("nav_next").disabled  = !firstLen || firstIndex >= firstLen - 1;
    document.getElementById("nav_last").disabled  = !firstLen || firstIndex >= firstLen - 1;

    var secondLen = this._SecondPageView.getCollection().length;
    var bSecondEmpty = !secondLen;
    document.getElementById("btnShowBibleTextForSecond").disabled = bSecondEmpty;
    document.getElementById("btnSortSecondByRateBibleRef").disabled = secondLen <= 1;
    document.getElementById("btnSortSecondByBibleRef").disabled = secondLen <= 1;
    this._BtnDeletePair.disabled = bSecondEmpty;

    var userRate = null;
    if (!bSecondEmpty && firstParolQual && secondParolQual) {
      var aPair = this._Model.getPairSet().findFirstSecond(firstParolQual, secondParolQual);
      if (aPair) {
        userRate = aPair.getUserRate();
      }
    }

    document.getElementById("btnYes"   ).disabled = bSecondEmpty;
    document.getElementById("btnMaybe" ).disabled = bSecondEmpty;
    document.getElementById("btnNo"    ).disabled = bSecondEmpty;
    document.getElementById("btnRmRate").disabled = !userRate;

    var SecondParol = this._FilteredParolView.getCurParol();
    var isFirst = SecondParol && this._Model.getParolInCollection(SecondParol.getID());

    var filteredLen= this._FilteredParolView.getCollection().length;
    document.getElementById("btnBibleTextForParol").disabled = !filteredLen;
    document.getElementById("btnGotoFilteredFirstParol").disabled = !filteredLen || !isFirst;
    this._AddPair.disabled = !this._Model.getUserName() || !filteredLen;

    var SecondParol = this._FilteredParolView.getCurParol();
    var bFilterParolMaybeEdited = SecondParol && SecondParol.getCategory("State") == 0
      && (SecondParol.getAuthor() == this._Model.getUserName() 
          || this._Model.getUserName() == "admin"); // [admin]
    // Parol buttons are hidden if not logged in -> [editParol]
    document.getElementById("btnParolEdit"  ).disabled = !bFilterParolMaybeEdited;
    document.getElementById("btnParolDelete").disabled = !bFilterParolMaybeEdited;

    document.getElementById("biblenav_histprev").disabled = !this._BibleChapterUndoStack.length;
    document.getElementById("biblenav_histnext").disabled = !this._BibleChapterRedoStack.length;

    var aBibleRef = this._BibleChapterView.getBibleRef();
    document.getElementById("biblenav_prev").disabled = !aBibleRef || aBibleRef.getChapterNumber() <= 1;
//TODO low 2010-03-27 HS - use info about [number of chapters in bible]
    //document.getElementById("biblenav_next").disabled;
  }
  catch (e) {
    alert("Controller.updateCommands: " + e.name + ": " + e.message);
  }
}

Controller.prototype.showState = function(msg) 
{
  if (!this._StatusLine) return;

  if (this._StatusLineTimer) {
    clearTimeout(this._StatusLineTimer);
  }

  // show value
  this._StatusLine.innerHTML = msg;

  if (msg) {
    // install timeout to reset msg again
    var that = this;
    this._StatusLineTimer = setTimeout(
        function() {
         that._StatusLineTimer = null; // prevent from calling clearTimeout()
         that._StatusLine.innerHTML = "&nbsp;";
       },
       2000);
  }
}

// showError:
//   use for notifications like
//   this._Model.notify("errorLoadBibleChapter", this.xhr);
Controller.prototype.showError = function(event, xhr) 
{
  var s = " (" + event;
  if (xhr) {
    if (xhr.statusText) {
    s += " - " + xhr.statusText;
    }
    if (xhr.responseText) {
      s += " - " + xhr.responseText;
    }
    if (xhr.status) {
      s += " - HTTP code " + xhr.status;
    }
  }
  s += ")";
  // beautify multi-line Apache errors, filter tags
  s = s.replace(/\s+/g, " ").replace(/<.*?>/g, " ");
  this.showState("Fehler beim Übertragen der Daten" + s);

  if (xhr) {
    // For server response e.g. "Name does not match for given email address (error 53)"
    // show alert message identified by error number "53" (if defined)
    var res = xhr.responseText.match(/\(error\s+(\d+)\)/);
    if (res) {
      var errNum = res[1];
      var errMsg = {
  //TODO low HS 2009-12-31 alert texts multilanguage      
        "51": "Bitte geben Sie Ihre E-Mail-Adresse an!",
        "52": "Bitte geben Sie eine gültige E-Mail-Adresse an (z.B. \"max@mustermann.org\")!",
        "53": "Die E-Mail-Adresse ist schon für einen anderen Benutzernamen eingetragen - bitte verwenden Sie den ursprünglich eingetragenen Benutzernamen. (Bitte schreiben Sie uns, falls Sie dies für einen Fehler in unserer Seite halten)",
        "54": "Für den Benutzernamen ist eine andere E-Mail-Adresse eingetragen. Bitte geben Sie die E-Mail-Adresse an, die Sie ursprünglich für den Benutzernamen angegeben haben. (Bitte schreiben Sie uns, falls Sie eine andere E-Mail-Adresse verwenden möchten)",
        "61": "Der Zweitspruch ist nicht mehr vorhanden.",
        "62": "Sie können diesen Zweitspruch nicht löschen, weil er von einem anderen Benutzer zugeordnet wurde.",
        "63": "Sie können diesen Zweitspruch nicht mehr löschen, weil er bereits Bewertungen von anderen Benutzern hat."
      };
      errMsg["55"] = errMsg["54"]; // "55": "Für den Benutzernamen ist eine andere E-Mail-Adresse eingetragen. Außerdem ist die E-Mail-Adresse schon für einen anderen Benutzernamen eingetragen."

      var msg = errMsg[errNum];
      if (msg) {
        alert(msg);
      }    
    }
  }
}

Controller.prototype.receiveInitial = function() 
{
  this.showState("Daten laden......");

  // Series = all Parol 2011, or one series. =: [series]
  // Value of "series" cookie:
  // - ""    = not set, select one series at random
  // - 2011  = entire list of Parol for year
  // - 1..40 = series of 10 Parol

  var series = this.getCookie("series");
  if (!series) {
    // easy randomize based on seconds of time
    series = (new Date().getTime() % this._Model.getNumSeries()) + 1;
    this.setCookie("series", series);
  }
  else if (Number(series) > 2000 && Number(series) != this._TargetYear) {
    series = this._TargetYear;
    this.setCookie("series", series);
  }

  this.fillSeriesCombo(series);

  // if user switched to entire series (year number), do not randomize
  if (Number(series) < 2000) {
    this._Model.computeSeries(series); // series is 1-based
  }

  var parolQual = this.getCookie("first");
  var initialFirstIndex = 0;
  if (parolQual) {
    initialFirstIndex = this._Model.findParolQualIndexInSeries(parolQual) || 0;
  }
  this._FirstPageView.updateCollection(initialFirstIndex); // notifies via Controller.selectFirst!

  this.updateCommands(); // ***
}

// === First ===

Controller.prototype.selectFirst = function(index)
{
  try {
    var aParol = null;
    var parolQual = "";
    if (null != index && this._Model.getSeries()) {
      aParol = this._Model.getSeries()[index];
      if (aParol) {
        parolQual = aParol.getID();
      }
    }
    // even if aParol == null:
    this._FirstView.update(aParol, "first");
    if (this._FirstSL) {
      this._FirstSL.innerHTML = aParol.getSL();
    }

    this.setCookie("first", parolQual);

//TODO low HS 2009-12-31 in selectFirst, retrieve only second Parol that are still missing for First parol
    if (!parolQual) { // we already have all second Parol texts needed for first
      this.updateSecond();
    }
    else if (this._Model.getAreAllParolLoaded()) {
      this.updateSecond();
    }
    else {
      // also leads to this.updateSecond()
      this.showState("Ergänzende Sprüche laden für " + parolQual + "...");
      this._Model.loadParolBoxAllSecondForFirstQualList(parolQual);
      // do not update bible text - navigation of first parol should not have effect for tabs
      //if (this.getOpenTab() == "BibleText") {
      //  this.selectBiblePage(parolQual);        
      //}
    }
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (selectFirst)");
  }
}

Controller.prototype._gotoFirstParol = function(FirstParol)
{
  try {
    // keep it simple: release restriction to series
    // [selectSeries]
    var n = null;
    this._Model.computeSeries(n);
    this.setCookie("series", n);
    var Series = document.getElementById("Series");
    if (Series) {
      Series.options.selectedIndex = 0; // all
    }

    var firstIndex = this._Model.findParolQualIndexInSeries(FirstParol.getID()) || 0;
    this._FirstPageView.updateCollection(firstIndex);
    this.selectFirst(firstIndex);
    this.updateCommands(); // ***
  }
  catch (e) {
    alert("Fehler beim Anzeigen des Erstspruchs: " + e.name + ": " + e.message + " (_gotoFirstParol)");
  }
}


// === Series ===

Controller.prototype.fillSeriesCombo = function(seriesSelected)
{
  try {
    var Coll = this._Model.getCollection();
    
    // the easy way which does not work in IE6:
    // var html = "<option value=''>(alle Erstsprüche 2011)</option>\n";
    // for (i = 1, len = this._Model.getNumSeries(); i <= len; ++i) {
    //   html += "<option value='" + i + "'>" + i + "&nbsp;&nbsp;(ab " + Coll[i-1].getSL() + ")</option>\n";
    // }
    // this._SelectSeries.innerHTML = html;

    // drop child nodes used as dummy in HTML file,
    while (this._SelectSeries.firstChild) {
      this._SelectSeries.removeChild(this._SelectSeries.firstChild);
    }

    var Series = this._SelectSeries;
    var createAppendOption = function(value, content) {    
      var option = document.createElement("option");
      option.setAttribute("value", value);
      if (seriesSelected == value) {
        option.setAttribute("selected", "selected");
      }
      option.appendChild(document.createTextNode(content));
      // this._SelectSeries is undefined here
      Series.appendChild(option);
    };
    
    // create first entry for all Parol 2011, see [series]
    createAppendOption(this._TargetYear, "(alle Erstsprüche " + this._TargetYear + ")");
    for (var i = 1, len = this._Model.getNumSeries(); i <= len; ++i) {
      createAppendOption(i, i + " (ab " + Coll[i-1].getSL() + ")");
    }

  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (fillSeriesCombo)");
  }
}

Controller.prototype.setCookie = function(name, value, days)
{
  if (!days) { 
    days = 30;
  }
  //alert("setCookie(" + name + ", " + value + ")");
  var aCookie = new Cookie("BibleStudio");
  aCookie[name] = value;
  aCookie.store(days);
}

Controller.prototype.getCookie = function(name)
{
  var aCookie = new Cookie("BibleStudio");
  //alert("getCookie(" + name + " => " + aCookie[name] + ")");
  return aCookie[name];
}

Controller.prototype.selectSeries = function(select)
{
  try {
    var value = select.options[select.options.selectedIndex].value;
    // TODO low 2010-01-23 HS - current assumption: series combo-box contains first Parol 2011 + 40 series, see [series]
    var n = Number(value);
    // [selectSeries]
    this._Model.computeSeries(n < 2000 ? n : null); // series is 1-based
    this.setCookie("series", n);
    this._FirstPageView.updateCollection();
    this.selectFirst(0);
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (selectSeries)");
  }
}

// === ParolBox ===

Controller.prototype.receiveAllSecondForFirstQualList = function() 
{
  this.updateSecond();
  this.showState("Ergänzende Bibelsprüche geladen.");
}


// === PairSet ===

Controller.prototype.selectSecondLine = function(lineIndex)
{
  try {
    // lineIndex maybe null

    // select in Second list
    this._SecondPageView.selectLineIndex(lineIndex);

    // update Second view
    var aParol = this._SecondPageView.getCurParol();
    // even if aParol == null:
    this._SecondView.update(aParol);
    jQuery("#tdSecond").addClass("second").removeClass("filteredParol");
    this.updateCommands(); // ***
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (selectSecondLine)");
  }
}

Controller.prototype.updateSecond = function() 
{
  var parolQual = this._FirstPageView.getCurParolQual();
  if (!parolQual) { // be defensive
    return;
  }
  this._SecondPageView.setFirstParolQual(parolQual);
  this._SecondPageView.updateCollection();
  this._SecondPageView.updatePage();
  this.updateCommands(); // ***
}

Controller.prototype.receiveLoadUserPairRateList = function() 
{
  this._SecondPageView.updatePage();
  this.showState("Bewertungen geladen.");
  this.updateCommands(); // ***
}

Controller.prototype.receivePostUserPairRate = function() 
{
  // do not sort - selection will move and irritiate:
  // this._SecondPageView.sortCollection();
  
  // be nice - advance by one
  // but it irritates if you want to correct the setting again
  //TODO low HS 2009-12-31 after user pair rate modification, auto advance selection in second list - make this an option
  // this.navigateSecond("next");
  
  this._SecondPageView.updatePage();
  this.showState("Bewertung gespeichert.");
  this.updateCommands(); // ***
}

Controller.prototype.rate = function(rate)
{
  var firstParolQual = this._FirstPageView.getCurParolQual();
  var secondParolQual = this._SecondPageView.getCurParolQual();
  if (firstParolQual && secondParolQual) {
    this._Model.postUserPairRate(firstParolQual, secondParolQual, rate);
  }
}

Controller.prototype.sortSecondByBibleRef = function()
{
  this._SecondPageView.sortCollectionByBibleRef();
  this._SecondPageView.updatePage();
  this._SecondPageView.scrollSelectionIntoView();
}

Controller.prototype.sortSecondByRateBibleRef = function()
{
  this._SecondPageView.sortCollectionByRateBibleRef();
  this._SecondPageView.updatePage();
  this._SecondPageView.scrollSelectionIntoView();
}

Controller.prototype.receiveDeletePair = function() 
{
  this._SecondPageView.deleteSecond();
  this._SecondPageView.updatePage();
  this.showState("Zweitspruch gelöscht.");
  this.updateCommands(); // ***
}

Controller.prototype.deletePair = function()
{
  var firstParolQual = this._FirstPageView.getCurParolQual();
  var secondParolQual = this._SecondPageView.getCurParolQual();
//alert("Controller.deletePair: " + firstParolQual + " - " + secondParolQual);
  if (firstParolQual && secondParolQual
      && confirm("Wollen Sie den gewählten Spruch wirklich aus der Liste der zugeordneten Zweitsprüche löschen?")) {
    this._Model.deletePair(firstParolQual, secondParolQual);
  }
}

Controller.prototype.toggleSecondList = function()
{
  var List = document.getElementById("SecondList");
  jQuery(List).slideToggle(400, function() {
    jQuery("#btnSecondList").text(List.style.display == "none" ? "Liste auf >>" : "Liste zu <<");
  });
}

Controller.prototype.readPairsInfo = function(eventType, info) 
{
  //alert("arg="+arguments + ", 0= " + arguments[0] + ", 1= " + arguments[1]);
  this.showState(info);
  this.updateCommands(); // ***
}


// === FilterParol ===

Controller.prototype._filterParolApplyHavingText = function(filter)
{
  this._Model.filterParol(jQuery.trim(filter));
  this._FilteredParolView.setFilteredParol(); // using Model
  this._FilteredParolView.updateCollection();
  this._FilteredParolView.updatePage();
  this.updateCommands(); // ***
}

Controller.prototype.receiveAllParol = function() 
{
  // if we come from login, do not apply filter at once
  if (this._loadParolBoxFromLogin) {
    if (this._LoadParolBoxTimer) {
      clearTimeout(this._LoadParolBoxTimer);
    }
    this._loadParolBoxFromLogin = null;
  }
  else {
    this._filterParolApplyHavingText(this._FilterForParol.value);
  }
}

Controller.prototype.gotoFilteredFirstParol = function()
{
  try {
    var FirstParol  = this._FilteredParolView.getCurParol();
    if (FirstParol && FirstParol != this._FirstPageView.getCurParol()) {
      this._gotoFirstParol(FirstParol);
    }
  }
  catch (e) {
    alert("Fehler beim Anzeigen des Erstspruchs: " + e.name + ": " + e.message + " (gotoFilteredFirstParol)");
  }
}

Controller.prototype._filterParolApply = function()
{
  // TODO low HS 2009-12-31 after loadParolBox has been called but the data has not arrived yet,
  // any subsequent call to _filterParolApply will again call loadParolBox :-(
  // This may make sense to be able to retry - after some time => use timer to avoid quick retry?

  if (this._Model.getAreAllParolLoaded()) {
    this._filterParolApplyHavingText(this._FilterForParol.value);
  }
  else {
    // why is this not shown in FF3.5.3? this._BibleChapterView.showText("<p>Lade Text aller Bibelsprüche (bitte warten)...</p>");
    this.showState("Lade Text aller Bibelsprüche (bitte warten)...");
    this._Model.loadParolBox();
  }
}

Controller.prototype.filterParol = function()
{
  this._filterParolApply();
}

Controller.prototype.filterParolReset = function()
{
  this._FilterForParol.value = "";
  this._filterParolApply();
}

Controller.prototype.selectFilteredParolLine = function(lineIndex, bUpdateSecondView)
{
  try {
    // lineIndex maybe null
    // select in list
    this._FilteredParolView.selectLineIndex(lineIndex);

    if (bUpdateSecondView) {
      // even if aParol == null:
      var secondParol = this._FilteredParolView.getCurParol();
      if (secondParol) {
        this._SecondView.update(secondParol);
       jQuery("#tdSecond").addClass("filteredParol").removeClass("second");
      }
    }
    this.updateCommands(); // ***
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (selectFilteredParolLine)");
  }
}

Controller.prototype.showParol = function(id)
{
  try {
    var aParol = this._Model.getParolBox().findParol(id);
    if (aParol) {
      this._SecondView.update(aParol);
      jQuery("#tdSecond").addClass("filteredParol").removeClass("second");
    }
    this.updateCommands(); // ***
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (showParol)");
  }
}

Controller.prototype.showBibleTextForParol = function()
{
  var aParol = this._FilteredParolView.getCurParol();
  if (aParol) {
    this.showBibleTextForParolQual(aParol.getID());
  }
}

Controller.prototype.postAddPairFound = function() 
{
  var secondParol = this._FilteredParolView.getCurParol();
  this._SecondPageView.gotoParolQual(secondParol.getID());

  this.showState("Spruchpaar bereits vorhanden.");

  var firstParolQual = this._FirstPageView.getCurParolQual();
  this._Model.postUserPairRate(firstParolQual, secondParol.getID(), +1);
  //TODO low HS 2009-12-31 after second parol has been added from parol list, scroll into view (cf. -->receivePostAddPair)
  // this._SecondPageView.scrollSelectionIntoView(true);
  //this._FilteredParolView.scrollSelectionIntoView(false);
}

Controller.prototype.receivePostAddPair = function() 
{
  // The pair has been added, or it already existed in Model!
  // Continue as if the pair has been added.

  this._SecondPageView.updateForFirstParolQual();
  this._SecondPageView.updateCollection();
  var secondParol = this._FilteredParolView.getCurParol();
  this._SecondPageView.gotoParolQual(secondParol.getID());

  this.showState("Spruchpaar gespeichert.");

  var firstParolQual = this._FirstPageView.getCurParolQual();
  this._Model.postUserPairRate(firstParolQual, secondParol.getID(), +1);

//TODO low HS 2010-02-28 should show the ParolSecond image for secondParol in FilteredParolView
//TODO low HS 2009-12-31 scroll into view - must be done for Second and FilteredParolView (<--postAddPairFound)
  // but screen flickers, => avoid for now (lists are short for some time)
  // this._SecondPageView.scrollSelectionIntoView(true);
  // this._FilteredParolView.scrollSelectionIntoView(false);
}

Controller.prototype._isNewPairOk = function(firstParol, secondParol)
{
  try {
    if (firstParol == secondParol) {
      alert("Sie können den 1. Bibelspruch nicht sich selber zuordnen.");
      return false;
    }
    var First  = new Bible20.Bible.BibleRef().fromParolID(firstParol.getID());
    var Second = new Bible20.Bible.BibleRef().fromParolID(secondParol.getID());
    if (First.getBook() == Second.getBook()
        && First.getChapterNumber() == Second.getChapterNumber()
        && !confirm("Der Bibelspruch steht im selben Kapitel wie der Erstspruch!\n\n"
                    + "Bibelsprüche aus demselben direkten Zusammenhang ergänzen sich oft gut, "
                    + "aber der Nutzen ist für den Leser möglicherweise gering, "
                    + "wenn er die Verse sowieso im Zusammenhang liest.\n\n"
                    + "Möchten Sie den Bibelspruch wirklich zuordnen?")) {
      return false;
    }

    return true;
  }
  catch (e) {
    alert("Fehler beim Prüfen des Spruchpaares: " + e.name + ": " + e.message + " (_isNewPairOk)");
  }
}

Controller.prototype.addPair = function()
{
  try {
    if (!this._Model.getUserName()) return; // be defensive - check whether logged in
    var firstParol  = this._FirstPageView.getCurParol();
    var secondParol = this._FilteredParolView.getCurParol();
    if (firstParol && secondParol) {
      if (!this._isNewPairOk(firstParol, secondParol)) {
        return;
      }
      this._Model.addPair(firstParol, secondParol);
    }
  }
  catch (e) {
    alert("Fehler beim Hinzufügen des Spruchpaares: " + e.name + ": " + e.message + " (addPair)");
  }
}

// === FirstStatus ===

Controller.prototype.showBibleTextForStatusParol = function()
{
  var aParol = this._FirstStatusView.getCurParol();
  if (aParol) {
    this.showBibleTextForParolQual(aParol.getID());
  }
}

Controller.prototype.updateFirstStatus = function()
{
  try {
    this._FirstStatusView.setCollection(this._Model.getCollection());
    this._FirstStatusView.updatePage();
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Erstsprüche: " + e.name + ": " + e.message + " (updateFirstStatus)");
  }
}

Controller.prototype.gotoFirstStatusParol = function()
{
  try {
    var FirstParol  = this._FirstStatusView.getCurParol();
    if (FirstParol && FirstParol != this._FirstPageView.getCurParol()) {
      this._gotoFirstParol(FirstParol);
    }
  }
  catch (e) {
    alert("Fehler beim Anzeigen des Erstspruchs: " + e.name + ": " + e.message + " (gotoFirstStatusParol)");
  }
}

Controller.prototype.selectFirstStatusLine = function(lineIndex, bUpdateSecondView)
{
  try {
    // lineIndex maybe null
    // select in list
    this._FirstStatusView.selectLineIndex(lineIndex);

    if (bUpdateSecondView) {
      // even if aParol == null:
      var secondParol = this._FirstStatusView.getCurParol();
      if (secondParol) {
        this._SecondView.update(secondParol);
       jQuery("#tdSecond").addClass("filteredParol").removeClass("second");
      }
    }
    this.updateCommands(); // ***
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (selectFirstStatusLine)");
  }
}


// === Tabs ===

Controller.prototype.gotoTabFilteredParol = function()
{
  var $tabs = $('#tabs').tabs();
  //TODO low HS 2009-12-31 using constant index of "Parol" tab
  $tabs.tabs('select', "#tabFilteredParol");
}

Controller.prototype.gotoTabHelp = function()
{
  var $tabs = $('#tabs').tabs();
  //TODO low HS 2009-12-31 using constant index of "Login" tab
  $tabs.tabs('select', "#tabHelp");
  document.getElementById("helpButtonTarget").scrollIntoView(false);
  //document.getElementById("tabs").scrollIntoView(true);
}

// === ParolEdit ===

Controller.prototype._parolEditClear = function()
{
  var BibleRefControl = document.getElementById("parolEditBibleRef");
  BibleRefControl.disabled = false;
  // 2011-06-24 HS: initialize from search box (which often contains bible chapter)
  BibleRefControl.value = this._FilterForParol.value;
  document.getElementById("parolEditIntro").value = "";
  document.getElementById("parolEditText").value = "";
  this._ParolEditQual = "";
}

Controller.prototype._parolEditGetBibleRef = function()
{
  try {
    var BibleRefControl  = document.getElementById("parolEditBibleRef");
    var hint = "Welche Bibelstelle meinten Sie?\nBitte schreiben Sie die Bibelstelle z.B. wie\n\n"
      + "1. Mose 2,3-4\n"
      + "1. Mose 2:3.4\n"
      + "Philemon 4-5\n";
  
    // skip if bible ref is white-space only
    if (!BibleRefControl.value.match(/\S/)) {
      alert(hint);
      BibleRefControl.focus();
      return null;
    }
  
    // analyze bible ref
    var Norm = new Bible20.Bible.BibleRefNormalizer();
    var aBibleRef = Norm.normalize(jQuery.trim(BibleRefControl.value));
    // normalize() silently ignores end value < start value!
    if (!aBibleRef) {
      // skip if not understood
      alert(hint);
      BibleRefControl.focus();
      return null;
    }
  
    // normalize guarantees end && start <= end
    var start = aBibleRef.getVerseNumberStart();
    var end   = aBibleRef.getVerseNumberEnd();
  
    // allow at most 3 verses
    if (end >= start + 3) {
      alert("Bitte verwenden Sie höchstens 3 Verse!");// (" + start + " .. " + end + ")");
      BibleRefControl.focus();
      return null;
    }
    return aBibleRef;
  }
  catch (e) {
    alert("Fehler: " + e.name + ": " + e.message + " (_parolEditGetBibleRef)");
  }
}

Controller.prototype.parolNew = function()
{
  delete this._ParolEditQual; // be defensive (may be set from previous parolEditApply)
  jQuery("#parolEditStep2").hide();
  jQuery("#headingParolEdit").hide();
  // mind the order of the effects!
  jQuery("#btnParolEditStep2").show();
  jQuery("#parolEditNewHint").show();
  jQuery("#headingParolNew").show(); // is contained in #divParolEdit
  jQuery("#divParolEdit").slideDown();
  this._parolEditClear();
  // set focus into bibleRef edit control
  document.getElementById("parolEditBibleRef").focus();
  this._ParolEditMode = "new";
}

Controller.prototype.parolEdit = function()
{
  var FilteredParol = this._FilteredParolView.getCurParol();
  if (FilteredParol) {
    var BibleRefControl = document.getElementById("parolEditBibleRef");
    var BibleTextControl = document.getElementById("parolEditText");
    BibleRefControl.disabled = true; // edit only intro/text, not bibleRef!
    BibleRefControl.value = FilteredParol.getSL();
    document.getElementById("parolEditIntro").value = FilteredParol.hasIL() ? FilteredParol.getRichIL() : "";
    var text = FilteredParol.getRichL();
    document.getElementById("parolEditText").value = text;
    this._ParolEditQual = FilteredParol.getID(); // remember only qual - be defensive about deletion.
    BibleTextControl.title = this._ParolEditQual + " " + this._Model.getBibleName(); // [hint for expert]
    this._ParolEditMode = "edit";

    jQuery("#headingParolNew").hide();
    jQuery("#parolEditNewHint").hide();
    jQuery("#btnParolEditStep2").hide();
    jQuery("#parolEditStep2").show();
    jQuery("#headingParolEdit").show();
    jQuery("#divParolEdit").slideDown();
  }
}

Controller.prototype.receiveParolEditDelete = function(event, parolQual, nDeletedRows, nParolRows)
{
  // $nDeletedRows > 0, $nParolRows == 0: deletion successful
  // $nDeletedRows == 0, $nParolRows > 0: not deleted, condition was not met
  // $nDeletedRows == 0, $nParolRows == 0: not deleted, did not exist any more
  // alert("receiveParolEditDelete: parolQual="+parolQual+", nDeletedRows="+nDeletedRows+", nParolRows="+nParolRows);
  if (nParolRows > 0) {
    alert("Der Bibelspruch wurde leider schon von jemand anderem zugeordnet, deshalb können Sie ihn nicht mehr löschen.");
    return;
  }

  this.showState("Bibelspruch gelöscht.");

  // reset edit area in case the deleted Parol was currently being edited
  if (parolQual == this._ParolEditQual) {
    this.parolEditClose();
  }

  var index = this._FilteredParolView.getCurIndex();
  this._FilteredParolView.setFilteredParol(); // using Model
  this._FilteredParolView.updateCollection();
  this._FilteredParolView.updatePage();

  // again set selection in _FilteredParolView (try to keep position)
  var len = this._FilteredParolView.getCollection().length;
  if (len > 0) {
    if (index >= len) {
      --index;
    }
    this.selectFilteredParolLine(index, true);
  }
  else {
    this._SecondView.update(null);
    jQuery("#tdSecond").addClass("filteredParol").removeClass("second");
  }
  this.updateCommands(); // ***
}

Controller.prototype.parolDelete = function()
{
  var FilteredParol = this._FilteredParolView.getCurParol();
  if (FilteredParol
    && FilteredParol.getCategory("State") == 0
    && (FilteredParol.getAuthor() == this._Model.getUserName()
        || this._Model.getUserName() == "admin")) { // [admin]
    if (this._Model.getPairSet().hasFirstForSecond(FilteredParol.getID())) {
      alert("Der neue Bibelspruch wurde leider schon zugeordnet, deshalb können Sie ihn nicht mehr löschen.");
    }
    else if (confirm("Wollen Sie den neuen Bibelspruch " + FilteredParol.getSL() + " wirklich löschen?")) {
      this._Model.parolEditDelete(FilteredParol.getID());
    }
  }
}

Controller.prototype._wrap = function(verse) 
{
  var MAXLEN = 65;
  var MINSPLIT = "30"; // no splitting before this column in a line
//TODO low 2010-02-26 HS - line-breaking - punctuation depends on language  
  // ... bla!", he said
  // "/" may occur as delimiter for emphasis

  // \u3xxx see Unicode 3.0 East Asian Quotation Marks (p. 152)
  var QUOTES = "\"\'\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u201f\u300c\u300d\u300e\u300f\u301d\u301f";
  var PUNCTUATION  = "\\/\\.,;:!\\?" + QUOTES;
  // Hyphen see Unicode 3.0 Unicode Dash Characters (p. 151)
  var HYPHEN       = "\u2012\u2013\u2014\u2015";

  // starting at pos MINSPLIT, match sentence termination, followed by quotes, termination of quoted sentence part etc., like
  var RE_CLAUSE    = new RegExp("^(.{" + MINSPLIT + ",}[\\.!?][" + PUNCTUATION + "]*)\\s+(.*)$");
  var RE_SUBCLAUSE = new RegExp("^(.{" + MINSPLIT + ",}[,;:"+ HYPHEN + "][" + PUNCTUATION + "]*)\\s+(.*)$");
  var RE_WORD      = new RegExp("^(.{" + MINSPLIT + ",}\\S)\\s+(.*)$");
  var RE_TRIM      = new RegExp("^\\s+|\\s+$");

  // wrap each line separately (keep line structure e.g. given in psalms)
  var LINES = verse.split(/[ ]*\n[ ]*/);
  var result = "";
  for (var i = 0, len = LINES.length; i < len; ++i) {
    var line = LINES[i];
    var wrappedLine = "";
    if (line.length <= MAXLEN) {
      // no need to wrap
      wrappedLine = line;
    }
    else {
      var rest = line;
      // cut off from front of overlong rest:
      while (rest.length > MAXLEN) {
        var part = rest.substr(0, MAXLEN);
        // scan for breaking place:
        // try break at end of clause first, then sub-clause, then at word boundary
        var Match = part.match(RE_CLAUSE)
          || part.match(RE_SUBCLAUSE)
          || part.match(RE_WORD);
        if (!Match) {
          break;
        }
        //alert("_wrap Match[1]="+Match[1]+", rest.substr="+rest.substr(Match[1].length));
        // Regexes match at least one character => loop advances
        if (wrappedLine) { wrappedLine += "\n"; }
        wrappedLine += Match[1].replace(RE_TRIM, "");
        rest = rest.substr(Match[1].length);      
      }
      wrappedLine += "\n" + rest.replace(RE_TRIM, "");
    }
    if (result) { result += "\n"; }
    result += wrappedLine;
  }
  return result;
}

Controller.prototype.receiveParolEditBibleChapter = function(eventType, isAsync, aChapter, book, chapter, verseNumberStart, verseNumberEnd) 
{
  if (isAsync) {
    this.showState("Bibeltext (Kapitel) geladen.");
  }
  var BibleTextControl = document.getElementById("parolEditText");
  var BibleRefControl  = document.getElementById("parolEditBibleRef");

  if (aChapter) {
    var text = "";
    for (var i = verseNumberStart, V = aChapter.getVerses(), chapterNumber = aChapter.getNumber(); 
         i <= verseNumberEnd && i <=  V.length; ++i) {
      var verse = aChapter.getVerse(i).getText(); // undefined if e.g. non-existing verse number
      if (verse) {
        verse = verse.replace(/<br\s*\/>/g, "\n").replace(/\n\n+/g, "\n").replace(/<em\s*>(.*?)<\/em\s*>/g, "/$1/");
        //verse = this._wrap(verse);
        if (text) { 
          text += "\n";
        }
        text += verse;
      }
    }
    if (text) {
      // disable bibleRef [only after text successfully received]
      BibleRefControl.disabled = true;
      jQuery("#btnParolEditStep2").hide();
      jQuery("#parolEditNewHint").hide();
      jQuery("#parolEditStep2").slideDown();
      BibleTextControl.value = text;
      BibleTextControl.focus();
      return;
    }
  }
  // else do not modify BibleText.value (be defensive)
  alert("Für die angegebene Bibelstelle ist kein Bibeltext vorhanden - bitte prüfen Sie die Bibelstelle.\n\n"
        + "Bitte schreiben Sie uns, wenn Sie dies für eine Fehlfunktion des BibelStudio halten!");
  BibleRefControl.focus();
}

Controller.prototype.parolEditStep2 = function()
{
  var BibleTextControl = document.getElementById("parolEditText");
  var BibleRefControl  = document.getElementById("parolEditBibleRef");

  var aBibleRef = this._parolEditGetBibleRef(); // shows hint if returning null
  if (!aBibleRef) {
    return;
  }
  // normalize bible reference (use already in hint).
  // Do not append parolQual to BibleRefControl.value:
  // parsing the value again e.g. to show the bible text would fail
  var Formatter = new Bible20.Bible.BibleRefFormatter();
  BibleRefControl.value = Formatter.Format(
    aBibleRef.getBook(), aBibleRef.getChapterNumber(), 
    aBibleRef.getVerseNumberStart(), aBibleRef.getVerseNumberEnd());

//TODO 2010-02-20 HS medium - handle multiple bibles with different bible references per parol
  var Parols = this._Model.findVerseParols(aBibleRef.getBook(), aBibleRef.getChapterNumber(), 
    aBibleRef.getVerseNumberStart(), aBibleRef.getVerseNumberEnd());
  var parolQual = aBibleRef.toString();

  if (Parols.length) {
    var texts = "--\n";
    var Map = {};
    for (i = 0, len = Parols.length; i < len; ++i) {
      var aParol = Parols[i];
      texts += aParol.getL() + "\n" + aParol.getSL() + "\n--\n";
      Map[aParol.getID()] = 1;
    }
    var hint = "Die folgenden Bibelsprüche (" + Parols.length + ") enthalten bereits Verse der Bibelstelle\n"
      + BibleRefControl.value + "\n";

    // Parol that covers 1 verse: 3 Parol, 2 verses: 6 Parol, 3 verses: 9 Parol (!!)
    var max = 3 * (aBibleRef.getVerseNumberEnd() - aBibleRef.getVerseNumberStart() + 1);
    if (Parols.length >= max) {
      alert(hint
        + "\nVermutlich ist ein weiterer Teilvers als Spruch nicht sinnvoll.\n"
        + "Bitte schreiben Sie uns, wenn Sie dies für eine Fehlfunktion des BibelStudio halten!\n\n"
        + texts);
      BibleRefControl.focus();
      return;
    }
  
    if (!confirm(hint
      + "\nBitte erstellen Sie nur dann einen weiteren Spruch, wenn er andere Teilverse als die bestehenden Sprüche enthält:\n\n"
      + texts
      + "\nMöchten Sie einen weiteren Spruch erstellen?")) {
      BibleRefControl.focus();
      return;
    }
  
    // compute new unique ID by adding numeric suffix
    var curParolQual = parolQual; 
    var index = 1;
    while (Map[curParolQual]) {
      curParolQual = parolQual + "_" + ++index;
    }
    parolQual = curParolQual;
  }

  this._ParolEditQual = parolQual;
  BibleTextControl.title = this._ParolEditQual + " " + this._Model.getBibleName();// [hint for expert]

  // do disable/hide/show [only after text successfully received]
  // do nothing e.g. for "Ps. 1,200"!
  this._Model.loadBibleChapter(aBibleRef.getBook(), aBibleRef.getChapterNumber(), 
    aBibleRef.getVerseNumberStart(), aBibleRef.getVerseNumberEnd(), "loadParolEditBibleChapter");
}

// === Bible ===

// receiveBibleChapter(eventType, aChapter, verseNumberStart, verseNumberEnd)
Controller.prototype.receiveBibleChapter = function(eventType, isAsync, aChapter, book, chapter, verseNumberStart, verseNumberEnd, bLeaveHistory) 
{
  //alert("Controller.receiveBibleChapter " + arguments);
  if (isAsync) {
    this.showState("Bibeltext (Kapitel) geladen.");
  }
  if (aChapter) {
    if (!bLeaveHistory) {
      var aBibleRef = this._BibleChapterView.getBibleRef();
      if (aBibleRef) {
        this._BibleChapterUndoStack.push(aBibleRef);
        this._BibleChapterRedoStack = [];
      }
    }
    this._BibleChapterView.update(book, chapter, aChapter);
    if (verseNumberStart) {
      this._BibleChapterView.selectVerse(verseNumberStart, verseNumberEnd, false);//bScrollIntoView, false => scroll to bottom
    }
  }
  this.updateCommands(); // ***
}

Controller.prototype.showBibleTextForParolQual = function(parolQual)
{
  var BookChV1V2 = Bible20.Bible.BibleRef.splitParolID(parolQual);
  if (BookChV1V2) {
    var $tabs = $('#tabs').tabs();
    $tabs.tabs('select', "#tabBibleText");
    this._Model.loadBibleChapter.apply(this._Model, BookChV1V2);
  }
}

Controller.prototype.showBibleTextForFirst = function()
{
  this.showBibleTextForParolQual(this._FirstPageView.getCurParolQual());
}

Controller.prototype.showBibleTextForSecond = function()
{
  this.showBibleTextForParolQual(this._SecondPageView.getCurParolQual());
}

Controller.prototype.findBibleRef = function()
{
  try {
    var Norm = new Bible20.Bible.BibleRefNormalizer();
    var aBibleRef = Norm.guess(jQuery.trim(this._BibleRef.value));
    if (!aBibleRef) {
      alert("Welche Bibelstelle meinten Sie? Bitte schreiben Sie die Bibelstelle z.B. wie \"1. Mose 4\".");
      this._BibleRef.focus();
      return;
    }
    this._Model.loadBibleChapter(aBibleRef.getBook(), aBibleRef.getChapterNumber(), 
      aBibleRef.getVerseNumberStart(), aBibleRef.getVerseNumberEnd());
  }
  catch (e) {
    alert("Fehler beim Anzeigen der Daten: " + e.name + ": " + e.message + " (findBibleRef)");
  }
}

Controller.prototype.navigateBible = function(dir)
{
  var aBibleRef = this._BibleChapterView.getBibleRef();
  if (!aBibleRef) {
    return;
  }
  var chapterNumber = aBibleRef.getChapterNumber();
  switch (dir) {
  case "prev":
    if (chapterNumber <= 1) {
      return;
    }
    --chapterNumber;
    break;
  case "next":
//TODO low 2010-03-27 HS - use info about [number of chapters in bible]
    ++chapterNumber;
    break;
  default:
    return;
  }
  this._Model.loadBibleChapter(
    aBibleRef.getBook(), chapterNumber);
  this.updateCommands(); // ***
}

Controller.prototype.navigateBibleHistory = function(dir)
{
  var aBibleRef;
  switch (dir) {
  case "prev":
    aBibleRef = this._BibleChapterUndoStack.pop();
    if (!aBibleRef) {
      return;
    }
    var oldBibleRef = this._BibleChapterView.getBibleRef();
    if (oldBibleRef) {
      this._BibleChapterRedoStack.push(oldBibleRef);
    }
    break;
  case "next":
    aBibleRef = this._BibleChapterRedoStack.pop();
    if (!aBibleRef) {
      return;
    }
    var oldBibleRef = this._BibleChapterView.getBibleRef();
    if (oldBibleRef) {
      this._BibleChapterUndoStack.push(oldBibleRef);
    }
    break;
  default:
    return;
  }
  this._Model.loadBibleChapter(
    aBibleRef.getBook(), aBibleRef.getChapterNumber(),
    aBibleRef.getVerseNumberStart(), aBibleRef.getVerseNumberEnd(), null, /*bLeaveHistory*/true);
  this.updateCommands(); // ***
}

Controller.prototype.showBibleTextForEditParol = function()
{
  var aBibleRef = this._parolEditGetBibleRef(); // shows hint if returning null
  if (!aBibleRef) {
    return;
  }
  var parolQual = aBibleRef.getBook() + aBibleRef.getChapterNumber() + "v" + aBibleRef.getVerseNumberStart();
  var end = aBibleRef.getVerseNumberEnd();
  if (end) {
    parolQual += "-" + end;
  }
  this.showBibleTextForParolQual(parolQual);
}


Controller.prototype.receiveParolEditPostEdit = function()
{
  var aParol = this._Model.getParolBox().findParol(this._ParolEditQual);
  delete this._ParolEditQual;
  this.parolEditClose();
  if (aParol) {
    var secondIndex = this._SecondPageView.findParolIndex(aParol);
    if (secondIndex) {
      this._SecondPageView.updatePage();
      this._SecondPageView.selectLineIndex(secondIndex);
    }

    this._FilteredParolView.updatePage();
    var index = this._FilteredParolView.findParolIndex(aParol);
    this.selectFilteredParolLine(index, true);
  }
  this.showState("Änderung gespeichert.");
}

Controller.prototype.receiveParolEditPostNew = function()
{
  var BibleRefControl = document.getElementById("parolEditBibleRef");
  // disable bibleRef => switch from "new" to "edit" mode
  BibleRefControl.disabled = true;

  this.parolEditClose();

  var aParol = this._Model.getParolBox().findParol(this._ParolEditQual);
  if (aParol) {
    if (this._Model.filterParolAdd(aParol)) {
      this._FilteredParolView.setFilteredParol(); // update collection data using Model
      this._FilteredParolView.updateCollection(); // update displayed collection stats
      this._FilteredParolView.updatePage();
      var index = this._FilteredParolView.findParolIndex(aParol);
      // update second view
      this.selectFilteredParolLine(index, true);
      // irritating (must assume user already sees filteredParolView)
      // this._FilteredParolView.scrollSelectionIntoView(false); // scroll new Parol to bottom of screen
    }
    this.updateCommands(); // ***
  }
  this.showState("Neuer Bibelspruch gespeichert.");
}

Controller.prototype.introOk = function(intro)
{
  if (intro && intro.match(/\n/)) {
    alert("Die einführende Zeile darf keinen Zeilenumbruch enthalten - bitte entfernen Sie diesen!");
    return false;
  }
  if (intro && intro.length > 65
      && !confirm("Die einführende Zeile sollte nicht länger als 65 Zeichen sein - wollen Sie die Zeile\n\n" + intro + "\n\nwirklich übernehmen?")) {
    return false;
  }
  return true;
}

Controller.prototype.textOk = function(text)
{
  var MAXCHARS = 500;
  if (text.length > MAXCHARS) {
    alert("Leider ist der Text zu lang für einen Bibelspruch. " 
      + MAXCHARS + " Zeichen sind erlaubt, das wäre im eingegebenen Text an dieser Stelle:\n\n"
      + text.substr(490, 10) + " | " + text.substr(500, 10));
    return false;
  }
  if (text.match(/\n[ ]*\n/)) {
    alert("Der Text darf keine Leerzeilen enthalten - bitte schreiben Sie den Text zusammenhängend.");
    return false;
  }
  return true;
}

Controller.prototype.parolEditWrap = function() 
{
  var BibleTextControl = document.getElementById("parolEditText");
  BibleTextControl.value = this._wrap(BibleTextControl.value);
}

Controller.prototype.parolEditApply = function()
{
  var BibleTextControl = document.getElementById("parolEditText");
  var aBibleRef = this._parolEditGetBibleRef(); // shows hint if returning null
  if (!aBibleRef) {
    return;// should not occur
  }
  if (BibleTextControl.value.match(/\S/)) {
    var BibleRefControl   = document.getElementById("parolEditBibleRef");
    var BibleIntroControl = document.getElementById("parolEditIntro");
    var ref   = jQuery.trim(BibleRefControl.value);
    var intro = jQuery.trim(BibleIntroControl.value);
    var text  = jQuery.trim(BibleTextControl.value);

    if (!this.introOk(intro)) {
      BibleIntroControl.focus();
      return;
    }
    if (!this.textOk(text)) {
      BibleTextControl.focus();
      return;
    }

    var aParol = new Bible20.Parol.Parol();

    aParol.setIL(intro);
    var PairsNotOk = 'Die Zeichen "//" für Hervorhebung sind nicht paarweise. Bitte verwenden Sie die Hervorhebung so:\n\n'
      + 'Es ist //ein// Gott.';

    if (!aParol.getILPairsOk()) {
      alert(PairsNotOk);
      BibleIntroControl.focus();
      return;
    }
    intro = aParol.getHtmlIL();

    aParol.setL(text);
    if (!aParol.getLPairsOk()) {
      alert(PairsNotOk);
      BibleTextControl.focus();
      return;
    }
    text = aParol.getHtmlL();
    //alert("intro=<" + intro + ">, text=<" + text + ">");

    // trim each line
    text = text.replace(/[ ]+\n/g, "\n").replace(/\n[ ]+/g, "\n");

    // now apply
    if (this._ParolEditMode == "edit") {
      this._Model.parolEditPostModified(this._ParolEditQual, intro, text, ref);
    }
    else {
      this._Model.parolEditPostNew(this._ParolEditQual, intro, text, ref);
    }
    delete this._ParolEditMode;
  }
}

Controller.prototype.parolEditClose = function()
{
  jQuery("#divParolEdit").slideUp();
  this._ParolEditMode = "";
}


// === Login ===

Controller.prototype.gotoTabLogin = function()
{
  var $tabs = $('#tabs').tabs();
  //TODO low HS 2009-12-31 using constant index of "Login" tab
  $tabs.tabs('select', "#tabLogin");
  this._btnLogin.scrollIntoView(false);// locate at screen bottom
}

Controller.prototype._toggleLogin = function(bTurnOn)
{
  // TODO low HS 2009-12-31 improvement: use CSS class to adapt all elements that are sensitive to login state
  var forLoggedIn       =  bTurnOn ? "block" : "none";
  var forLoggedInInline =  bTurnOn ? "inline" : "none";
  var forLoggedOut      = !bTurnOn ? "block" : "none";
  
  var bibleName = this._Model.getBible().getName();
  jQuery(".BibleName").each(function() { this.firstChild.data = bibleName; });
  this._BibleChapterView.showText("");

  this._btnGotoLogin.style.display = forLoggedOut;
  this._btnLogin.style.display     = forLoggedOut;
  this._btnLogout.style.display    = forLoggedIn;

  this._AddPair.style.display      = forLoggedInInline;
  this._TabFilteredParolHint.style.display = forLoggedIn;
  this._BtnDeletePair.style.display = forLoggedInInline;
  // disable/enable input controls
  this._UserName.disabled = bTurnOn;
  this._Email.disabled = bTurnOn;

  // Note about order of animation and assignments to style directly:
  // assignments run first, independent of the order of code statements,
  // because the animation runs asynchronously.
  // To force the assignment after the animation, put them into a callback of an animation.
  if (bTurnOn) {
    jQuery(".Rate").show();
    jQuery("#editParol").show(); // [editParol]
    jQuery("#Welcome").slideDown();
    jQuery("#loginHint").addClass("inactive");
  }
  else {
    jQuery(".Rate").hide();
    jQuery("#editParol").hide(); // [editParol]
    jQuery("#divParolEdit").hide();
    jQuery("#Welcome").slideUp();
    jQuery("#loginHint").removeClass("inactive");
  }
}

Controller.prototype.receiveLogin = function()
{
  // do not continue to expose user input in login tab -> make empty
  // keep user name - it is not shown elsewhere
  // keep accept license
  //this._UserName.value = ""; 
  this._Email.value = "";
  //this._AcceptLicense.checked = false;

  // forget auto-login only after successful receive, see [autologin]
  if (this._AutoLogin.checked) {
    this.setCookie("userName", this._Model.getUserName());
    this.setCookie("email", this._Model.getEmail());
  }

  this._toggleLogin(true);

  // 2010-01-11 HS: with login, automatically get Parols
  // (otherwise, it would not be clear to users that they must click "Show all" first)
  if (!this._Model.getAreAllParolLoaded()) {
    this.showState("Lade Text aller Bibelsprüche (bitte warten)...");

    if (this._LoadParolBoxTimer) {
      clearTimeout(this._LoadParolBoxTimer);
    }
    
    // set flag to remember we come from login
    this._loadParolBoxFromLogin = true;
    // install timeout to reset variable again (be defensive in case load does not arrive)
    var that = this;
    this._LoadParolBoxTimer = setTimeout(
      function() {
        that._loadParolBoxFromLogin = null;
      },
      10000);
    this._Model.loadParolBox();
  }
  else {
    this.updateCommands(); // ***
  }
}

Controller.prototype.login = function()
{
  // get data, trim leading+trailing white-space
  var userName = jQuery.trim(this._UserName.value);
  var email    = jQuery.trim(this._Email   .value);
  
  // What userName and email values are valid?
  // userName and email are transferred as URL path parts or parameters.
  // Firefox 3.5 does not mask (at least) the characters "/&|" in constructing the URL,
  // so that the resulting URL fails.
  // 2009-12-31 HS: for now, just restrict userName, and forbid some known special chars in email.
  if (!userName.match(/^[A-Za-z0-9_]{4,}$/)) {
    alert("Bitte geben Sie einen gültigen Benutzernamen an!\n\nDer Benutzername darf nur Buchstaben des lateinischen Alphabets (A-Za-z0-9_ ohne Leer- oder Sonderzeichen) enthalten und muss mindestens 4 Buchstaben lang sein.");
    this._UserName.focus();
    return;
  }
  //TODO low HS 2009-12-31 improve pattern for valid email address
  if (!email.match(/^[\w\.-]+@[\w\.-]+$/)) {
    alert("Bitte geben Sie eine gültige E-Mail-Adresse an (z.B. max@mustermann.org)!\n\nDie E-Mail-Adresse muss das '@'-Zeichen enthalten, darüber hinaus jedoch nur Zeichen, die in Wörtern vorkommen, sowie '.' (Punkt) und '-' (Minus).");
    this._Email.focus();
    return;
  }
  if (!this._AcceptLicense.checked) {
    jQuery("#acceptLicenseText").addClass("acceptLicenseTextHighlight");
    alert("Bitte stimmen Sie den Lizenzbedingungen zu!");
    this._AcceptLicense.focus();
    return;
  }
  jQuery("#acceptLicenseText").removeClass("acceptLicenseTextHighlight");

  // forget auto-login already before HTTP roundtrip, see [autoLogin]
  if (!this._AutoLogin.checked) {
    this.setCookie("userName", "");
    this.setCookie("email", "");
  }

  this._Model.login(userName, email);
}

Controller.prototype.receiveLogout = function()
{
  this._toggleLogin(false);
  this._SecondPageView.updatePage();
  this.updateCommands(); // ***
}


Controller.prototype.logout = function()
{
  // allow set/reset of auto-login at logout time, see [autoLogin]
  // do this before this._Model.logout()
  this.setCookie("userName", this._AutoLogin.checked ? this._Model.getUserName() : "");
  this.setCookie("email",    this._AutoLogin.checked ? this._Model.getEmail()    : "");

  this._Model.logout();
}


// === Control FirstPageView ===

Controller.prototype.navigateFirst = function(dir)
{
  this._FirstPageView.navigate(dir);
  this.updateCommands(); // ***
}

// === Control SecondPageView ===

// 2010-01-30 HS dropped
//Controller.prototype.navigateSecond = function(dir)
//{
//  this._SecondPageView.navigate(dir);
//}

// === Tabs ===

$(function(){
  // hide hint about Javascript
  // (moved here from Controller constructor where the hint shortly flashed up at startup in IE6)
  var jshint = document.getElementById("jshint");
  jshint.style.display = "none";

  // Tabs
  $('#tabs').tabs({
    select: function(event, ui) {
      // Image in help page is about 200K => load it only when entering the page
      if (ui.panel.id == "tabHelp") {
        var imgProxy = document.getElementById("imgProxyHelpBibleStudio");
        if (imgProxy) {
          jQuery(imgProxy).replaceWith('<img src="images/helpBibleStudio.jpg" alt="" align="right"/>');
        }
      }
      else if (ui.panel.id == "tabFirstStatus") {
        document._Controller.updateFirstStatus();
      }
    }
  });

  //hover states on the static widgets
  $('#dialog_link, ul#icons li').hover(
    function() { $(this).addClass('ui-state-hover'); }, 
    function() { $(this).removeClass('ui-state-hover'); }
  );
  


  // do not re-load bible text after tab switch!
  //$('#tabs').bind('tabsselect', function(event, ui) {
  //    // Objects available in the function context:
  //    // ui.tab     // anchor element of the selected (clicked) tab
  //    // ui.panel   // element, that contains the selected/clicked tab contents
  //    // ui.index   // zero-based index of the selected (clicked) tab
  //  if (ui.index == 1) {
  //    document._Controller.selectBiblePage();
  //  }
  //});  
});

