/* ********************************************************************
 * Evertz Modular Layout Tools - Web Application
 *
 * Module list population script
 *
 */

/* ***** Prototype additions *************************************** */
/* Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
}; */

function cloneObject(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; };
}



/* ****************************************************************************************************************************************
 ******************************************************************************************************************************************
 ******************************************************************************************************************************************
 * 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;
  this.dwdm    = 200;
  this.fiber   = {};
  this.option  = {};
  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 && o.indexOf(":") == -1;) this.option[o] = false;

  if (typeof modHash[this.name] == 'undefined') {
    modHash[this.name] = modLength++;
  } // else alert("Duplicate module: " + this.name);

  /* ***** Set default available frames **************************** */
  switch (this.type) {
    case "400": this.frames = ['Loose 400','400FR']; break;
    case "500": this.frames = ['Loose 500', '500FR']; break;
    case "3000": this.frames = ['Loose 3000', '3000FR']; this.height = 645; break;
    case "7700":
      switch (this.block) {
        case 0: // Normal 7700, allowed in all
          this.frames = ['Loose 7700', '7800FR', '7800FR-Q', '7700FR-C', '7700FR-C-48VDC', '350FR'];
          break;
        case 1: // Allowed in 7800FR only
          this.frames = ['Loose 7700', '7800FR', '7800FR-Q'];
          break;
        case 2: // Blocked slot applicable to older frames only
          this.frames = ['7700FR-C', '7700FR-C-48VDC', '350FR'];
          break;
      }
      break;
  };


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


	/* ******************************************************************
	 * 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 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] === 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.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#");


      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 (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;
        }
      }
    }
  };


	/* ******************************************************************
	 * 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) {
    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) 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 (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];
  }
}



/* ********************************************************************
 * 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 = "", 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.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.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');

/* ********************************************************************
 * Sort the modules array making sure "Empty Slot" is at position 0
 *
 */
function modSort(a, b) {
  if (a.name == "Empty Slot") return -1;
  if (b.name == "Empty Slot") return 1;
  if (a.name > b.name) return 1;
  if (b.name > a.name) return -1;
  return 0;
}




/* ****************************************************************************************************************************************
 ******************************************************************************************************************************************
 ******************************************************************************************************************************************
 * Global enclosure object definition
 *
 */
function Enclosure(type, name, frame, slots, online, loose, psu, rpsu, panel, overlap, drag, fc, power, suffix, screenX, screenY) {
  this.type = type;
  this.name = name;
  this.frame = frame;
  this.slots = slots;
  this.online = online;
  this.loose = loose;
  this.psu = psu;
  this.rpsu = rpsu;
  this.panel = panel;
  this.overlap = overlap;
  this.drag = drag;
  this.fc = fc;
  this.power = power;
  this.suffix = suffix;
  this.screen = [screenX, screenY];

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

// ***** Create basic containers **************************************
var enclosures = [], encHash = {}, encLength = 0;

/* ********************************************************************
 * Sort the enclosures array using a natural sorting order
 *
 */
function encSort(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;
}



var optionDescriptions = {
  "F":   "Fill Input",
  "F75": "F-Type Connector",
  "S":   "Frame Synchronizer",
  "B":   "Balanced Audio",
  "50":  "50\u03A9 I/O Impedance",
  "VBI": "Timecode & caption translator",
  "L":   "LNB Power - L-Band only",
  "BNC": "BNC Connector - replaces F-Type",
  "RP":  "Rear Panel Only",

  "RCP": "Rack-mount remote control panel",
  "DCP": "Desktop remote control panel",
  "CLH": "Crawl support",
  "TP":  "Air temperature probe",
  "E":   "EAS crawl insertion",
  "IG":  "Integrated IntelliGain\u00ae",
  "U":   "Unbalanced AES audio"
};