// ===================================================================
// Class for storing data of one Parol.

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.Parol = function ()
{
  try {
    this._title   = "";
    this._id      = null;
    this._canonicalid = "";
    this._updated = "";
    this._IL      = "";
    this._L       = "";
    this._SL      = "";
    this._Categories = new Object(); // assume unique terms
  }
  catch (e) {
    alert("Parol: " + e);
  }
}

Bible20.Parol.Parol.NULL = new Bible20.Parol.Parol();

// class method: computeCanonicalID

Bible20.Parol.Parol.computeCanonicalID = function(id)
{
  var sep = "___";
  var pos = id.indexOf(sep);
  return pos >= 0 ? id.substr(pos + sep.length) : id;
}

// --- formatting helper

Bible20.Parol.Parol.prototype =
{
  // clearOptional:
  // used e.g. before existing Parol is re-read.
  // Must initialize all properties that are optional
  // and would erroneously be kept from previous read
  clearOptional: function()
  {
    this._author = "";
    this._IL = "";
    this._ILPairsOk = 1;
    this._LPairsOk = 1;
    this._Categories = new Object(); // assume unique terms
  },

  _wrap: function(value, start, end)
  {
    // value as from file may contain "<em>...</em>"
    return value.replace(/<em>/g, start).replace(/<\/em>/g, end);
  },

  // --- getter ---

  getTitle: function()
  {
    return this._title;
  },

  getID: function()
  {
    return this._id;
  },

  getCanonicalID: function()
  {
    return this._canonicalid;
  },

  getUpdated: function()
  {
    return this._updated;
  },

  getAuthor: function()
  {
    return this._author;
  },

  hasIL: function()
  {
    return !!this._IL;
  },

  getIL: function()
  {
    return this._wrap(this._IL, "", "");
  },

  getL: function()
  {
    return this._wrap(this._L, "", "");
  },

  getLOneLine: function()
  {
    return this._wrap(this._L, "", "").replace(/\n/g, " ");
  },

  getSL: function()
  {
    return this._SL;
  },


  // --- get rich ("there is //one// God")

  getRichIL: function()
  {
    return this._wrap(this._IL, "//", "//");
  },

  getRichL: function()
  {
    return this._wrap(this._L, "//", "//");
  },

  getRichOneLine: function()
  {
    var s = this._IL ? ("[" + this.getRichIL() + "] ") : "";
    return s + this.getRichL().replace(/\n/g, " ");
  },

  // --- get html ("there is <em>one</em> God")

  getHtmlIL: function()
  {
    return this._IL;
  },

  getHtmlL: function()
  {
    return this._L;
  },

  getHtml: function()
  {
    var s = this._IL ? (this._IL + " "): "";
    return s + this._L + " " + this._SL;
  },

  // --- get "//" pairs status

  getILPairsOk: function()
  {
    return this._ILPairsOk;
  },

  getLPairsOk: function()
  {
    return this._LPairsOk;
  },



  // === Categories ===

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

  getCategory: function(term)
  {
    // 2009-05-30 HS (Firefox 2.0.x): this produces console warning
    //  "reference to undefined property this._Categories[term]"
    // remedy :-(( return term in this._Categories ? this._Categories[term] : undefined;
    return this._Categories[term];
  },

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


  // === modification ===

  setID: function(text)
  {
    this._id = text;
    this._canonicalid = Bible20.Parol.Parol.computeCanonicalID(text);
    return this;
  },

  setTitle: function(text)
  {
    this._title = text;
    return this;
  },

  // see ParolBoxModel!
  setUpdated: function(updated)
  {
    this._updated = updated;
    return this;
  },

  setAuthor: function(name)
  {
    this._author = name;
    return this;
  },


  // _buildHtml:
  // interpretes "rich" text containing "//" and builds HTML text containing "<em>";
  // e.g. for text == "there is //one// God"
  //               => "there is <em>one</em> God"
  _buildHtml: function(text, status)
  {
    try {
      var nextIndex = 0;
      var result;
      var pattern = /\/\//g;
      var bStarted = false;
      var emText = "";

      var append = function(start, end)
      {
        if (end > start) {
          var fragment = text.substr(start, end - start);
          if (!bStarted) {
            emText += fragment;
          }
          else {
            emText += "<em>" + fragment + "</em>";
          }
        }
      };

      // Concept:
      //   loop over all "//" in text and process the fragment before this "//",
      //   after the loop handle fragment past the last "//".
      //   If number of "//" is odd, insert last one as text, set error indication.
      //
      // Example:
      //    012345678901234567
      //   "pre//in//post//err"
      //
      // loop #      :    1     2     3  (after loop)
      // nextIndex   :    0     5     9   15    15
      // result.index:    3     7    13    -     -
      // appends     :  pre  (in)  post   //   err
      //
      // where "(in)" means "in" put under an "<em>" element

  //TODO low 2008-09-10 HS: Parol._buildHtml (used for Parol editing): this algorithm allows line breaks within an <em> area - ok for now.
      while ((result = pattern.exec(text)) != null) {
        // append text before '//'
        // (top-level text for odd occurrence of '//', <em> text for even one)
        append(nextIndex, result.index);
        nextIndex = pattern.lastIndex;
        bStarted = !bStarted;
      }
      status.pairsOk = !bStarted;
      if (bStarted) {
        // append final non-paired '//' as text
        append(nextIndex - 2, nextIndex);
        bStarted = false;
      }
      // append text (entire text if no '//' contained, or text after last '//')
      append(nextIndex, text.length);
      return emText;
    }
    catch (e) {
      alert("Parol._buildHtml: " + e);
    }
  },

  setIL: function(text)
  {
    try {
      var status = { pairsOk: 1 };
      this._IL = this._buildHtml(text, status);
      this._ILPairsOk = status.pairsOk;
      return this;
    }
    catch (e) {
      alert("Parol.setIL: " + e);
    }
  },

  setL: function(text)
  {
    try {
      if (!text) text = " "; // avoid empty text
      var status = { pairsOk: 1 };
      this._L = this._buildHtml(text, status);
      this._LPairsOk = status.pairsOk;
      return this;
    }
    catch (e) {
      alert("Parol.setL: " + e);
    }
  },

  setSL: function(text)
  {
    this._SL = text;
    return this;
  },
  
  // [BibleRef] cache for BibleRef:
  // this._BibleRef
  getBibleRef: function()
  {
    return this._BibleRef;
  },
  
  setBibleRef: function(aBibleRef)
  {
    this._BibleRef = aBibleRef;
    return this;
  }  
}

