//******************************************************************
//***********     TUB support/manipulation routines      ***********
//******************************************************************
/*
 * The pcTub.js code is provided as part of the Phone Matrix Service
 * Agreement and is classified as PrivateCall code.  It may be used
 * by the DSP while the Service Agreement is in force.
 *
 * Copyright (c) Phone Matrix Inc, 2003-2004.
 */





var gsTubErr    = "";


// ------------------- Tub manipulation functions --------------------------
function makeTub() {
  return new TUB()
}

function makeTubFromStr(sStr) {
  return new TUB(sStr)
}

function tubAdd(oTub,sKey,oObj) {
  oTub.add(sKey,oObj)
}

function makeTubArray(oTub) {
  var oArr = [oTub];
  return oArr;
}

function newTubArray() {
  return new Array();
}

function tubArrayAdd(oArr,oTub) {
  oArr[oArr.length] = oTub;
}

function extendTubArray(oArr,oTub,nIX) {
  oArr[nIX] = oTub;
  return oArr;
}

function getArrayElement(oArr,nEl) {
  return oArr[nEl];
}

function getArrayCount(oArr) {
  return oArr.length;
}

// --------------------- Tub retrieval functions ----------------------------

// Extract inner Tub from array referenced by sKey
function getInnerTub(oTub,sKey,nTub) {
  var oObj = oTub.getKeyValue(sKey);
  if (oObj instanceof Array) {
    return oObj[nTub];
  }
  throw "expected Tub array for parm "+sKey;
}

// Extract inner Tub count from array referenced by sKey
function getTubCount(oTub,sKey,nTub) {
  var oObj = oTub.getKeyValue(sKey);
  if (oObj instanceof Array) {
    return oObj.length
  }
  throw "expected Tub array for parm "+sKey;
}

// --------------------- Tub Inspection functions ----------------------------
function isFaultTub(oTub){
  oFault = oTub.getKeyValue("Fault");
  if (oFault == null) {
    gsFault = null;
    return false;
  }
  if (oFault instanceof TUB){
    gsFault = oFault.toText();
    return true;
  }
  if (oFault instanceof String){
    gsFault = oFault;
    return true;
  }
  gsFault = "Unexpected type of Fault object";
  return true;
}

// ------------------- Tub Transformation functions --------------------------

function tubToString(oTub) {
  return oTub.toText()
}

function getTubFromString(sTub) {
  var oTub
  oTub = new TUB(sTub);
  return oTub;
}


// Return Tub as a Hex String
function getHexTub(oT){return getHexString(oT.toText());}

// creates Tub from Hex string (Converse of getHexTub)
function getTubFromHex(sTubAsHex){
  var oTub
  oTub = new TUB(getUnHexString(sTubAsHex));
  return oTub;
}


// ----------------------- Local Private Routines ----------------------------
function getHexString(sStr) {
  var sHex = "0123456789ABCDEF";
  var sAns = "";
  for(var i=0;i<sStr.length;i++) {
    var s = sStr.charCodeAt(i);
    var nNib1 = (s-0) & 0x0F;
    var nNib2 = ((s-0)>>4) & 0x0F;
    sAns += sHex.substring(nNib2,nNib2+1)+sHex.substring(nNib1,nNib1+1);
  }
  return sAns;
}

function getUnHexString(sStr) {
  var sAns = "";
  for(var i=0;i<sStr.length;i+=2) {
    var s1 = sStr.charCodeAt(i);
    var s2 = sStr.charCodeAt(i+1);
    sAns += String.fromCharCode((getUnHexChar(s1)<<4) + (getUnHexChar(s2)));
  }
  return sAns;
}

function getUnHexChar(c) {
  if ((c >= 0x30) && (c <= 0x39)) return c & 0x0F;
  return (c & 0x0F) + 9; // 0x41 thru ox4F (a -> f or A -> F
}



//******************************************************************
//****************** TUB class and it's functionality **************
//******************************************************************

function TUB(sTub){
  this.oTubsMap = new Object();
  this.parse    = parse;
  this.addNext  = addNext;
  this.add      = add;
  this.addNotNull = addNotNull;
  this.add3     = add3;
  this.remove   = remove;
  this.addObj   = addObj;
  this.wrap     = wrap;
  this.checkKey = checkKey;
  this.toText   = toText;
  this.getValue  = getValue;
  this.getKeyValue  = getKeyValue;
  this.getKeyStrValue = getKeyStrValue

  if (sTub == null) return;
  var nPos = 0;
  while (nPos < sTub.length){
    nPos = this.addNext(nPos,sTub);
  }

  function add(sKey,oObj){
    this.add3(sKey,oObj,false);
  }

  function addNotNull(sKey,oObj){
    if (oObj == null) return;
    this.add3(sKey,oObj,false);
  }

  function add3(sKey,oObj,bRepDup){
    this.addObj(sKey,oObj,bRepDup);
  }

  function addObj(sKey,oNewObj,bRepDup){
    this.checkKey(sKey);
    var oExistTub = this.oTubsMap[sKey];
    if (oExistTub != null){
      if (!bRepDup) {
        gsTubErr += ";237 Duplicate Key "+sKey;
        throw "Duplicate Key "+sKey;
      }
    }
    this.oTubsMap[sKey] = oNewObj;
  }


  function parse(sTubStr){
    var aLines = sTubStr.split(";");
    for(var i=0;i<aLines.length;i++) {
      var sLine = aLines[i];
      var nIx = sLine.indexOf('=');
      if (nIx < 0) {
        gsTubErr += ";250 Invalid field "+sLine;
        throw ("Invalid field "+sLine);
      }
      var sKey = sLine.substring(0,nIx);
      this.add(sKey,sLine.substring(nIx+1));
    }
  }

  function checkKey(sKey){
    if (sKey.indexOf(".") >= 0) {
      gsTubErr += ";260 Invalid key (.) "+sKey;
      throw ("Invalid key (.) "+sKey);
    }
    if (sKey.indexOf("=") >= 0) {
      gsTubErr += ";264 Invalid key (.) "+sKey;
      throw ("Invalid key (=) "+sKey);
    }
    if (sKey.indexOf(";") >= 0) {
      gsTubErr += ";268 Invalid key (.) "+sKey;
      throw ("Invalid key (;) "+sKey);
    }
    var cChar = sKey.charCodeAt(0);
    var c0    = "0".charCodeAt(0);
    var c9    = "9".charCodeAt(0);
    if ((cChar >= c0) && (cChar <= c9)) {
      gsTubErr += ";273 Non-alpha key start(.) "+sKey;
      throw ("Non-alpha key "+sKey);
    }
  }

  function getKeyValue(sKey){
    return this.oTubsMap[sKey];
  }

  function getValue(sKey){
    return this.oTubsMap[sKey];
  }

  // This is a more universal delete as long as the toString
  // checks for it. EC4425
  function remove(sKey) {
    this.oTubsMap[sKey] = null;
  }

  function getKeyStrValue(sKey){
    var oVal= this.oTubsMap[sKey];
    if (oVal == null) return "";
    if (oVal instanceof TUB) return "";
    if (oVal instanceof Array) return "";
    return oVal;
  }

  function toText(){
    var bFirst = true;
    var oProp;
    var sBuf = "";
    var sTub = "";
    for(oProp in this.oTubsMap){
      var oObj = this.oTubsMap[oProp];
      if (oObj == null) continue; //universal remove support (EC4425)
      if (!bFirst) sBuf += ";";
      bFirst = false;
      if (oObj instanceof TUB){
        sTub = this.wrap(oProp+"("+oObj.toText()+")");
      }else if (oObj instanceof Array){
        var sB1 = "[";
        for(var i=0;i<oObj.length; i++) {
          var oTub = oObj[i];
          var s2 = this.wrap("("+oTub.toText()+")");
          sB1 += s2;
        }
        sB1 += "]";
        sTub = this.wrap(oProp+sB1);
      }else{
        sTub = this.wrap(oProp+"="+oObj);
      }
      sBuf += sTub;
    }
    return sBuf;
  }

  function addNext(nPos,sTub){
    var nPos0 = nPos;
    while (true){
      var cChar = sTub.charAt(nPos);
      if ("0123456789".indexOf(cChar) >= 0) {
        nPos++;
      }else{
        break;
      }
    }
    var sFld;
    if (nPos0 != nPos){
      var nLen = sTub.substring(nPos0,nPos)-0;
      sFld = sTub.substring(nPos,nPos+nLen);
      nPos += nLen;
      if(nPos < sTub.length){
        if (sTub.charAt(nPos) == ';') nPos++;
      }
    }else{
      var nEnd = sTub.indexOf(";",nPos);
      if (nEnd < 0){
        nEnd = nPos = sTub.length;
      }else{
        nPos = nEnd +1;
      }
      sFld = sTub.substring(nPos0,nEnd);
    }
    var nAt = 0;
    var cAt = '';
    while (true){
      if (nAt >= sFld.length) {
        gsTubErr += ";341 Invalid field "+sFld;
        throw ("Invalid field "+sFld);
      }
      cAt = sFld.charAt(nAt);
      if (cAt == '=') break;
      if (cAt == '(') break;
      if (cAt == '[') break;
      nAt++;
    }
    var sKey = sFld.substring(0,nAt);
    switch(cAt){
      case '=': {this.add(sKey,sFld.substring(nAt+1));break;}
      case '(': {
        if (!(sFld.charAt(sFld.length-1)==')')) {
          gsTubErr += ";355 No ending in ) fld "+sFld;
          throw ("No ending ) in field "+sFld);
        }
        this.add(sKey,new TUB(sFld.substring(nAt+1,sFld.length-1)));
        break;
      }
      case '[': {
        if (!(sFld.charAt(sFld.length-1)==']')) {
          gsTubErr += ";363 No ending in ] fld "+sFld;
          throw ("No ending ] in field "+sFld);
        }
        nAt = sKey.length+1;
        var aLst = new Array();
        while (sFld.charAt(nAt) != ']'){
          var nAt0 = nAt;
          while (true){
            var cChar = sFld.charAt(nAt);
            if ("0123456789".indexOf(cChar) >= 0) {
              nAt++;
            }else{
              break;
            }
          }
          var nLen = sFld.substring(nAt0,nAt)-0;
          var sTub1 = sFld.substring(nAt+1,nAt-1+nLen);
          aLst[aLst.length] = new TUB(sTub1);
          nAt += nLen;
        }
        this.add(sKey,aLst);
        break;
      }
      default: {
        gsTubErr += ";387 cant happen here";
        throw ("cant happen here");
      }
    }
    return nPos;
  }

  function wrap(sStr){
    while (true){
      if (sStr.indexOf(';') >= 0) break;
      if (sStr.indexOf(')') >= 0) break;
      return sStr;
    }
    return sStr.length+sStr;
  }
} // End of the Class TUB;
