// ===================================================================
// Class for managing a set of Bible20.Pair.Pair().

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

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


Bible20.Pair.PairSet = function()
{
  this.init();
}

Bible20.Pair.PairSet.prototype =
{
  toString: function()
  {
    return "PairSet";
  },

  init: function()
  {
    try {
      this._List = new Array(); // owns the Pairs
      this._Map = new Object(); // maps (string representation of Pair) to Pair
      this._IsSecond = {}; // maps parolQual to bool (= exists P.secondQual==parolQual)
    }
    catch (e) {
      alert("PairSet.init: " + e);
    }
  },

  _keyFirstSecond: function(firstQual, secondQual)
  {
    return firstQual + "\t" + secondQual;
  },

  _findKey: function(key)
  {
    return this._Map[key];
  },

  // _findIndexFirstSecond:
  // returns index in this._List so that
  // - this._List[index] == Pair(firstQual, secondQual), OR
  // - this._List[index] is the insertion index for Pair(firstQual, secondQual)
  // sorted ascending by firstQual, secondQual
  _findIndexFirstSecond: function(firstQual, secondQual)
  {
    for (var i = 0, L = this._List, len = L.length; i < len; ++i) {
      var curFirstQual  = L[i].getFirst();
      var curSecondQual = L[i].getSecond();
      if (curFirstQual == firstQual) {
        if (curSecondQual >= secondQual) {
          return i;
        }
      }
      else if (curFirstQual > firstQual) {
        return i;
      }
    }
    return this._List.length;
  },

  // --- Access ---

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

  at: function(index)
  {
    return (0 <= index && index < this._List.length) ? this._List[index] : null;
  },

  // hasPair:
  // returns true iff. the qualifier pair of aPair is contained in this,
  // null otherwise

  hasPair: function(aPair)
  {
    try {
      return null != this._Map[this._keyFirstSecond(aPair.getFirst(), aPair.getSecond())];
    }
    catch (e) {
      alert("PairSet.hasPair: " + e);
    }
  },

  // hasFirstSecond:
  // returns true iff. the pair (firstQual, secondQual) is contained in this,
  // null otherwise

  hasFirstSecond: function(firstQual, secondQual)
  {
    try {
      return null != this._Map[this._keyFirstSecond(firstQual, secondQual)];
    }
    catch (e) {
      alert("PairSet.hasFirstSecond: " + e);
    }
  },

  findFirstSecond: function(firstQual, secondQual)
  {
    try {
      return this._Map[this._keyFirstSecond(firstQual, secondQual)];
    }
    catch (e) {
      alert("PairSet.findFirstSecond: " + e);
    }
  },

  countSecondForFirst: function(firstQual)
  {
    try {
  //TODO low 2010-02-11 HS - speedup (use _Map...?)
      var count = 0;
      for (var i = 0, L = this._List, len = L.length; i < len; ++i) {
        if (L[i].getFirst() == firstQual) {
          ++count;
        }
      }
      return count;
    }
    catch (e) {
      alert("PairSet.countSecondForFirst: " + e);
    }
  },

  // findSecondPerFirst:
  // returns an object with keys firstQual and values array of Pairs P contained in this with P.getFirst() == firstQual
  // EXPENSIVE!

  findSecondPerFirst: function()
  {
    try {
  //TODO low 2010-03-13 HS - speedup (use _Map...?)
      var Res = new Object();
      for (var i = 0, L = this._List, len = L.length; i < len; ++i) {
        var aPair = L[i];
        var firstQual = aPair.getFirst();
        var Arr = Res[firstQual];
        if (!Arr) {
          Arr = [];
          Res[firstQual] = Arr;
        }
        Arr.push(aPair);
      }
      return Res;
    }
    catch (e) {
      alert("PairSet.findSecondPerFirst: " + e);
    }
  },

  // findSecondForFirst:
  // returns an array of Pairs P contained in this with P.getFirst() == firstQual
  // maybe empty.
  // EXPENSIVE!

  findSecondForFirst: function(firstQual)
  {
    try {
  //TODO low 2010-02-11 HS - speedup (use _Map...?)
      var Res = new Array();
      for (var i = 0, L = this._List, len = L.length; i < len; ++i) {
        var aPair = L[i];
        if (aPair.getFirst() == firstQual) {
          Res.push(aPair);
        }
      }
      return Res;
    }
    catch (e) {
      alert("PairSet.findSecondForFirst: " + e);
    }
  },

  // hasFirstForSecond:
  // returns true iff there is a Pair P contained in this with P.getSecond() == secondQual

  hasFirstForSecond: function(secondQual)
  {
    try {
      return this._IsSecond[secondQual];
    }
    catch (e) {
      alert("PairSet.hasFirstForSecond: " + e);
    }
  },


  // === Modification ===

  addPair: function(aPair)
  {
    try {
      var key = this._keyFirstSecond(aPair.getFirst(), aPair.getSecond());
      if (this._findKey(key) == null) {
        this._Map[key] = aPair;
        var index = this._findIndexFirstSecond(aPair.getFirst(), aPair.getSecond());
        this._List.splice(index, 0, aPair);
      }
      return this;
    }
    catch (e) {
      alert("PairSet.addPair: " + e);
    }
  },

  // addFirstSecond(firstQual, secondQual):
  // returns an existing or adds and returns a new pair with firstQual and secondQual
  addFirstSecond: function(firstQual, secondQual)
  {
    try {
      var key = this._keyFirstSecond(firstQual, secondQual);
      var aPair = this._findKey(key);
      if (aPair == null) {
        aPair = new Bible20.Pair.Pair(firstQual, secondQual);
        this._Map[key] = aPair;
        this._IsSecond[secondQual] = true;
        var index = this._findIndexFirstSecond(aPair.getFirst(), aPair.getSecond());
        this._List.splice(index, 0, aPair);
      }
      return aPair;
    }
    catch (e) {
      alert("PairSet.addFirstSecond: " + e);
    }
  },

  // EXPENSIVE!
  _updateForSecond: function(secondQual)
  {
//TODO low 2010-02-22 HS - speedup (use _Map...?)
    var secondOccurs = false;
    for (var i = 0, L = this._List, len = L.length; i < len; ++i) {
      if (L[i].getSecond() == secondQual) {
        secondOccurs = true;
        break;
      }
    }
    if (!secondOccurs) {
      delete this._IsSecond[secondQual];
    }
  },

  // EXPENSIVE!
  delPair: function(aPair)
  {
    try {
      var firstQual = aPair.getFirst();
      var secondQual = aPair.getSecond();
      var index = this._findIndexFirstSecond(firstQual, secondQual);
      if (index != null) {
        var key = this._keyFirstSecond(firstQual, secondQual);
        delete this._Map[key];
        this._List.splice(index, 1);
        this._updateForSecond(secondQual);
      }
      return this;
    }
    catch (e) {
      alert("PairSet.delPair: " + e);
    }
  },

  // EXPENSIVE!
  delFirstSecond: function(firstQual, secondQual)
  {
    try {
      var index = this._findIndexFirstSecond(firstQual, secondQual);
      if (index != null) {
        var key = this._keyFirstSecond(firstQual, secondQual);
        delete this._Map[key];
        this._List.splice(index, 1);
        this._updateForSecond(secondQual);
      }
      return this;
    }
    catch (e) {
      alert("PairSet.delFirstSecond: " + e);
    }
  },

  // delParol:
  // deletes all pairs that have parolQual as either firstQual or as secondQual
  // returns the number of deleted pairs.
  // EXPENSIVE!
  delParol: function(parolQual)
  {
    try {
      var delCount = 0;
      // loop backwards since we may delete list elements
      this._IsSecond = {};
      for (var L = this._List, i = L.length - 1; i >= 0; --i) {
        var curFirstQual  = L[i].getFirst();
        var curSecondQual = L[i].getSecond();
        if (parolQual == curFirstQual || parolQual == curSecondQual) {
          var key = this._key(curFirstQual, curSecondQual);
          delete this._Map[key];
          this._List.splice(i, 1);
          ++delCount;
        }
        else {
          this._IsSecond[curSecondQual] = true;
        }
      }
      return delCount;
    }
    catch (e) {
      alert("PairSet.delParol: " + e);
    }
  },

  // EXPENSIVE!
  resetUserPairRates: function()
  {
    try {
      for (var L = this._List, i = L.length - 1; i >= 0; --i) {
        L[i].setUserRate("", "");
      }
    }
    catch (e) {
      alert("PairSet.resetUserPairRates: " + e);
    }
  },

  // EXPENSIVE!
  updateParolCache: function(aParolBox)
  {
    // compute Pair...:
    // this._firstParol
    // this._secondParol
    // this._firstBibleRef
    // this._secondBibleRef
    for (var L = this._List, i = L.length - 1; i >= 0; --i) {
      var parolQual1 = L[i].getFirst();
      var parolQual2 = L[i].getSecond();
      var aParol1 = aParolBox.findParol(parolQual1);
      if (aParol1) {
        L[i]._firstParol = aParol1;
        L[i]._firstBibleRef = aParol1.getBibleRef(); // see [BibleRef]
      }
      var aParol2 = aParolBox.findParol(parolQual2);
      if (aParol2) {
        L[i]._secondParol = aParol2;
        L[i]._secondBibleRef = aParol2.getBibleRef(); // see [BibleRef]
      }
    }
  }
}
