// ===================================================================
// BibleStudio model
// manages state for associating Parols,
// works as a (partly transparent) facade for ParolBox and PairSet etc.
//
// Terms:
//
// - Parol: formatted verses with bible reference, for multiple bible editions
// - ParolTextBox: list of Parol
//
// - Collection: list of Parol qualifiers (of first verses)
// - Series: equal to Collection, or a subset of it (slices)
//   Series are numbered starting from 1!
//
// - FilteredParol: list of Parol (matching filter)
//
// - Second: list of all verses associated with a given First verse
// - Pair: ordered pair (firstQual, secondQual) of qualifiers of associated verses
// - PairSet: set of Pair
//
// - PairRate: tuple (Yes, Maybe, No) associated with a Pair
// - PairRateSet: set of PairRate
//
// - add: add object to Model
// - del: remove object from Model
// - read: parse string into objects
// - write: compute string from objects
// - load: load string from server into objects
// - receive: asynchronously called handler of get/post
// - post: save objects to string on server


function BibleStudioModel()
{
  try {
    this._Collection  = null;
    this._Parol2First = null;
    this._Series  = null;
    this._CurSeries = null;
    this._NumSeries = 40; // 40 * 10 = 400 first verses
    this._LenSeries = 10;
    this._ParolTextBox = null;
    this._AreAllParolLoaded = false;

    this._PairSet  = null;

    this._FilteredParol  = null;
    this._Filter = "";

    this._collectionName = null;
    this._bibleName      = null;
    this._userName = "";
    this._email = "";

    // Helper (create once, not in init)
    this._Observable = new Observable();
    this._ParolBoxReader = new Bible20.Parol.ParolBoxReader();
    this._ZefReader = new Bible20.Bible.ZefReader();

    this.init();
  }
  catch (e) {
    alert("BibleStudioModel: " + e);
  }
}

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

BibleStudioModel.prototype.init = function()
{
  //TODO low HS 2009-12-31 using constant biblename "UnsereLuther" for bible text
  this._BibleUnsereLuther   = new Bible20.Bible.Bible("UnsereLuther"); // no login
  this._BibleSchlachter2000 = new Bible20.Bible.Bible("Schlachter2000"); // with login
  this._Bible = this._BibleUnsereLuther; // no login
  this._ParolTextBox = new Bible20.Parol.ParolTextBox();
  //TODO low HS 2009-12-31 using constant biblename "Schlachter2000" for parol text
  this._ParolTextBox.addBible("Schlachter2000", "setcurrent");

  this._Collection = null;
  this._Parol2First = {};
  this._Series  = null;
  this._PairSet  = new Bible20.Pair.PairSet();
  this._FilteredParol = []
  this._Filter = "";
}

BibleStudioModel.prototype._getBible2Url = function(relPath)
{
  // domain: cf. Flanagan p. 301
  return "http://" + document.domain + relPath;
}

BibleStudioModel.prototype._getBibleStudioUrl = function(relPath)
{
  // domain: cf. Flanagan p. 301
  return this._getBible2Url("/service/BibleStudio/" + relPath);
}


// === Load Initial ===

BibleStudioModel.prototype.readParolBox = function(dom)
{
  try {
    this._ParolBoxReader.readEntries(this._ParolTextBox, dom);
    // alert("XXXX readParolBox: readEntries done");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (readParolBox)");
  }
}

BibleStudioModel.prototype.readCollection = function(dom)
{
  try {
    // Collection
    var Coll = dom.getElementsByTagName("parolcollection");
    var collText = Coll[0].firstChild.data;
    var QUAL = collText.match(/^([A-Za-z0-9_-]+)$/mg); // "Dt5v21_F", "1P2v22-23"
    this._Collection = [];
    this._Parol2First = {};
    for (var i = 0, len = QUAL.length; i < len; ++i) {
//TODO low 2010-02-28 HS - collection built based on existence of Parol in bible
      var aParol = this.getParolBox().findParol(QUAL[i]);
      if (aParol) {
        this._Collection.push(aParol);
        this._Parol2First[aParol.getID()] = aParol;
      }
    }
    // sort collection by Bible (assume BibleRef has been set in each Parol, see [BibleRef])
    this._Collection.sort(function(aParol1, aParol2) {
      return Bible20.Bible.BibleRef.compare(aParol1.getBibleRef(), aParol2.getBibleRef());
    });
    
    this.computeSeries(this._CurSeries);
    //alert("done Collection => " + this._Collection.length);
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (readCollection)");
  }
}

BibleStudioModel.prototype._readUserPairRate = function(domPair, aPair)
{
  var s = domPair.getAttribute("rate");
  var rate = null;
  var comment = null;
  if (s != null) {
    rate = Number(s);
    if (domPair.firstChild) {
      comment = domPair.firstChild.data;
    }
  }
  // else: reset in case 'rate' attribute is missing!
  aPair.setUserRate(rate, comment);
}

BibleStudioModel.prototype.readPairs = function(dom)
{
  try {
    // PairSet
    //
    //
    // <pairs user='HSteeb'>
    // <first qual='Gn1v31'>
    // <pair second='Jr3v1' yes='0' maybe='0' no='0'/>
    // <pair second='Jr27v5' yes='0' maybe='0' no='0' rate='1'>my comment...</pair>
    // </first>
    // </pairs>

    var Infos = dom.getElementsByTagName("info");
    if (Infos.length) {
      this.notify("readPairsInfo", Infos[0].firstChild.data);
      return false;
    }
    var Pairs = dom.getElementsByTagName("pairs");
    var FirstArr = Pairs[0].getElementsByTagName("first");
    //alert("readPairs for " + FirstArr.length + " first verses");
    var aPairSet = this._PairSet;
    for (var i = 0, len = FirstArr.length; i < len; ++i) {
      var First = FirstArr[i];
      var parolQual1 = First.getAttribute("qual");
      var SecondArr = First.getElementsByTagName("pair");
      
      for (var j = 0, len2 = SecondArr.length; j < len2; ++j) {
        var Second = SecondArr[j];
        var parolQual2 = Second.getAttribute("second");
        var aPair = aPairSet.addFirstSecond(parolQual1, parolQual2);
        aPair.setRate(
          Number(Second.getAttribute("yes")),
          Number(Second.getAttribute("maybe")),
          Number(Second.getAttribute("no"))
          );
        this._readUserPairRate(Second, aPair);
      }
    }
    aPairSet.updateParolCache(this._ParolTextBox);
    return true;
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (readPairs)");
    return false;
  }
}

BibleStudioModel.prototype.readUserPairRateList = function(dom)
{
  try {
    //<userpairrates user='HSteeb'>
    //<pairrate first='Gn2v15' second='Ps96v1' rate='-1'/>
    //</userpairrates>

    var UserPairRates = dom.getElementsByTagName("pairrate");
    var aPairSet = this._PairSet;
    for (var i = 0, len = UserPairRates.length; i < len; ++i) {
      var UserPairRate = UserPairRates[i];
      var parolQual1 = UserPairRate.getAttribute("first");
      var parolQual2 = UserPairRate.getAttribute("second");
      if (parolQual1 && parolQual2) {
        var aPair = aPairSet.addFirstSecond(parolQual1, parolQual2);
        this._readUserPairRate(UserPairRate, aPair);
      }
    }
    aPairSet.updateParolCache(this._ParolTextBox);
    return true;
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (readUserPairRateList)");
    return false;
  }
}

function _receiveQueryInit()
{
  try {
    // --- reset data of previous load ---
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      var userName = "";
      var email    = "";
      var bOk      = false;
      try { 
        // for auto-login: handle userName/email, as in [Login]
        var domUsernames = dom.getElementsByTagName("username");
        if (domUsernames) {
          userName = domUsernames[0].firstChild.data;
          email    = dom.getElementsByTagName("email")[0].firstChild.data;
          // be defensive:
          // values must be equal to the ones sent in loadInitial, 
          // otherwise reset the stored values
          if (userName && email && userName == aModel._userName && email == aModel._email) {
            aModel._Bible = aModel._BibleSchlachter2000;
            aModel.notify("loginDone");
            bOk = true;
          }
        }
      }
      catch (e) {
        // silently ignore violation of assumptions for user info input
      }
      if (!bOk) {
        aModel._userName = "";
        aModel._email = "";
      }

      aModel.readParolBox(dom);
      var root = dom.getElementsByTagName("paroltext")[0];
      // getAttribute returns string!
      if (root && root.getAttribute("all") == 1) {
        aModel._AreAllParolLoaded = true;
      }

      aModel.readCollection(dom);
      // readPairs:
      // - updates pairs cache
      // - also reads user pair rates if these are included in pairs list
      //   (no need to call aModel.readUserPairRateList(dom), as done in login)
      aModel.readPairs(dom);
    }
    aModel.notify("loadInitialDone");

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

BibleStudioModel.prototype.loadInitial = function()
{
  try {
    if (!this._bibleName) return;
    if (!this._collectionName) return;

    this.notify("loadInitialStart");
    var _onError = function() {
      alert("Fehler beim Abrufen der Daten (HTTP: "+ this.xhr.status + " + " + this.xhr.statusText + ") (loadInitial)");
    }
    var url = this._getBibleStudioUrl("Query/Init"
        + "?bible=" + this._bibleName 
        + "&collection=" + this._collectionName
        // TODO low HS 2009-12-31 using constant "Gn3v8" for first parol of collection
        + "&parolQual=" + "Gn3v8"
      );
    if (this._userName && this._email) { // not normally set at startup (except if taken from cookie)
      url += "&user=" + this._userName + "&email=" + this._email;
    }
    else {
      // be defensive: let empty parameter override potential inconsistent cookie
      url += "&user=&email=";
    }
    var Client = new Http.Client({onLoad: _receiveQueryInit, onError: _onError});
    Client._Model = this;
    Client.get(url);
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (loadInitial)");
  }
}


// === Bible ===

BibleStudioModel.prototype.getBibleName = function()
{
  return this._bibleName;
}
 
BibleStudioModel.prototype.setBibleName = function(name)
{
  this._bibleName = name;
}
 
// loadBibleChapter:
// if (book, chapter) is already stored in this._Bible as aChapter, 
//   directly notifies "loadBibleChapter",
// otherwise HTTP gets the chapter,
//   then notifies "loadBibleChapter".
// Notify parameters: bool isAsync, aChapter, verseNumberStart, verseNumberEnd.
// Possible improvement: use Arguments object 
//   to pass around any parameters instead of verseNumberStart, verseNumberEnd.
BibleStudioModel.prototype.loadBibleChapter = function(book, chapter, verseNumberStart, verseNumberEnd, eventType, bLeaveHistory)
{
  try {
    if (!eventType) {
      eventType = "loadBibleChapter";
    }
    //alert("XXX loadBibleChapter=" + book + ", " + chapter + ", " + verseNumberStart + ", "+ verseNumberEnd);
    if (!this._bibleName) return;
    if (!book) return;

    var aChapter = this.getBible().getBook(book).getChapter(chapter);
    if (aChapter != Bible20.Bible.Chapter.NULL) {
      this.notify(eventType, false, aChapter, book, chapter, verseNumberStart, verseNumberEnd, bLeaveHistory);
      return;
    }

    var _onError = function() {
//TODO low 2010-03-27 HS - use info about [number of chapters in bible] => no need to ignore load errors any more
      // silently ignore "chapter ... not found"
      if (!this.xhr.responseText.match(/E400:1022/)) {
        this._Model.notify("errorLoadBibleChapter", this.xhr);
      }
    }
    var _onSuccess = function() {
      try {
        var aClient = this;
        var aModel  = this._Model;
        aModel._ZefReader.read(aClient.xhr.responseText, aModel._Bible);
        aChapter = aModel.getBible().getBook(book).getChapter(chapter);
        aModel.notify(eventType, true, aChapter, book, chapter, verseNumberStart, verseNumberEnd, bLeaveHistory);
      }
      catch (e) {
        alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (loadBibleChapter)");
      }
    }
    // /service/Bible/Text/Schlachter2000/Gn1
// TODO high HS 2010-02-20 BibleStudioModel.loadBibleChapter - using parolBoxListID to protect bible text
    var path = "/service/Bible/Text/" + this._Bible.getName() +  "/" + book + chapter + "?parolBoxListID=tSfIvBQ5Medit2010";
    var url = this._getBible2Url(path);
    var Client = new Http.Client({onLoad: _onSuccess, onError: _onError});
    Client._Model = this;
    Client.get(url);
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (loadBibleChapter)");
  }
}

BibleStudioModel.prototype.getBible = function()
{
  return this._Bible;
}
 


// === Collection ===

BibleStudioModel.prototype.getCollection = function()
{
  return this._Collection;
}
 
// getParolInCollection(parolQual)
// if parolQual in this.getCollection(), returns the Parol,
// otherwise undefined.
BibleStudioModel.prototype.getParolInCollection = function(parolQual)
{
  return this._Parol2First[parolQual];
}
 
BibleStudioModel.prototype.getCollectionName = function()
{
  return this._collectionName;
}
 
BibleStudioModel.prototype.setCollectionName = function(coll)
{
  this._collectionName = coll;
}
 

// === ParolEdit ===

function _receiveParolEditPost(event)
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      aModel.readParolBox(dom);
      aModel.notify(event);
      return;
    }
    aModel.notify("errorParolEditPost");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (_receiveParolEditPost)");
  }
}

function _receiveParolEditPostEdit()
{
  return _receiveParolEditPost.call(this, "parolEditPostEdit");
}

function _receiveParolEditPostNew()
{
  return _receiveParolEditPost.call(this, "parolEditPostNew");
}

BibleStudioModel.prototype._parolEditPost = function(parolQual, intro, text, ref, mode)
{
  if (!this._bibleName) return;
  if (!this._userName) return;
  
  var _onError = function() {
    this._Model.notify("errorParolEditPost", this.xhr);
  }
  var path = "Parol/" + parolQual 
    + "?mode=" + mode
    + "&bible=" + this._bibleName 
    + "&user="  + this._userName 
    + "&intro=" + encodeURIComponent(intro)
    + "&text="  + encodeURIComponent(text) 
    + "&ref="   + encodeURIComponent(ref);
  var url = this._getBibleStudioUrl(path);
  var Client = new Http.Client({
      onLoad: mode == "new" ? _receiveParolEditPostNew : _receiveParolEditPostEdit, 
      onError: _onError
    });
  Client._Model = this;
  Client.post(url);
}

BibleStudioModel.prototype.parolEditPostModified = function(parolQual, intro, text, ref)
{
  return this._parolEditPost(parolQual, intro, text, ref, "edit");
}

BibleStudioModel.prototype.parolEditPostNew = function(parolQual, intro, text, ref)
{
  return this._parolEditPost(parolQual, intro, text, ref, "new");
}

function _receiveParolEditDelete()
{
  try {
    var aClient = this;
    var aModel  = this._Model;
    var dom = aClient.xhr.responseXML;

    var parolQual;
    var deletedRows;
    var parolRows;

    if (dom && dom.childNodes.length) {
      var Data = dom.getElementsByTagName("deletedparol")[0];
      if (Data) {
        parolQual    = Data.getAttribute("parol");
        nDeletedRows = Number(Data.getAttribute("deletedRows"));
        nParolRows   = Number(Data.getAttribute("parolRows"));
        //alert("Model _receiveParolEditDelete: parolQual="+parolQual+", nDeletedRows="+nDeletedRows+", nParolRows="+nParolRows);

        // service/BibleStudio/Parol.php:
        // $nDeletedRows > 0, $nParolRows > 0: n/a
        // $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
        if (!nParolRows) {
          aModel._PairSet.delParol(parolQual);
          var aParol = aModel._ParolTextBox.remove(parolQual);
          if (aParol) {
            aModel.filterParolDelete(aParol);
          }
        }
      }
    }
    aModel.notify("parolEditDelete", parolQual, nDeletedRows, nParolRows);
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (_receiveParolEditDelete)");
  }
}

BibleStudioModel.prototype.parolEditDelete = function(parolQual)
{
  if (!this._userName) return;
  
  var _onError = function() {
    //alert("Fehler beim Anmelden (HTTP: "+ this.xhr.status + ")");
    this._Model.notify("errorParolEditDelete", this.xhr);
  }
  var path = "Parol/" + parolQual 
    + "?mode=delete"
    + "&user="  + this._userName;
  var url = this._getBibleStudioUrl(path);
  var Client = new Http.Client({onLoad: _receiveParolEditDelete, onError: _onError});
  Client._Model = this;
  Client.post(url);
}

BibleStudioModel.prototype.findVerseParols = function(book, chapter, verseNumberStart, verseNumberEnd)
{
//TODO low 2010-02-25 HS verse number for which bible? cf. BibleChapterView.js - aParolBox.findChapterParols()
  return this._ParolTextBox.findVerseParols("Schlachter2000", book, chapter, verseNumberStart, verseNumberEnd);
}

// === Login ===

function _receiveLogin()
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      // Handle [Login] (cf. _receiveQueryInit())
      var userName = dom.getElementsByTagName("username")[0].firstChild.data;
      var email    = dom.getElementsByTagName("email")[0].firstChild.data;
      if (userName && email) {
        aModel._userName = userName;
        aModel._email = email;
        aModel._Bible = aModel._BibleSchlachter2000;
        aModel.notify("loginDone");
        aModel.loadUserPairRateList();
        return;
      }
    }
    aModel.notify("errorLogin");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (_receiveLogin)");
  }
}

BibleStudioModel.prototype.login = function(userName, email)
{
  var _onError = function() {
    //alert("Fehler beim Anmelden (HTTP: "+ this.xhr.status + ")");
    this._Model.notify("errorLogin", this.xhr);
  }
  var path = "User/" + userName +  "?email=" + email;
  var url = this._getBibleStudioUrl(path);
  var Client = new Http.Client({onLoad: _receiveLogin, onError: _onError});
  Client._Model = this;
  Client.post(url);
}

BibleStudioModel.prototype.logout = function()
{
  // TODO low HS 2009-12-31 for now, no login on server => no need to reset info on the server
  this._userName = "";
  this._Bible = this._BibleUnsereLuther;
  this._email = "";
  this._PairSet.resetUserPairRates();
  this.notify("logoutDone");
}

// getUserName:
// user is logged in iff. getUserName() is true
BibleStudioModel.prototype.getUserName = function()
{
  return this._userName;
}

BibleStudioModel.prototype.getEmail = function()
{
  return this._email;
}

BibleStudioModel.prototype.setUserName = function(userName)
{
  this._userName = userName;
}

BibleStudioModel.prototype.setEmail = function(email)
{
  this._email = email;
}


// === Series ===

// computeSeries(series)
// - series == null => use entire collection
// - otherwise: 0 < series <= this._NumSeries
BibleStudioModel.prototype.computeSeries = function(series)
{
  try {
    if (series == null) {
      this._Series = this._Collection;
    }
    else {
      series = series - 0; // force number!
      if (series < 1 || series > this._NumSeries) {
        return;
      }
      // compute the slice
      this._Series = [];
      for (var i = series - 1, len = this._Collection.length; i < len; i += this._NumSeries) {
        this._Series.push(this._Collection[i]);
      }
    }
    this._CurSeries = series;
  }
  catch (e) {
    alert("Fehler beim Berechnen der Daten: " + e.name + ": " + e.message + " (computeSeries)");
  }
}

BibleStudioModel.prototype.findParolQualIndexInSeries = function(parolQual)
{
  try {
    for (var i = 0, A = this._Series, len = A && A.length; i < len; ++i) {
      var aParol = A[i];
      if (aParol && aParol.getID() == parolQual) {
        return i;
      }
    }
    return null;
  }
  catch (e) {
    alert("Fehler beim Berechnen der Daten: " + e.name + ": " + e.message + " (findParolQualIndexInSeries)");
  }
}


// getCurSeries():
// 1-based series, or null (if series denotes entire Collection)
BibleStudioModel.prototype.getCurSeries = function()
{
  return this._CurSeries;
}

BibleStudioModel.prototype.getNumSeries = function()
{
  return this._NumSeries;
}

BibleStudioModel.prototype.getSeries = function()
{
  return this._Series;
}
 
 

// === ParolBox ===


function _receiveAllSecondForFirstQualList()
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      aModel.readParolBox(dom);
      aModel._PairSet.updateParolCache(aModel._ParolTextBox);
    }
    aModel.notify("loadAllSecondForFirstQualList");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (receiveParolBox)");
  }
}

BibleStudioModel.prototype.loadParolBoxAllSecondForFirstQualList = function(parolQual)
{
  if (!this._bibleName) return;
  if (!this._ParolTextBox) {
    this._ParolTextBox = new Bible20.Parol.ParolTextBox();
    //this._ParolBoxPerBible[this._bibleName] = this._ParolTextBox;
  }

  // be defensive
  if (this._AreAllParolLoaded) {
    this.notify("loadAllSecondForFirstQualList");
    return;
  }

  var _onError = function() {
    alert("Fehler beim Abrufen der Daten (HTTP: "+ this.xhr.status + ")");
  }
  var path = "Query/SecondTextListForFirstQual/" + this._bibleName +  "?parolQual=" + parolQual;
  var url = this._getBibleStudioUrl(path);
  var Client = new Http.Client({onLoad: _receiveAllSecondForFirstQualList, onError: _onError});
  Client._Model = this;
  Client.get(url);
}

function _receiveAllParol()
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      aModel.readParolBox(dom);
      aModel._AreAllParolLoaded = true;
      aModel._PairSet.updateParolCache(aModel._ParolTextBox);
    }
    aModel.notify("loadAllParol");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (_receiveAllParol)");
  }
}

BibleStudioModel.prototype.loadParolBox = function()
{
  if (!this._bibleName) return;
  if (!this._ParolTextBox) {
    this._ParolTextBox = new Bible20.Parol.ParolTextBox();
    //this._ParolBoxPerBible[this._bibleName] = this._ParolTextBox;
  }

  var _onError = function() {
    alert("Fehler beim Abrufen der Daten (HTTP: "+ this.xhr.status + ")");
  }
  var path = "ParolTextList/" + this._bibleName;
  var url = this._getBibleStudioUrl(path);
  var Client = new Http.Client({onLoad: _receiveAllParol, onError: _onError});
  Client._Model = this;
  Client.get(url);
}

BibleStudioModel.prototype.getParolBox = function()
{
  return this._ParolTextBox;
}
 
BibleStudioModel.prototype.getAreAllParolLoaded = function()
{
  return this._AreAllParolLoaded;
}

// === Pairs ===

BibleStudioModel.prototype.getPairSet = function()
{
  return this._PairSet;
}

function _receiveDeletePair()
{
  try {
    var aClient = this;
    var aModel  = this._Model;
    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      var aPair = dom.getElementsByTagName("deletedpair")[0];
      if (aPair) {
        var parolQual1 = aPair.getAttribute("first");
        var parolQual2 = aPair.getAttribute("second");
        aModel._PairSet.delFirstSecond(parolQual1, parolQual2);
      }
    }
    aModel.notify("postDeletePair");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (receiveDeletePair)");
  }
}

BibleStudioModel.prototype.deletePair = function(firstParolQual, secondParolQual)
{
  try {
    if (!this._userName) return;
    
    var aPair = this._PairSet.findFirstSecond(firstParolQual, secondParolQual);
    if (aPair) {
      var _onError = function() {
        this._Model.notify("errorDeletePair", this.xhr);
      }
      var path = "PairList?user=" + this._userName + "&"
        + "parolQual1=" + firstParolQual + "&"
        + "parolQual2=" + secondParolQual;
      var url = this._getBibleStudioUrl(path);
      var Client = new Http.Client({onLoad: _receiveDeletePair, onError: _onError});
      Client._Model = this;
      // 2010-01-11 HS: why does Konqueror 4.2.2 send HTTP "DELETE" as "GET"????
      // It works fine with FF 3.5.3 and Opera 10.10.
      // Client.Delete(url);
      // TODO 2010-01-11 low HS - hack for HTTP DELETE not working under Konqueror 4.2.2, see [HTTP-DELETE]
      Client.post(url + "&mode=DELETE");
    }
  }
  catch (e) {
    alert("BibleStudioModel.deletePair: " + e);
  }
}

function _receivePostUserPairRate()
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      if (!aModel.readPairs(dom)) {
        return; // notify done by readPairs
      }
    }
    aModel.notify("postUserPairRate");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (receivePostUserPairRate)");
  }
}

BibleStudioModel.prototype.postUserPairRate = function(firstParolQual, secondParolQual, rate)
{
  try {
    if (!this._userName) return;
    
    var aPair = this._PairSet.findFirstSecond(firstParolQual, secondParolQual);
    if (aPair) {
      var _onError = function() {
        alert("Fehler beim Speichern der Bewertung (HTTP: "+ this.xhr.status + ") (postUserPairRate)");
      }
      if (rate == null) {
        rate = ""; // for transmission
      }
      var oldRate = aPair.getUserRate();
      if (oldRate == null) {
        oldRate = "";
      }
      var comment = "";
      // TODO low HS 2009-12-31 - comment for user pair rate not yet supported
      var path = "UserPairRate/" + this._userName + "/"
        + firstParolQual + "/"
        + secondParolQual
        // TODO low HS 2009-12-31 protect comment for user pair rate (as soon as it is support)
        + "?oldRate=" + oldRate + "&rate=" + rate + "&comment=" + comment;
      var url = this._getBibleStudioUrl(path);
      var Client = new Http.Client({onLoad: _receivePostUserPairRate, onError: _onError});
      Client._Model = this;
      Client.post(url);
    }
  }
  catch (e) {
    alert("BibleStudioModel.postUserPairRate: " + e);
  }
}

function _receiveLoadUserPairRateList()
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      if (!aModel.readUserPairRateList(dom)) {
        return;
      }
    }
    aModel.notify("loadUserPairRateList");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (receiveLoadserPairRateList)");
  }
}

BibleStudioModel.prototype.loadUserPairRateList = function()
{
  try {
    if (!this._userName) return;
    
    var _onError = function() {
      alert("Fehler beim Abrufen Ihrer Bewertungen (HTTP: "+ this.xhr.status + ") (loadUserPairRateList)");
    }
    var path = "UserPairRateList/" + this._userName;
    var url = this._getBibleStudioUrl(path);
    var Client = new Http.Client({onLoad: _receiveLoadUserPairRateList, onError: _onError});
    Client._Model = this;
    Client.get(url);
  }
  catch (e) {
    alert("BibleStudioModel.loadUserPairRateList: " + e);
  }
}



function _receiveAddPair()
{
  try {
    var aClient = this;
    var aModel  = this._Model;

    var dom = aClient.xhr.responseXML;
    if (dom && dom.childNodes.length) {
      if (!aModel.readPairs(dom)) {
        return; // notify done by readPairs
      }
    }
    aModel.notify("postAddPair");
  }
  catch (e) {
    alert("Fehler beim Laden der Daten: " + e.name + ": " + e.message + " (_receiveAddPair)");
  }
}


BibleStudioModel.prototype.addPair = function(firstParol, secondParol)
{
  try {
    if (!this._userName) return;
    
    var aPair = this._PairSet.findFirstSecond(firstParol.getID(), secondParol.getID());
    if (aPair) {
      this.notify("postAddPairFound");
    }
    else {      
      var _onError = function() {
        alert("Fehler beim Speichern des neuen Spruchpaares (HTTP: "+ this.xhr.status + ") (addPair)");
      }
      var path = "PairList?user=" + this._userName
        + "&parolQual1=" + firstParol.getID()
        + "&parolQual2=" + secondParol.getID();
      var url = this._getBibleStudioUrl(path);
      var Client = new Http.Client({onLoad: _receiveAddPair, onError: _onError});
      Client._Model = this;
      Client.post(url);
    }
  }
  catch (e) {
    alert("BibleStudioModel.addPair: " + e);
  }
}

// --- FilteredParol ---

BibleStudioModel.prototype.filterParol = function(filter)
{
  try {
    this._FilteredParol = [];
    this._Filter = filter;
    for (var i = 0, Box = this._ParolTextBox, len = Box.getLength(); i < len; ++i) {
      var aParol = Box.at(i);
      //TODO low HS 2009-12-31 improvement for parol filtering: cache getHtml() (=computed value) for speedup
      // if no filter set, display full list
      if (!filter || aParol.getHtml().match(filter)) {
        this._FilteredParol.push(aParol);
      }
    }
    this._FilteredParol.sort(
      function(a, b) {
        return Bible20.Bible.BibleRef.compare(a.getBibleRef(), b.getBibleRef());
      }
    );
  }
  catch (e) {
    alert("BibleStudioModel.filterParol: " + e);
  }
}

// add aParol into list
// return true iff. added
BibleStudioModel.prototype.filterParolAdd = function(aParol)
{
  try {
    // be defensive: search first
    for (var i = 0, Arr = this._FilteredParol, len = Arr.length; i < len; ++i) {
      if (Arr[i] == aParol) {
        // aParol already contained in list
        return false;
      }
    }
    // insert in front
    this._FilteredParol.splice(0, 0, aParol);
    return true;
  }
  catch (e) {
    alert("BibleStudioModel.filterParolAdd: " + e);
  }
}

// delete aParol from the list
// return true iff. deleted
BibleStudioModel.prototype.filterParolDelete = function(aParol)
{
  try {
    for (var i = 0, Arr = this._FilteredParol, len = Arr.length; i < len; ++i) {
      if (Arr[i] == aParol) {
        this._FilteredParol.splice(i, 1);
        return true;
      }
    }
    return false;
  }
  catch (e) {
    alert("BibleStudioModel.filterParolDelete: " + e);
  }
}

BibleStudioModel.prototype.getFilter = function()
{
  return this._Filter;
}

// getFilteredParol()
// returns array of Parol
BibleStudioModel.prototype.getFilteredParol = function()
{
  return this._FilteredParol;
}


// --- Observable ---

BibleStudioModel.prototype.observe = function(eventType, obj, handler)
{
  try {
    // notify will call handler in context of object "obj"
    return this._Observable.observe(eventType, obj, handler);
  }
  catch (e) {
    alert("BibleStudioModel.observe: " + e);
  }
}

BibleStudioModel.prototype.notify = function(eventType)
{
  try {
//    return this._Observable.notify(eventType, arguments);// first argument: eventType, + all additional ones
    return this._Observable.notify.apply(this._Observable, arguments);// first argument: eventType, + all additional ones
  }
  catch (e) {
    alert("BibleStudioModel.notify(" + eventType + "): " + e);
  }
}

