// ===================================================================
// PairSet model
// $Id: PairSet.js 3967 2009-11-16 20:47:29Z helmut $

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();
      this._Map = new Object(); // maps (string representation of Pair) to Pair
    }
    catch (e) {
      alert("PairSet.init: " + e);
    }
  },

  _key: function(firstID, secondID)
  {
    // must correspond with _keyFirstSecond()!
    return firstID + "\t" + secondID;
  },

  _keyFirstSecond: function(First, Second)
  {
    // must correspond with _key()!
    return First.getID() + "\t" + Second.getID();
  },

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

  // _findIndexFirstSecond:
  // returns index in this._List so that
  // - this._List[index] == Pair(First, Second), OR
  // - this._List[index] is the insertion index for Pair(First, Second)
  // sorted ascending by First.getID(), Second.getID()
  _findIndexFirstSecond: function(First, Second)
  {
    var firstID = First.getID();
    var secondID = Second.getID();
    for (var i = 0, L = this._List, len = L.length; i < len; ++i) {
      var curFirstID = L[i].getFirst().getID();
      var curSecondID = L[i].getSecond().getID();
      if (curFirstID == firstID) {
        if (curSecondID >= secondID) {
          return i;
        }
      }
      else if (curFirstID > firstID) {
        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 ID pair (in the order first, second) 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 ID pair (First, Second) is contained in this,
  // null otherwise

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

  // hasFirstSecondID:
  // returns true iff. the ID pair (firstID, secondID) is contained in this,
  // null otherwise

  hasFirstSecondID: function(firstID, secondID)
  {
    try {
      return null != this._Map[this._key(firstID, secondID)];
    }
    catch (e) {
      alert("PairSet.hasFirstSecond: " + e);
    }
  },

  countSecondForFirstID: function(firstID)
  {
    try {
  //TODO low 2009-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().getID() == firstID) {
          ++count;
        }
      }
      return count;
    }
    catch (e) {
      alert("PairSet.countSecondForFirstID: " + e);
    }
  },

  // findSecondForFirstID:
  // returns an array of Parol P with all ID pairs (firstID, P.getID()) are contained in this,
  // maybe empty.

  findSecondForFirstID: function(firstID)
  {
    try {
  //TODO low 2009-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().getID() == firstID) {
          Res.push(aPair.getSecond());
        }
      }
      return Res;
    }
    catch (e) {
      alert("PairSet.findSecondForFirstID: " + 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(First, Second):
  // adds and returns a new pair with First and Second
  addFirstSecond: function(First, Second)
  {
    try {
      var key = this._keyFirstSecond(First, Second);
      var aPair = this._findKey(key);
      if (aPair == null) {
        aPair = new Bible20.Pair.Pair(First, Second);
        this._Map[key] = aPair;
        var index = this._findIndexFirstSecond(aPair.getFirst(), aPair.getSecond());
        this._List.splice(index, 0, aPair);
      }
      return aPair;
    }
    catch (e) {
      alert("PairSet.addFirstSecond: " + e);
    }
  },

  //_rebuildList: function()
  //{
  //  try {
  //    this._List = new Array();
  ////TODO medium 2008-12-27 HS - sort by bible reference?
  ////TODO medium 2009-02-05 HS - Improve performance?
  //    var keys = new Array;
  //    for (var key in this._Map) {
  //      keys.push(key);
  //    },
  //    keys.sort();
  //    for (var i = 0; i < keys.length; ++i) {
  //      this._List.push(this._Map[keys[i]]);
  //    },
  //  },
  //  catch (e) {
  //    alert("PairSet._rebuildList: " + e);
  //  },
  //}

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

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

  // delParol:
  // deletes all pairs that have aParol as either First or as Second
  // returns the number of deleted pairs.
  delParol: function(aParol)
  {
    try {
      var id = aParol.getID();
      var delCount = 0;
      // loop backwards since we may delete list elements
      for (var L = this._List, i = L.length - 1; i >= 0; --i) {
        var curFirstID  = L[i].getFirst().getID();
        var curSecondID = L[i].getSecond().getID();
        if (id == curFirstID || id == curSecondID) {
          var key = this._key(curFirstID, curSecondID);
          delete this._Map[key];
          this._List.splice(i, 1);
          ++delCount;
        }
      }
      return delCount;
    }
    catch (e) {
      alert("PairSet.delParol: " + e);
    }
  }
}