// ===================================================================
/** 
  @fileoverview BibleRefNormalizer class
  @requires Bible20.Bible.BookNames The indexing of bible book names (for #compareTo).

  @author Helmut Steeb hs2010@bible.net (insert current year)
**/
var Bible20;
if (!Bible20) {
  Bible20 = {};
}
else if (typeof Bible20 != "object") {
  throw new Error("Bible20 already exists and is not an object");
}

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


/**
  @class Bible20.Bible.BibleRefNormalizer - class for converting free-text bible reference to a "normalized" Parol qualifier like "Gn1v3".

  @constructor
  Constructs a new Bible20.Bible.BibleRefNormalizer object.

  @requires Bible20.Bible.BookNames The indexing of bible book names (for #compareTo).
**/

Bible20.Bible.BibleRefNormalizer = function()
{
  try {
    var MAP = Bible20.Bible.BibleRefNormalizer._MAP;
    this._MAP = {};
    for (var p in MAP) {
      var norm = MAP[p];

      // enter patterns from MAP into this._MAP
      // this._MAP[initial] = [RE1, RE2, ...]
      // where
      // - initial is the initial character of user input,
      // - if RE1 matches user input,
      // - RE1.source is additionally used as prefix string 
      //   for a new RegEx to match chapter and verse numbers
      // NOTE: since initial is used case sensitively,
      // user input like "phm" is not matched,
      // whereas "1mo" is matched (agains RegExp with "i").
      // This must be adapted for handling multiple bibles, anyway.

      var initial = p.substr(0, 1);
      if (!this._MAP[initial]) {
        this._MAP[initial] = [];
      }
      // this._MAP[initial]:
      // array of (RegEx, pattern, normBook) for this initial
      this._MAP[initial].push([new RegExp("^" + p, "i"), norm]);

      // goodie: add norm book names from _MAP (only if len>=2):
      // to avoid misinterpretation, match only before word boundary,
      // otherwise e.g. "Phm" would match wrong book name "Ph"
      if (norm.length > 1) {
        initial = norm.substr(0, 1);
        if (!this._MAP[initial]) {
          this._MAP[initial] = [];
        }
        this._MAP[initial].push([new RegExp("^" + norm + "\\b", "i"), norm]);
      }
    }
  }
  catch (e) {
    alert("Bible20.Bible.BibleRefNormalizer: " + e);
  }
}

Bible20.Bible.BibleRefNormalizer._MAP = {
// TODO 2010-01-08 low HS - multilanguage bible book names (TBD add "Genesis" even for German)
  // NOTE: the first character of each expression is used to restrict the set of regexes that must be applied
  // => do not use e.g. parenthesis as first character!
  //OT
  "1\\.? ?Mo": "Gn",  "Ge?n": "Gn",
  "2\\.? ?Mo": "Ex",  "Ex": "Ex",
  "3\\.? ?Mo": "Lv",  "Le?v": "Lv",
  "4\\.? ?Mo": "Nu",  "Nu": "Nu",
  "5\\.? ?Mo": "Dt",  "D(?:eu)?t": "Dt",
  "Jos": "Jos",  
  "Richt": "Jdc",
  "Rut": "Rth",
  "1\\.? ?Sa?m": "1Sm",
  "2\\.? ?Sa?m": "2Sm",
  "1\\.? ?K\u00f6": "1Rg", // IE6 fails on literal "ö" in string
  "2\\.? ?K\u00f6": "2Rg",
  "1\\.? ?Chr": "1Chr",
  "2\\.? ?Chr": "2Chr",
  "Esr": "Esr",
  "Neh": "Neh",
  "Est": "Esth",
  "Hio?": "Job",
  "Ps": "Ps",
  "Spr": "Prv",
  "Pr": "Eccl",
  "Hoh": "Ct" ,
  "Jes": "Is",
  "Jer": "Jr",
  "Kla?g": "Thr",
  "Hes": "Ez" ,
  "Da?n": "Dn" ,
  "Hos": "Hos",
  "Joe": "Joel",
  "Am": "Am" ,
  "Ob": "Ob" ,
  "Jon": "Jon",
  "Mich": "Mch",
  "Nah": "Nah",
  "Hab": "Hab",
  "Zep": "Zph",
  "Ha?g": "Hgg",
  "Sach": "Zch",
  "Mal": "Ml" ,
  // NT
  "Ma?t": "Mt",
  "Mk" : "Mc",    "Mar" : "Mc",
  "Lu?k"  : "L",
  "Joh?" : "J",
  "Apg": "Act",   "Apostelg": "Act",
  "R\u00f6?m": "R",
  "1\\.? ?Kor": "1K",
  "2\\.? ?Kor": "2K",
  "Gal": "G",
  "Eph": "E",
  "Phili": "Ph", // prefix "Phil" would also match "Philemon"!
  "Kol": "Kol",
  "1\\.? ?Th?ess": "1Th",
  "2\\.? ?Th?ess": "2Th",
  "1\\.? ?Tim": "1T",
  "2\\.? ?Tim": "2T",
  "Tit": "Tt",
  "Phi?l?e?m": "Phm",
  "Heb": "H",
  "Jak": "Jc",
  "1\\.? ?Pe?t?": "1P",
  "2\\.? ?Pe?t?": "2P",
  "1\\.? ?Jo?h?": "1J",
  "2\\.? ?Jo?h?": "2J",
  "3\\.? ?Jo?h?": "3J",
  "Ju?d": "Jd", // Jd = Juda, not Judges
  "Off": "Ap",  "Ap": "Ap"
};

Bible20.Bible.BibleRefNormalizer.prototype.toString = function()
{
  return "BibleRefNormalizer";
}

// Tuple = _findBook(bibleRef)
// Tuple[0] = pattern from this._MAP
// Tuple[1] = norm book name ("Gn")
// return first match!
Bible20.Bible.BibleRefNormalizer.prototype._findBook = function(bibleRef)
{
  var initial = bibleRef.substr(0, 1);
  var Tuples = this._MAP[initial] || [];
  for (var i = 0, len = Tuples.length; i < len; ++i) {
    var RegEx = Tuples[i][0];
    if (bibleRef.match(RegEx)) {
      return Tuples[i];
    }
  }
  return null;
}

// guess:
// be nice, detect at least the book name and use defaults for the rest.
// Returns Bible20.Bible.BibleRef or null
Bible20.Bible.BibleRefNormalizer.prototype.guess = function(bibleRef)
{
  try {
    if (!bibleRef) { 
      return null;
    }
    var Tuple = this._findBook(bibleRef);
    if (!Tuple || !Tuple[1]) {// book
      return null;
    }
    var pattern = Tuple[0].source;
    var book    = Tuple[1];

    var chapter = null;
    var verseNumberStart = null;
    var verseNumberEnd = null;
    var isSingle = Bible20.Bible.BookNames.isSingleChapterBookName(book);

    // match 1 - 3 subsequent numbers (ignore separator between number 2 and 3)
    var numberPattern = new RegExp(pattern + ".*?(\\d+)(?:\\D+(\\d+)(?:\\D+(?:(\\d+))?)?)?", "i");
    var res = bibleRef.match(numberPattern);

    // --- extract ---
    if (res) {
      // Phm 1   => chapter 1, verse 1
      // Phm 1-2 => chapter 1, verse 1-2
      // Phm 2   => chapter 1, verse 2
      if (isSingle && (res[1] != "1" || res[2])) {
        chapter = 1;
        verseNumberStart = Number(res[1]);
        verseNumberEnd   = Number(res[2]);
      }
      // Gn 2     => chapter 2
      // Gn 2,3   => chapter 2, verse 3
      // Gn 2,3-4 => chapter 2, verse 3-4
      else {
        chapter          = Number(res[1]);
        verseNumberStart = Number(res[2]);
        verseNumberEnd   = Number(res[3]);
      }
    }

    // --- sanitize ---
    if (!chapter) {
      chapter = 1;
    }
    if (!verseNumberStart) {
      verseNumberStart = 1;
      verseNumberEnd = null;
    }
    // interprete second number as verseNumberEnd, no way to represent non-consecutive verses
    else if (verseNumberEnd && verseNumberStart > verseNumberEnd) {
      verseNumberEnd = null;
    }
    //alert("You mean: " + book + chapter + "v" + verseNumberStart + sep + verseNumberEnd);
    return new Bible20.Bible.BibleRef(book, chapter, verseNumberStart, verseNumberEnd);
  }
  catch (e) {
    alert("Fehler beim Untersuchen der Bibelstelle: " + e.name + ": " + e.message + " (guess)");
  }
}

// normalize:
// be strict, detect bible reference unambiguously, or return null.
// Single chapter books are accepted with our without chapter number "1".
// Chapter/verse separators accepted e.g.: "2,3-5", "2,3.4", "2,3+4", "2:3.4", "2:3+4"
// NOT: "2:3,4" (sorry, comma needed for "2,3-5"), "2,3+5" (verses must be consecutive) 
// Returns Bible20.Bible.BibleRef or null
Bible20.Bible.BibleRefNormalizer.prototype.normalize = function(bibleRef)
{
  try {
    if (!bibleRef) { 
      return null;
    }
    var Tuple = this._findBook(bibleRef);
    if (!Tuple || !Tuple[1]) {// book
      //alert("normalize: book not found");
      return null;
    }
    var pattern = Tuple[0].source;
    var book    = Tuple[1];
    //alert("normalize: found book=" + book);

    var chapter = null;
    var verseNumberStart = null;
    var sep = null;
    var verseNumberEnd = null;
    var isSingle = Bible20.Bible.BookNames.isSingleChapterBookName(book);
    //alert("single " + book + ": " + isSingle);
    // "P_" = with parentheses
    var P_DIG  = "(\\d+)";
    var NONDIG = "\\D*?";
    var WS     = "\\s*";
    var CH2V   = "[,:]";
    var P_V2V  = "([\\.+-])";
    if (isSingle) {
      var numberPattern = new RegExp(pattern + NONDIG + P_DIG + WS + "(?:" + P_V2V + WS + P_DIG + WS + ")?$", "i");
      var res = bibleRef.match(numberPattern);
      // --- extract ---
      if (res) {
        // Phm 1   => chapter 1, verse 1
        // Phm 1-2 => chapter 1, verse 1-2
        // Phm 2   => chapter 1, verse 2
        chapter          = 1;
        verseNumberStart = Number(res[1]);
        sep              = res[2];
        verseNumberEnd   = Number(res[3]);
        //alert("is single, match: " + book + chapter + "v" + verseNumberStart + sep + verseNumberEnd);
      }
    }
    // try ref including chapter number also for single chapter book that failed on verse ref only
    if (!chapter)  {
      var numberPattern = new RegExp(pattern + NONDIG + P_DIG + WS + CH2V + WS + P_DIG + WS + "(?:" + P_V2V + WS + P_DIG + WS + ")?$", "i");

      var res = bibleRef.match(numberPattern);
      // --- extract ---
      if (res) {
        // Gn 2     => chapter 2
        // Gn 2,3   => chapter 2, verse 3
        // Gn 2,3-4 => chapter 2, verse 3-4
        chapter          = Number(res[1]);
        verseNumberStart = Number(res[2]);
        sep              = res[3];
        verseNumberEnd   = Number(res[4]);

        if (isSingle && chapter != 1) {
          //alert("normalize: single book with chapter != 1: ch="+ chapter);
          return null;
        }
        //alert("match: " + book + chapter + "v" + verseNumberStart + sep + verseNumberEnd);
      }
      else {
        //alert("NO match");
        return null;
      }
    }

    // --- sanitize ---
    if (!chapter || !verseNumberStart) {
      //alert("normalize: chapter or verseNumberStart missing");
      return null;
    }
    // sep given <=> verseNumberEnd given
    if (!!sep != !!verseNumberEnd) {
      //alert("normalize: sep/verseNumberEnd not paired: " + sep + " " + verseNumberEnd);
      return null;
    }
    if (sep && sep.match(/[\.+]/) && verseNumberStart + 1 != verseNumberEnd) {
      //alert("normalize: verseNumberEnd not consecutive for [.+]: " + sep + " " + verseNumberEnd);
      // only consecutive verses, no "Gn 3,1.4" or "Gn 3,1+4"
      return null;
    }
    if (verseNumberEnd && verseNumberStart >= verseNumberEnd) {
      //alert("normalize: verseNumberEnd not >= verseNumberStart: " + verseNumberEnd);
      return null;
    }

    //alert("Got: isSingle=" + isSingle + ": " + book + chapter + "v" + verseNumberStart + sep + verseNumberEnd);
    return new Bible20.Bible.BibleRef(book, chapter, verseNumberStart, verseNumberEnd);
  }
  catch (e) {
    alert("Fehler beim Untersuchen der Bibelstelle: " + e.name + ": " + e.message + " (normalize)");
  }
}

