/* ********************************************************************
 * Evertz Modular Layout Tools - Web Application
 *
 * Shared object & module list population script
 *   - Shared between single frame and bulk frame layout tools
 *
 */

var isIE = /*@cc_on!@*/false;
var isIE5 = (navigator.userAgent && navigator.userAgent.indexOf('MSIE 5.') > -1);
var isIE6 = /*@if(@_jscript_version == 5.6)!@end@*/false;
var isIElt8 = /*@if(@_jscript_version < 5.8)!@end@*/false;

var Taint = false;
var preload = new Image(); preload.src = "/img/evertz/evertz.white.24px.png";

// ***** Prototype additions ******************************************
function cloneObject(obj) {
  if (typeof obj != "object") return obj;
  var newObj = (obj instanceof Array) ? [] : {};
  for (i in obj) {
    if (obj[i] && typeof obj[i] == "object") {
      newObj[i] = cloneObject(obj[i]);
    } else newObj[i] = obj[i];
  } return newObj;
}

String.prototype.trim = function() {
  return this.replace(/^\s+|\s+$/g,"");
};
String.prototype.lpad = function(len, char) {
  char = char.toString().substr(0, 1);
  for (var x = 0, str = ""; x < len - this.length; x++) str += char;
  return (str + this);
};
String.prototype.removeWord = function(word) {
  for (var foo = this.trim().split(" "), bar = [], baz, x = 0; baz = foo[x++];)
    if (baz.length && baz != word) bar.push(baz);
  return bar.join(" ");
};

Array.prototype.find = function(data) {
  for (var x = 0, y; y = this[x++];) if (y == data) return true;
  return false;
};


/* ********************************************************************
 * Disable text selection within an element
 *
 */
function selectDisable(elem) {
  elem.onselectstart = function() { return false; };
  elem.unselectable = "on";
  elem.style.MozUserSelect = "none";
  elem.style.cursor = "default";
  if (window.opera) elem.onmousedown = function() { return false; };
}

/* ********************************************************************
 * Cancel mouse/keyboard interaction bubbling from an input element
 *
 */
function cancelInputBubble(elem) {
  elem.onkeydown = elem.onkeypress = elem[(isIE) ? 'onselectstart' : 'onmousedown'] = function(e) { return (e || event).cancelBubble = true; };
  elem.onfocus = function(e) {
    if (elem.type != "file" && app.view != "edit") app.switchControl('edit');
    return (e || event).cancelBubble = true;
  };
}

/* ********************************************************************
 * Return a broadcast address from an IP and subnet mask
 *
 */
function getBroadcastIP(ip, sn) {
  for (var x = 0, ip = ip.split("."), sn = sn.split("."), np = []; x < 4; x++)
    np[x] = parseInt(ip[x]) | (parseInt(sn[x]) ^ 255);
  return np.join(".");
}


/* ********************************************************************
 * Handle the appearance and content of the context menu
 *
 */
function contextMenu(e, x, y) {
  if (app.dragging) return false;

  e.returnValue = false;
  app.hideDialogs();

  var full, card, scWebpage, wframe = x + app.page * app.fpp, pfx = "f" + (wframe % app.fpp) + "s";
  if (app.type == "Single" || document.getElementById(pfx + y).parentNode.className.indexOf('select') === -1)
    frame[wframe].highlight(y, false);

  if (full = frame[wframe].isFull() & 4) {
    for (var i = 0; i < frame[wframe].slots;) {
      if (frame[wframe].slot[i].name != "Empty Slot" && frame[wframe].slot[i].name != "Future-Use") {
        if (app.type == "Bulk") {
          if (document.getElementById(pfx + i).parentNode.className.indexOf('select') !== -1) card = true;
        } else if (i == y) card = true;
      } i += frame[wframe].slot[i].slots;
    }
  }

  for (var ext, z = 0; ext = extras[z++];) {
    var exElem = document.getElementById('scExtra' + ext.code);
        exElem.className = (card) ? "" : "disabled";
    var exElemImg = exElem.getElementsByTagName('img')[0];
    var flag = frame[wframe].slot[y].flags[ext.code];
    exElemImg.src = (flag) ? "img/check-on.png" : "img/check-off.png";
    exElemImg.alt = (flag) ? "Yes" : "No";
  }

  document.getElementById('scOptions').className = (frame[wframe].slot[y].anyopt) ? "" : "disabled";
  document.getElementById('scClearFrame').className = (!full) ? "disabled" : "";
  document.getElementById('scDelete').className = document.getElementById('scCut').className = document.getElementById('scCopy').className = document.getElementById('scFill').className = (card) ? "" : "disabled";
  if (app.type == "Bulk") {
    document.getElementById('scFrameImage').className = document.getElementById('scDuplicateFrame').className = (!full) ? "disabled" : "";
  } else document.getElementById('scLabel').className = (card) ? "" : "disabled";

  if (scWebpage = document.getElementById('scWebpage')) {
    if (card && modules[modHash[frame[wframe].slot[y].name]].parent) {
      scWebpage.className = "";
      scWebpage.getElementsByTagName('a')[0].href = "../products/" + modules[modHash[frame[wframe].slot[y].name]].parent + ((app.mirror) ? ".html" : "");
      if (!app.mirror)
        setTimeout((function(parentProd) { return function() { app.findBlock(parentProd); }})(modules[modHash[frame[wframe].slot[y].name]].parent), 0);
    } else {
      scWebpage.className = "disabled";
      if (!app.mirror) document.getElementById('scBlockDiagram').className = "disabled";
    }
  }

  var cont = document.getElementById('slotContext');
  var scrld = scrollDist(), top = e.clientY + scrld[1];
  if (top + cont.offsetHeight > innerDimensions()[1]) top -= cont.offsetHeight;
      cont.style.top = top + "px";
      cont.style.left = (e.clientX + scrld[0]) + "px";
      cont.style.visibility = "visible";

  return false;
}


/* ********************************************************************
 * Handle the appearance and content of the selector context menu
 *
 */
function selectorContextMenu(e, x) {
  if (app.dragging) return false;
  e.returnValue = false;

  if (modules[x].parent) {
    document.getElementById('slcWebpage').className = "";
    document.getElementById('slcWebpage').getElementsByTagName('a')[0].href = "../products/" + modules[x].parent;
    setTimeout((function(parentProd) { return function() { app.findBlock(parentProd); }})(modules[x].parent), 0);
  } else {
    document.getElementById('slcWebpage').className = "disabled";
    document.getElementById('slcBlockDiagram').className = "disabled";
  }

  var cont = document.getElementById('selectorContext');
      cont.getElementsByTagName('h3')[0].firstChild.nodeValue = modules[x].name;
      cont.getElementsByTagName('h3')[0].parent = modules[x].parent;

  var scrld = scrollDist(), top = e.clientY + scrld[1];
  if (top + cont.offsetHeight > innerDimensions()[1]) top -= cont.offsetHeight;
      cont.style.top = top + "px";
      cont.style.left = (e.clientX + scrld[0]) + "px";
      cont.style.visibility = "visible";

  document.getElementsByName("module_filter")[0].focus();
  return false;
}


/* ********************************************************************
 * Start execution on page load and pass off
 *
 */
window.onload = function(e) {
  if (self.innerWidth) { // Attempt to resize the browser window
    frameWidth = self.innerWidth;
    frameHeight = self.innerHeight;
  } else if (document.documentElement && document.documentElement.clientWidth) {
    frameWidth = document.documentElement.clientWidth;
    frameHeight = document.documentElement.clientHeight;
  } else if (document.body) {
    frameWidth = document.body.clientWidth;
    frameHeight = document.body.clientHeight;
  }
  try {
    if ((frameWidth && frameWidth < page.screen[0]) || (frameHeight && frameHeight < page.screen[1]))
      window.resizeTo(page.screen[0], page.screen[1]);
  } catch(e) {}


  // ***** Sort and hash modules and enclosures ***********************
  modules.sort(function(a, b) {
    if (a.name == "Empty Slot") return -1;
    if (b.name == "Empty Slot") return 1;
    if (a.name == "Future-Use") return -1;
    if (b.name == "Future-Use") return 1;
    if (a.name > b.name) return 1;
    if (b.name > a.name) return -1;
    return 0;
  });
  for (var x = 0; x < modules.length; x++) modHash[modules[x].name] = x;

  enclosures.sort(function(a, b) {
    function chunkify(t) {
      var tz = [], x = 0, y = -1, m, n = 0, i, j;
      while (i = (j = t.charAt(x++)).charCodeAt(0)) {
        if ((m = (i == 46 || (i >= 48 && i <= 57))) !== n) {
          tz[++y] = "";
          n = m;
        } tz[y] += j;
      } return tz;
    }
    var aa = chunkify(a.frame.toLowerCase());
    var bb = chunkify(b.frame.toLowerCase());
    for (x = 0; aa[x] && bb[x]; x++) {
      if (aa[x] !== bb[x]) {
        var c = Number(aa[x]), d = Number(bb[x]);
        return (c == aa[x] && d == bb[x]) ? c - d : ((aa[x] > bb[x]) ? 1 : -1);
      }
    } return aa.length - bb.length;
  });
  for (var x = 0; x < enclosures.length; x++) encHash[enclosures[x].frame] = x;


  // ***** Execute page specific onload function **********************
  createOnload();


  // ***** Add Extra Options to Context Menus and Options menu ********
  for (var ext, x = 0; ext = extras[x++];) {
    var li = document.createElement('li');
        li.id = "scExtra" + ext.code;
        li.code = ext.code;
        li.title = ext.title;
        li.onclick = function() {
          if (this.className != 'disabled') {
            var box = this.firstChild;
            if (box.alt == "No") {
              box.src = "img/check-on.png";
              box.alt = "Yes";
              frame[app.working].slot[app.cursor].flags[this.code] = true;
            } else {
              box.src = "img/check-off.png";
              box.alt = "No";
              frame[app.working].slot[app.cursor].flags[this.code] = false;
            }
          } frame[app.working].refresh();
        };
      var img = document.createElement('img');
          img.src = "img/check-off.png";
          img.alt = "No";
        li.appendChild(img);
        li.appendChild(document.createTextNode(ext.description));
    document.getElementById('slotContextExtra').appendChild(li);
  }


  // ***** Apply misc event listeners *********************************
  selectDisable(Pop.selector.element);

  for (var x = 0, butts = document.getElementsByTagName('button'); x < butts.length; x++) {
    butts[x].onmouseover = function() { if (!this.disabled) this.className += " active"; };
    butts[x].onmouseout = function() { this.className = this.className.removeWord("active"); };
  }

  document.documentElement.onclick = function() {
    document.getElementById('slotContext').style.visibility = "";
    return true;
  };
  for (var x = 0, lis = document.getElementById('contextMenu').getElementsByTagName('li'); x < lis.length; x++) {
    lis[x].onmouseover = function() { if (this.className != "disabled") this.className = "hover"; };
    lis[x].onmouseout = function() { if (this.className != "disabled") this.className = ""; };
    for (var y = 0, as = lis[x].getElementsByTagName('a'); y < as.length; y++) {
      as[y].target = "_blank";
      as[y].onclick = function() {
        document.getElementById('slotContext').style.visibility = "";
        return (this.parentNode.className != "disabled");
      };
    }
  }
  document.getElementsByName('module_filter')[0].onkeypress = document.getElementsByName('module_filter')[0].onkeyup = function(e) {
    e = e || event;
    e.cancelBubble = true;
    switch (e.keyCode) {
      case 38: case 40: case 32: case 13: case 27: return false;
      default:
        clearTimeout(app.timeout);
        app.timeout = setTimeout(function() { app.selectorFilter(); }, 200);
    } return true;
  }
  if (document.getElementById('alert_contact')) {
    document.getElementById('alert_contact').onclick = function() {
      alert('For the fastest response time, please include an email address in the Contact Information field');
      return false;
    };
  }
  var saleslink = document.getElementById('saleslink');
  if (saleslink) saleslink.target = "_blank";


  // ***** Capture document keypresses ********************************
  document.documentElement.onkeydown = function(e) {
    if (app.keydown) return false;
    e = e || event;
    if (app.keyCheck(e.keyCode, (e.ctrlKey || e.metaKey), e.shiftKey)) {
      app.keydown = true;
      var keyCode = e.keyCode, ctrlKey = (e.ctrlKey || e.metaKey), shiftKey = e.shiftKey;
      frame.keydelay = setTimeout(function() {
        frame.keyrepeat = setInterval(function() { app.keyCheck(keyCode, ctrlKey, shiftKey); }, 35);
      }, 150);
      return false;
    } else app.keydown = false;
    return true;
  };
  document.documentElement.onkeypress = function(e) {
    switch ((e = e || event).keyCode) {
      case 13: case 33: case 34: case 35: case 36: case 37: case 38:
      case 39: case 40: case 46: case 65: case 67: case 86: case 88:
        e.cancelBubble = true;
        return false;
    } return true
  };
  document.documentElement.onkeyup = function(e) {
    app.keydown = false;
    clearTimeout(frame.keydelay);
    clearInterval(frame.keyrepeat);
  };


  // ***** Prevent bubbling from input elements ***********************
  var inputs = [
    document.getElementsByTagName('input'),
    document.getElementsByTagName('textarea'),
    document.getElementsByTagName('select')
  ];
  for (var i = 0; i < inputs.length; i++) {
    for (var x = 0; x < inputs[i].length; x++) {
      cancelInputBubble(inputs[i][x]);
      switch (inputs[i][x].nodeName.toUpperCase()) {
        case "SELECT": inputs[i][x].onchange = function() { Taint = true; }; break;
        case "INPUT": case "TEXTAREA":
          if (inputs[i][x].addEventListener) {
            inputs[i][x].addEventListener('input', function() { Taint = true; }, false);
          } else inputs[i][x].onpropertychange = function() { Taint = true; };
      }
    }
  }


  // ***** Handle keypress input in the selection dialog **************
  document.getElementsByName('module_filter')[0].onkeydown = function(e) {
    e = e || event;
    e.cancelBubble = true;
    clearTimeout(app.seltip);
    document.getElementById('selectorContext').style.visibility = "";

    var sellist = Pop.selector.element.getElementsByTagName('ul')[0];
    var slottype = (frame[app.working].image.length) ? frame[app.working].image[app.cursor].ptype : frame[app.working].type;
    var preview = document.getElementById('preview');

    switch (e.keyCode) {
      case 38: // ******************** Up
        if (app.fopts.length) {
          if (app.selkirk > -1) {
            app.fopts[app.selkirk].className = app.fopts[app.selkirk].className.removeWord("chosen");
            app.fopts[app.selkirk = Math.max(app.selkirk - 1, 0)].className += " chosen";
          } else app.fopts[app.selkirk = Math.min(app.fopts.length, Math.floor((sellist.scrollTop + sellist.offsetHeight) / 16)) - 1].className += " chosen";

          if ((app.selkirk + 1) * 16 > sellist.scrollTop + sellist.offsetHeight) {
            sellist.scrollTop = (app.selkirk + 1) * 16 - sellist.offsetHeight + 2;
          } else if (app.selkirk * 16 < sellist.scrollTop) sellist.scrollTop = app.selkirk * 16;

          preview.src = modules[modHash[app.fopts[app.selkirk].firstChild.nodeValue]].fileName(slottype, page.rotate);
        } else preview.src = modules[0].fileName(slottype);
        return false;

      case 40: // ******************** Down
        if (app.fopts.length) {
          if (app.selkirk > -1) {
            app.fopts[app.selkirk].className = app.fopts[app.selkirk].className.removeWord("chosen");
            app.fopts[app.selkirk = Math.min(app.selkirk + 1, app.fopts.length - 1)].className += " chosen";
          } else app.fopts[app.selkirk = Math.max(0, Math.ceil(sellist.scrollTop / 16))].className += " chosen";

          if ((app.selkirk + 1) * 16 > sellist.scrollTop + sellist.offsetHeight) {
            sellist.scrollTop = (app.selkirk + 1) * 16 - sellist.offsetHeight + 2;
          } else if (app.selkirk * 16 < sellist.scrollTop) sellist.scrollTop = app.selkirk * 16;

          preview.src = modules[modHash[app.fopts[app.selkirk].firstChild.nodeValue]].fileName(slottype);
        } else preview.src = modules[0].fileName(slottype);
        return false;

      case 32:
      case 13: // ******************** Enter & Spacebar
        if (app.selkirk > -1) {
          app.fopts[app.selkirk].onclick();
          document.getElementsByName("module_filter")[0].blur();
          app.selkirk = -1;
        } return false;

      case 27: // ******************** ESC
        app.selectorDisplay(false);
        break;

      default: // alert(e.keyCode);
    } return true;
  }


  // ***** Populate the picklist with all modules *********************
  var picklist = Pop.selector.element.getElementsByTagName('ul')[0];
  var pickitem = picklist.getElementsByTagName('li');
  var picktype = (frame[app.working].image.length) ? frame[app.working].image[app.cursor].ptype : frame[app.working].type;
  while (picklist.firstChild) picklist.removeChild(picklist.firstChild);
  picklist.onscroll = function() {
    clearTimeout(app.seltip);
    document.getElementById('selector_tooltip').style.visibility = "";
    document.getElementById('selectorContext').style.visibility = "";
  }
  for (var x = 0; x < modules.length; x++) {
    var li = document.createElement("li");
        li.uname = modules[x].name.toUpperCase();
        li.index = x;
        li.appendChild(document.createTextNode(modules[x].name));
      if (page.power) {
        var span = document.createElement("span");
            span.appendChild(document.createTextNode(" (" + modules[x].power + ")"));
          li.appendChild(span);
      } li.onmouseover = function() {
          var self = this;
          clearTimeout(app.seltime);
          clearTimeout(app.seltip);
          document.getElementById('preview').src = modules[this.index].fileName(picktype);
          document.getElementById('selectorContext').style.visibility = "";
          app.seltip = setTimeout(function() {
            var tooltip = document.getElementById('selector_tooltip');
            tooltip.firstChild.nodeValue = modules[self.index].descrip;
            tooltip.style.top = (self.offsetTop + self.parentNode.offsetTop - self.parentNode.scrollTop + 32)+ "px";
            tooltip.style.visibility = "visible";
          }, 250);
          this.className += " hover";
        };
        li.onmouseout = function() {
          clearTimeout(app.seltip);
          document.getElementById('selector_tooltip').style.visibility = "";
          if (app.selkirk > -1) {
            app.seltime = setTimeout(function() {
              document.getElementById('preview').src = modules[modHash[app.fopts[app.selkirk].firstChild.nodeValue]].fileName(picktype);
            }, 50);
          } this.className = this.className.removeWord("hover");
        };
        li.oncontextmenu = function(e) {
          e = e || event;
          e.returnValue = false;
          clearTimeout(app.seltip);
          document.getElementById('selector_tooltip').style.visibility = "";
          return selectorContextMenu(e, this.index);
        };
        li.onclick = function() {
          clearTimeout(app.seltip);
          document.getElementById('selector_tooltip').style.visibility = "";
          document.getElementById('selectorContext').style.visibility = "";
          frame[app.working].newmodule = true;
          if (frame[app.working].place(app.cursor, this.index, false))
            frame[app.working].highlight(app.cursor, true);
        };
    picklist.appendChild(li);
  }

  loadFrame();
  document.body.className = document.body.className.removeWord("wait");
  Pop.waiter.hide();
};






/* ****************************************************************************************************************************************
 * Global module object definition
 *
 */
function Module(type, name, image, block, slots, power, options, fibers, online, parent, descrip) {
  fibers = fibers.trim();
  options = options.trim();

  this.type    = type;
  this.name    = name;
  this.image   = image;
  this.block   = block;
  this.slots   = slots;
  this.power   = power;
  this.options = options;
  this.fibers  = fibers;
  this.online  = online;
  this.parent  = parent;
  this.descrip = (descrip || "\xa0") + ((this.block == 2) ? " (slot blocker included)" : "");
  this.height  = 316;

  this.frames  = [];
  this.uname   = name.toUpperCase();
  this.cwdm    = 27;
  /* HACK for 7707VT-8-HS+Cxx & 7708GT-4+Cxx wavelength */
  if (name.indexOf("7707VT-8-HS+") >= 0 || name.indexOf("7708GT-4+") >= 0) { /* */
    this.cwdm  = 47; /* */
  } /* */
  this.dwdm    = 200;
  this.fiber   = {};
  this.option  = {};
  this.label   = "";
  this.anyopt  = fibers.length || options.length || name.indexOf("xx") > -1;
  this.flags   = {RP: false, NO: false};
  this.multi   = false;
  this.deny    = [];

  for (var x = 0, f, fibers = fibers.split(" "); (f = fibers[x++]) && f.length;) this.fiber[f] = !(x - 1);
  for (var x = 0, o, options = options.split(" "); (o = options[x++]) && o.length;) {
    if (o.indexOf("|") > -1) {
      this.option["group" + x] = [];
      for (var y = 0, o_ = o.split("|"); y < o_.length; y++) this.option["group" + x][o_[y]] = !y;
    } else this.option[o] = false;
  }

  if (typeof modHash[this.name] == 'undefined')
    modHash[this.name] = modLength++;

  /* ***** Set default available frames **************************** */
  switch (this.type) {
    case "500":
      this.frames = ['Loose 500', '500FR'];
      if (this.name.indexOf('400') === 0)
        this.frames = ['Loose 400','400FR'];
      break;
    case "3000":
      this.height = 645;
      this.frames = ['Loose 3000', '3000FR'];
      if (this.name.indexOf('EMR') === 0 || this.name.indexOf('3025') === 0)
        this.frames = ['Loose EMR', 'EMX6-FR', 'EMX3-FR'];
      if (this.name.indexOf('370') === 0)
        this.frames = ['3700FR'];
      if (this.name.indexOf('3025') === 0)
        this.frames.push('3700FR');
      break;
    case "7700":
      switch (this.block) {
        case 0: // Normal 7700, allowed in all
          this.frames = ['Loose 7700', '7800FR', '7800FR-QT', '7801FR', '7800FR-48VDC', '7700FR-C', '7700FR-C+LF', '7700FR-C-48VDC', '350FR'];
          break;
        case 1: // Allowed in 7800FR only
          this.frames = ['Loose 7700', '7800FR', '7800FR-QT', '7801FR', '7800FR-48VDC'];
          break;
        case 2: // Blocked slot applicable to older frames only
          this.frames = ['7700FR-C', '7700FR-C+LF', '7700FR-C-48VDC', '350FR'];
          break;
      }
      if (this.name.indexOf('779') === 0 || this.name.indexOf('789') === 0 || this.name.indexOf('VIPX') > 0)
        this.frames.push('3700FR');
      break;
  };


  /* ******************************************************************
   * Return true or false if this module is allowed in the current
   *  frame type, frame, and slot number
   *
   */
  this.allowed = function(ftype, fname, slot) {
    if ((this.online || !app.online) && (!this.type || (ftype == this.type && this.frames.find(fname)))) {
      if (enclosures[encHash[fname]].maxslot && enclosures[encHash[fname]].maxslot < this.slots) return false;
      return (fname.indexOf('Loose') == -1 && this.deny.length) ? !this.deny[slot] : true;
    } else return false;
  };


  /* ******************************************************************
   * Return this module's name in various formats
   *   full == 0
   *     Module with WDM-sub and price-list options:
   *       eg. 7707IFTA27+FC+F75
   *
   *   full == 1
   *     Module as orderable:
   *       eg. 7700P(7707IFTA27+FC+F75+3RU)
   *
   *   full == 2
   *     Module in format to be saved and recalled:
   *       eg. 7700P(7707IFTAxx+FC+F75+3RU) CWDM:27 "Label" NotOrdered
   *
   */
  this.fullName = function(full) {
    var name = this.name.replace(/ \*$/, "");

    if (full < 2) {
      if (name.indexOf("xxx") > -1) {
        name = name.replace(/xxx/, this.dwdm);
      } else if (name.indexOf("xx") > -1)
        name = name.replace(/xx/, this.cwdm);
    }

    for (fbr in this.fiber)
      if (this.fiber[fbr] === true)
        name += "+" + fbr;

    for (opt in this.option) {
      if (this.option[opt] instanceof Array) {
        for (opt_ in this.option[opt])
          if (opt_ && this.option[opt][opt_] === true)
            name += "+" + opt_;
      } else if (opt && this.option[opt] === true) {
        name += "+" + opt;
      }
    }

    if (full) {
      if (this.type == "7700")
        name += enclosures[encHash['7800FR']].suffix;

      if (this.flags.RP) name = this.type + "P(" + name + ")";

      if (full == 2) {
        if (name.match(/xxx/)) {
          name += " DWDM:" + this.dwdm;
        } else if (name.match(/xx/))
          name += " CWDM:" + this.cwdm;

        if (this.label)
          name += ' "' + this.label + '"';
      }

      if (this.flags.NO) name += " NotOrdered";
      if (this.block == 2) name += " Blocker";
    }

    return name;
  };


  /* ******************************************************************
   * Take a string of + delimited options, parse and apply to this
   * module.  This unsets all previous options.
   *
   */
  this.setOptions = function(list) {
    if (list.length) {
      if (list.substr(0, 1) == "+") list = list.substr(1);

      // Hack to change options *containing* + symbols to # before splitting.
      // All other options which contain + need to have entries added here.
      list = list.replace(/AP\+/g, "AP#");
      // if (typeof this.option['UMX+IG'] == "boolean")
        list = list.replace(/UMX\+IG/g, "UMX#IG");



      list = list.split("+");


      // After splitting at +, change the # back to +.
      for (var x = 0; x < list.length; x++)
        list[x] = list[x].replace(/\#/g, "+");

      for (opt in this.option) {
        if (this.option[opt] instanceof Array) {
          for (opt_ in this.option[opt])
            if (typeof this.option[opt][opt_] == "boolean")
              this.option[opt][opt_] = !opt_;
        } else if (typeof this.option[opt] == "boolean")
          this.option[opt] = false;
      }

      for (flg in this.flags)
        if (typeof this.flags[flg] == "boolean")
          this.flags[flg] = false;

      for (var o, x = 0; o = list[x++];) {
        if (typeof this.fiber[o] == "boolean") {
          this.setFiber(o);
        } else if (typeof this.option[o] == "boolean") {
          this.option[o] = true;
        } else if (typeof this.flags[o] == "boolean") {
          this.flags[o] = true;
        } else
          for (opt in this.option)
            if (opt.indexOf('group') === 0 && typeof this.option[opt][o] == "boolean")
              for (opt_ in this.option[opt])
                if (typeof this.option[opt][opt_] == "boolean")
                  this.option[opt][opt_] = (opt_ == o);
      }
    }
  };


  /* ******************************************************************
   * Set the current fiber connector by unsetting all others
   *
   */
  this.setFiber = function(conn) {
    if (typeof this.fiber[conn] == "boolean")
      for (fbr in this.fiber)
        if (typeof this.fiber[fbr] == "boolean")
          this.fiber[fbr] = false;
    this.fiber[conn] = true;
  };


  /* ******************************************************************
   * Return this module's image file accounting for type, fiber
   * connectors and options
   *
   */
  this.fileName = function(defType, rotate) {
    var img = this.image;
    for (fbr in this.fiber)
      if (this.fiber[fbr] === true)
        img += "+" + fbr;
    if (this.block == 2) img += ".blkd";

    if (defType.length) {
      if (rotate) {
        img = "rear/rotate?type=" + (this.type || defType) + "&img=" + encodeURIComponent(img) + ".png";
      } else img = "rear/" + (this.type || defType) + "/" + img + ".png";
    }

    return img;
  };


  /* *******************************************************************
   * Take the optional values from another module and set them in this
   * module
   *
   */
  this.subTransfer = function(old) {
    this.cwdm = old.cwdm;
    this.dwdm = old.dwdm;

    for (fbr in old.fiber)
      if (typeof old.fiber[fbr] == "boolean" && typeof this.fiber[fbr] == "boolean")
        this.fiber[fbr] = old.fiber[fbr];

    for (opt in old.option) {
      if (old.option[opt] instanceof Array && this.option[opt] instanceof Array) {
        for (opt_ in old.option[opt])
          if (typeof old.option[opt][opt_] == "boolean" && typeof this.option[opt][opt_] == "boolean")
            this.option[opt][opt_] = old.option[opt][opt_];
      } else if (typeof old.option[opt] == "boolean" && typeof this.option[opt] == "boolean")
        this.option[opt] = old.option[opt];
    }

    for (flg in old.flags)
      if (typeof old.flags[flg] == "boolean" && typeof this.flags[flg] == "boolean")
        this.flags[flg] = old.flags[flg];

    this.label = old.label;
  }
}



/* ********************************************************************
 * Take a module name as generated by fullName(2) and return a Module
 * object
 *
 */
function makeModule(str) {
  var no = false, bk = false, cwdm = 27, dwdm = 200, rp = false, opt = "", label = "", mod;

  str = str.split(" ");

  for (var x = str.length, part; part = str[--x];) {
    parttl = part.toLowerCase().replace(/^\((c|d)wdm:(\d{2,3})\)$/, '$1wdm:$2');
    if (parttl == "blocker") {
      bk = true;
    } else if (parttl == "notordered") {
      no = true;
    } else if (parttl.indexOf("cwdm:") == 0) {
      cwdm = parseInt(parttl.substr(5));
    } else if (parttl.indexOf("dwdm:") == 0) {
      dwdm = parseInt(parttl.substr(5));
    } else if (part.match(/"$/)) {
      if (part != '""') {
        while (part.length == 1 || part.indexOf('"') != 0) part = str[--x] + " " + part;
        label = part.replace(/^"|"$/g, "");
      }
    } else {
      if (part.indexOf("7700P(") == 0) {
        rp = true;
        part = part.slice(6, part.length - 1);
      }
      while (!modHash[part + ((bk) ? " *" : "")] && part.indexOf("+") > -1) {
        part = part.split("+");
        opt = "+" + part.pop() + opt;
        part = part.join("+");
      }
      if (bk) part += " *";
      mod = cloneObject((modHash[part]) ? modules[modHash[part]] : modules[0]);
    }
  }

  mod.setOptions(opt);
  mod.label = label;
  mod.cwdm = cwdm;
  mod.dwdm = dwdm;
  mod.flags.RP = rp;
  mod.flags.NO = no;

  return mod;
}

// ***** Create basic containers **************************************
var modules = [], modHash = {}, modLength = 0;
modules[0] = new Module('','Empty Slot','blank',0,1,0,'','',1,'','Empty Slot');
modules[1] = new Module('','Future-Use','blank',0,1,0,'','',1,'','Future Use');




/* ********************************************************************
 * Create meta options which apply to all modules
 *
 */
function ExtraOption(code, description, title, className) {
  this.code = code;
  this.match = new RegExp("\\+" + code, "g");
  this.description = description;
  this.title = title;
  this.className = className;
}
var extras = [
  new ExtraOption("RP", "Rear Panel Only", "Order this module's rear panel only", "rearonly"),
  new ExtraOption("NO", "Future Purchase", "Not ordered - future purchase only", "notordered")
];





/* ****************************************************************************************************************************************
 * Global enclosure object definition
 *
 */
function Enclosure(type, name, frame, slots, rotate, online, loose, ps, rps, panel, overlap, split, reverse, drag, fc, rfc, power, suffix, screenXY) {
  this.type = type;       // Basic panel type
  this.name = name;       // Full English name of frame
  this.frame = frame;     // Short name / model name
  this.slots = slots;
  this.maxslot = 0;       // Slot limit for cards in this frame
  this.loose = loose;
  this.ps = ps;           // Filename of standard PSU
  this.rps = rps;         // Option code for the redundant PSU
  this.panel = panel;     // Default panel image dimensions
  this.overlap = overlap; // Amount of panel overlap
  this.split = split;     // Physical splits between consecutively numbered panels
  this.reverse = reverse; // Panel order is reversed (LtR)
  this.drag = drag;       // Dragable area for this frame
  this.fc = fc;           // List of modules which can act as frame controller
  this.rfc = rfc;         // Frame has a redundant frame controller option
  this.suffix = suffix;   // Panel suffix appended to panels in this frame type
  this.rotate = rotate;   // Rotate panel images 90&deg;
  this.power = power;     // Display power options for this frame
  this.screen = screenXY; // Try to resize window to these dimensions
  this.online = online;   // Display this frame online

  if (this.frame == "7801FR") this.maxslot = 2;

  if (typeof encHash[this.frame] == 'undefined')
    encHash[this.frame] = encLength++;
};

// ***** Create basic containers **************************************
var encHash = {}, encLength = 0;
var enclosures = [new Enclosure('Bulk','Bulk','Bulk',0,false,1,false,'','',[0,0],0,[],false,[1000,960],[],false,0,'',[1000,960])];


var optionDescriptions = {
  '':        "None",
  '3G':      "1080p 59.94, 50HZ 3G input, level B",
  '422':     "4:2:2 HD MPEG-2 Decoder",
  '42210B':  "4:2:2 10 bits support",
  '50':      "50\u03A9 I/O Impedance",
  'AAC':     "AAC decoder",
  'AC3E':    "Dolby\u00ae AC3 Encoder",
  'AC3E2':   "Second Dolby\u00ae AC3 Encoder",
  'AM':      "Advanced audio monitoring",
  'AVM':     "AVM Monitoring",
  'B':       "Balanced Audio",
  'BNC':     "BNC Connector - replaces F-Type",
  'CF2G':    "Embedded Compact Flash",
  'CLH':     "Crawl support",
  'CSX16':   "Closed Caption, Subtitle, and XDS support",
  'CSX18':   "Closed Caption, Subtitle, and XDS support",
  'CSX24':   "Closed Caption, Subtitle, and XDS support",
  'CSX32':   "Closed Caption, Subtitle, and XDS support",
  'CSX8':    "Closed Caption, Subtitle, and XDS support",
  'CWL':     "Crawl support",
  'DCP':     "Desktop remote control panel",
  'DD':      "Dolby\u00ae E/AC3 Decoder",
  'DD2':     "Dual Dolby\u00ae E/AC3 Decoders",
  'DEE':     "Dolby\u00ae E Encoder",
  'DEE2':    "Dual Dolby\u00ae E Encoders",
  'DLY':     "Audio delay",
  'DMX':     "5.1 to 2-Channel Stereo Downmix",
  'DSP':     "Gain, inversion, mono-mix, SoftSwitch\u2122",
  'E':       "EAS crawl insertion",
  'F':       "Fill Input",
  'F75':     "F-Type Connector",
  'FSE':     "Dual Frame Sync",
  'GPI':     "GPI Triggering into VITC/RP188 Timecode",
  'H264':    "H264 Capability",
  'HD':      "Dual HD Decoding",
  'ICL':     "Soft Knee Color Legalizer",
  'IG':      "Integrated IntelliGain\u2122",
  'IP':      "MPEG over IP Output",
  'IT':      "IntelliTrak Lipsync Control",
  'L':       "LNB Power - L-Band only",
  'LL':      "Lower latency",
  'RCP':     "Rack-mount remote control panel",
  'RJ45':    "Redundant Ethernet input",
  'RP':      "Rear Panel Only",
  'S':       "Frame Synchronizer",
  'SCTE104': "SCTE104 Digital Program Insertion Messaging",
  'SRC':     "Sample rate conversion, quad-mix",
  'STM':     "Stereoscopic Temporal Matching",
  'TL':      "Thumbnailing with monitoring",
  'TP':      "Air temperature probe",
  'TSM':     "Transport Stream Monitor",
  'U':       "Unbalanced AES audio",
  'UMX':     "Stereo to 5.1 Up-mix",
  'UMX+IG':  "2-Channel Stereo to 5.1 surround sound upmix with IntelliGain\u2122",
  'VANC':    "Serial RS-232/RS-422 Insertion into Vertical Ancillary Space",
  'VBI':     "Timecode & caption translator"
};

if (window.location.hash == "#options") {
  window.addEventListener('load', function() {
    for (var x = 0, ttopts = {}; x < modules.length; x++)
      for (o in modules[x].option)
        if (!optionDescriptions[o] && !ttopts[o])
          alert(ttopts[o] = [modules[x].name, o]);
  }, false);
}

window.onbeforeunload = function(e) {
  var e = e || window.event;
  if (Taint) return e.returnValue = "You are about to close or leave this page; all changes will be lost. Are you sure?";
};
