// ===================================================================
// Class for storing Parol for multiple bible editions,
// accessed by Parol qualifier and bible edition,
// with direct access to Parol of "current" bible edition.

var Bible20;
if (!Bible20) {
  Bible20 = {};
}
else if (typeof Bible20 != "object") {
  throw new Error("Bible20 already exists and is not an object");
}

if (!Bible20.Parol) {
  Bible20.Parol = {};
}
else if (typeof Bible20.Parol != "object") {
  throw new Error("Bible20.Parol already exists and is not an object");
}


Bible20.Parol.ParolTextBox = function()
{
  this.init();
}

Bible20.Parol.ParolTextBox.prototype = {
  init: function()
  {
    this._Bibles = []; // bible qualifiers
    this._Bible2Index = {}; // this._Bibles[this._Bible2Index[bible]] = bible
    this._curBible = null;
    this._curBibleIndex = null;

    // this._List[this._Map[aParol.getID()]][bibleIndex] == aParol
    this._Qual = []; // this._Qual[i] is array of parolQual (parallel to this._List)
    this._List = []; // this._List[i] is array of Parol indexed by bible index
    this._Map = {};  

    this._Categories = new Object(); // assume unique terms
  },

  getLength: function()
  {
    return this._List.length;
  },

  toString: function()
  {
    return "ParolTextBox (" + this._List.length + ")";
  },

  // --- Bible ---

  getCurBible: function()
  {
    return this._curBible || "";
  },

  setCurBible: function(bible)
  {
    if (bible == null) throw new Error("ParolTextBox.setCurBible: 'bible' undefined");
    var index = this._Bible2Index[bible];
    if (null == index) throw new Error("ParolTextBox.setCurBible: bible '" + bible + "' not in box");
    this._curBible = bible;
    this._curBibleIndex = index;
  },

  // EXPENSIVE!
  addBible: function(bible, bSetCurrent)
  {
    if (bible == null) throw new Error("ParolTextBox.addBible: 'bible' undefined");
    var index = this._Bible2Index[bible];
    if (null == index) {
      // remember bible
      index = this._Bible2Index[bible] = this._Bibles.length;
      this._Bibles.push(bible);

      // extend existing Parol arrays
      for (var i = 0, len = this._List.length; i < len; ++i) {
        this._List[i].length = index; // extends array
      }
    }
    if (bSetCurrent) {
      this._curBible = bible;
      this._curBibleIndex = index;
      //alert("XXX addBible(" + bible + ") => curBibleIndex="+this._curBibleIndex);
    }
  },

  // var Categories = aParolBox.getCategories();
  // Categories[term] == value
  getCategories: function()
  {
    return this._Categories;
  },

  getCategory: function(term)
  {
    return this._Categories[term];
  },

  addCategory: function(term, value)
  {
//TODO low 2009-05-08 HS - distinguish value null/undefined -> keeping/deleting term
    //Log.debug("ParolBox.addCategory(" + term + ", " + value + ")");//TODO
    this._Categories[term] = value;
  },

  // --- Get index / Parol ---

  // at(index[, bible]) 
  // bible defaults to bible passed to setCurBible
  at: function(index, bible)
  {
    var ParolArr = this._List[index];
    if (ParolArr) {
      var bibleIndex = bible ? this._Bible2Index[bible] : this._curBibleIndex;
      if (bibleIndex == null) {
        //alert("XXX at(" + index + ", " + bible + ") => curBibleIndex="+this._curBibleIndex);
        throw new Error("ParolTextBox.at: bibleIndex undefined for bible parameter '" + bible + "'");
      }
      return ParolArr[bibleIndex];
    }
    return null;
  },

  findParolIndex: function(id)
  {
    if (id == null) throw new Error("ParolTextBox.findParolIndex: id undefined");
    var index = this._Map[id];
    return index != undefined ? index : null;
  },

  findParol: function(id, bible)
  {
    if (id == null) throw new Error("ParolTextBox.findParol: id undefined");
    var index = this._Map[id];
    if (index != null) {
      return this.at(index, bible);
    }
    return null;
  },

  // findChapterParols:
  // returns array of aParol where
  // - aParol has text from bible
  // - aParol.getID() is in book + chapter
  // Note: this searches book+chapter in the given bible,
  //   which may be different from the norm bible ref stored in this._Qual!
  // EXPENSIVE!
  findChapterParols: function(bible, book, chapter)
  {
    var bibleIndex = bible ? this._Bible2Index[bible] : this._curBibleIndex;
    if (bibleIndex == null) {
      //alert("XXX findChapterParol(" + index + ", " + bible + ") => curBibleIndex="+this._curBibleIndex);
      throw new Error("ParolTextBox.findChapterParol: bibleIndex undefined for bible parameter '" + bible + "'");
    }
    var RE = new RegExp("^" + book + chapter + "v"); // add "v" to avoid match of "Gn1" with parol like "Gn12v4"
    var Result = [];
    for (var i = 0, len = this._List.length; i < len; ++i) {
      var ParolArr = this._List[i];
      if (ParolArr) {
        var aParol = ParolArr[bibleIndex];
//TODO 2010-02-13 HS medium - handle multiple bibles with different bible references per parol
        // match for the given bible
        if (aParol && RE.exec(aParol.getID())) {
          Result.push(aParol);
        }
      }
    }
    //alert("XXX findChapterParols(" + bible + ", " + book + " ch. " + chapter + ") => " + Result.length + " elements");
    return Result;
  },

  // findVerseParols:
  // returns array of aParol where
  // - aParol has text from bible
  // - aParol.getID() intersects with book + chapter + verses
  // Note: this searches book+chapter+verse in the given bible,
  //   which may be different from the norm bible ref stored in this._Qual!
  // EXPENSIVE!
  findVerseParols: function(bible, book, chapter, verseNumberStart, verseNumberEnd)
  {
    var Parols = this.findChapterParols(bible, book, chapter);
    var RE = new RegExp("v(\\d+)(?:-(\\d+))?");
    var Result = [];
    if (!verseNumberEnd) {
      verseNumberEnd = verseNumberStart;
    }
    for (var i = 0, len = Parols.length; i < len; ++i) {
      var aParol = Parols[i];
      var result = RE.exec(aParol.getID());
      if (result != null) {
        var start = Number(result[1]);
        var end   = result[2] ? Number(result[2]) : start;
        // check on range intersection
        if (verseNumberStart <= end && verseNumberEnd >= start) {
          Result.push(aParol);
        }
      }
    }
    return Result;
  },

  // === modification ===

  // append(aParol)
  // aParol must not yet be contained in box (not checked!)
  // returns aParol
  append: function(aParol)
  {
    var parolQual = aParol.getID();
    this._Map[parolQual] = this._List.length;

    var ParolArr = Array(this._Bibles.length);
    ParolArr[this._curBibleIndex] = aParol;
    this._List.push(ParolArr);
    this._Qual.push(parolQual);

    return aParol;
  },

  // appendNew(id)
  // returns new Parol inserted having id
  appendNew: function(id)
  {
    aParol = new Bible20.Parol.Parol().setID(id);
    return this.append(aParol);
  },

  // findAppendParol(id)
  // returns Parol found or inserted
  findAppendParol: function(id)
  {
    return this.findParol(id) || this.appendNew(id);
  },

  // remove:
  // if id was contained in this, deletes it and returns aParol (which may be null for the current bible),
  // otherwise return null
  // 2009-04-07 HS:
  //   using function name "delete" leads to an error in IE 6:
  ///  "Type error: object expected" $&%&%$!!
  // EXPENSIVE!
  remove: function(id)
  {
    var index = this._Map[id];
    if (index == null) {
      return null;
    }

    // delete from this._List
    var ParolArr = this._List[index];
    this._List.splice(index, 1);
    this._Qual.splice(index, 1);

    // adapt list indexes in this._Map for elements that followed aParol in this._List and this._Qual
    for (var i = index, len = this._Qual.length; i < len; ++i) {
      this._Map[this._Qual[i]] = i;
    }

    // delete entry for parol from this._Map
    delete this._Map[id];

    return ParolArr[this._curBibleIndex];
  }
}