//\\\\\\\\\\\\\\\\\\\\\\\\\ JSSim (c) jskl 2007 \\\\\\\\\\\\\\\\\\\\\\\\\\\

//*************************************************************************
//**                             UTILITIES                               **
//*************************************************************************

//====================== General Purpose Routines =========================

function listProperties(obj) {     // Debugging - list of object properties
   var list = "";
   for(var p in obj)
    {list += p + " : " + obj[p].toString().substring(0,30) + "\n"};
   alert(list);
};

//.........................................................................

function spaces(n)                          { // Returns a list on n spaces
  var s = "";
  for (var i = 0; i < n; i++)
   { s = s + " " };
  return s;
};

//.........................................................................

function syntaxOK(x) {            // Validation: testing syntax of a number
  if (isNaN(x)) {
     alert("Syntax error - the value is not a number.");
     return false;
  } else return true;
};

//.........................................................................

function testIntValue(textElement) {          // Validation: integer number
  var n = parseInt(textElement);
  if (! syntaxOK(n)) {
      alert("The value must be an integer number.");
      return false;
  };
  return true;
};

//.........................................................................

function testPosIntValue(textElement) {     // Validation: positive integer
  var n = parseInt(textElement);
  if (! syntaxOK(n)) { return false };
  if (n <= 0) {
      alert("The value must be a positive integer number.");
      return false;
  };
  return true;
};

//.........................................................................

function testNonNegIntValue(textElement){       // Validation: integer >= 0
  var n = parseInt(textElement);
  if (! syntaxOK(n)) { return false };
  if (n < 0) {
      alert("The value must be a non-negative integer number.");
      return false;
  };
  return true;
};

//.........................................................................

function testExper(textElement) {        // Validation: experiment duration
  var x = parseFloat(textElement);
  if (! syntaxOK(x)) { return false };
  if (x <= 0) {
     alert("Experiment duration must be a positive number.");
     return false;
  };
  return true;
};

//.........................................................................

function testNonnegLE1Value(textElement)         // Validation: probability
{ var x = parseFloat(textElement);
  if (! syntaxOK(x)) { return false };
  if ((x < 0) || (x > 1)) {
      alert("The probability must be a non-negative number not greater than 1.");
      return false;
  };
  return true;
};

//.........................................................................

function help(x)  {                 // Opens the help window, shows x in it
  if ((winhelp==null) || (winhelp.closed)) {
     winhelp = window.open("","hw",
"scrollbars,resizable,location,outerWidth=winhelpwidth,outerHeight=winhelpheigth");
  };
  winhelp.focus();
  winhelp.moveTo(winhelphorpos,winhelpverpos);
  winhelp.location=x;
};

//====================== Programmed Inheritance ===========================

function inherit(from,to) {    // Inheritance (list of methods after "to" )
  if (arguments.length == 2) { // Inherit all if no list given
    for(var p in from.prototype)
     if (typeof from.prototype[p] == "function")
       to.prototype[p] = from.prototype[p];
  } else {                     // Inherit the listed methods
    for (var i=2; i<arguments.length; i++)
     to.prototype[arguments[i]] = from.prototype[arguments[i]];
  };
};

//============================= Cookies ===================================

// These cookies functions are based on JavaScript guide functions at:
// http://developer.netscape.com/docs/manuals/communicator/jsguide4/index.htm

// Saves a cookie. Expiration date can be empty.
function saveCookie(name, value, expire) {
   document.cookie = name + "=" + escape(value)
     + ((expire == null) ? "" : ("; expires=" + expire.toGMTString()));
};

// Writes a cookie. Expiration after ndays days.
function writeCookie(name, value, ndays) {
   var today = new Date();
   var expires = new Date();
   expires.setTime(today.getTime() + 1000*60*60*24*ndays);
   saveCookie(name, value, expires);
};

// returns a cookie value or null, given the name of the cookie.
function readCookie(name) {
   //alert(document.cookie);
   if (document.cookie.length > 0) { // if there are any cookies
     var search = name + "=";
     offset = document.cookie.indexOf(search);
     //alert(document.cookie.substring(offset-2, offset-1));
     if (offset != 0) {  // If not first, search for full name
       while ((document.cookie.substring(offset-2, offset-1)!=";")&&(offset!=-1)) {
          offset++;
          offset = document.cookie.indexOf(search,offset);
       };
     };
     if (offset != -1) {           // if cookie exists
       offset += search.length;    // set index of beginning of value
       end = document.cookie.indexOf(";", offset);
                                   // set index of end of cookie value
       if (end == -1) end = document.cookie.length;
       return unescape(document.cookie.substring(offset, end));
     };
   };
   return null;
};

// returns a cookie value or a message, given the name of the cookie.
function cookietoString(name) {
   var c = readCookie(name);
   if (c == null) {
     c = "cookie does not exist";
   };
   return unescape(c);
};

// ...................... UTILITIES - head code ...........................

var winhelp = null;       // Help window object
var winhelpwidth = 600;   // Help window size and location
var winhelpheigth = 400;
var winhelphorpos = 20;
var winhelpverpos = 50;

//*************************************************************************
//**                         RANDOM NUMBERS                              **
//*************************************************************************

//====================  Theoretical Distributions =========================

function uniform(minx,maxx) {
   var u = Math.random();
   return minx + u*(maxx - minx);   // constant for minx=maxx
};

//.........................................................................

function exponential(mean) {
   var u = Math.random();
   return -mean*Math.log(u);
};

//.........................................................................

function normal(mean,std) {
    var x = 0;
    for (var i = 1; i <= 12; i++) {
       x += Math.random();
    };                              // x = standard normal: mean=0, sigma=1
    return (x - 6)*std + mean;      // constant for std=0
};

//.........................................................................

function erlang(mean,shape) {
   var expmean = mean/shape;        // exponential mean
   var x = 0;
   for (var i = 1; i <= shape; i++) {
     x += exponential(expmean)
   };
   return x;
};

//.........................................................................

function triangular(minx,likely,maxx) {
   var found = false;
   while (!found) {                 // rejection used
      var x = uniform(minx,maxx);
      var u = Math.random();
      if (x<likely) {               // left slope
        found = (u <= ((x - minx)/(likely - minx)))
      } else {
        if (x>likely) {               // right slope
          found = (u <= ((maxx - x)/(maxx - likely)))
        } else {                    // middle (constant if all same)
          found = true
        };
      };
   };
   return x;
};

//======================  User Defined Distribution =======================

function DistributionProperties(name,obj) { // Creating properties
   obj.dname = name;                    // Name
   obj.sname = name;                    // Screen name (can be shared)
   obj.xv = new Array();                // Values
   obj.px = new Array();                // Probabilities
   obj.fx = new Array();                // Cumulative Distribution
   obj.size = -1;                       // Last index (0 = 1 entry)
   obj.status = "Table Empty";          // Others: Table OK, Editing Table
   obj.confirmed = false;
   obj.enterMode = "Prob";              // Entering mode (Prob,Cumul)
   obj.generMode = "Cont";              // Generation (Cont,Discr)
   obj.disttype = "User";               // Types: User,Exp,Uni,Norm
   obj.prompt1 = "Unused";
   obj.par1 = 0;
   obj.prompt2 = "Unused";
   obj.par2 = 0;
   obj.prompt3 = "Unused";
   obj.par3 = 0;
};

function Distribution(name) {           // Constructor
   DistributionProperties(name,this);
};

//............................... methods .................................

Distribution.prototype.scrupdate = function () { with (this) {
  eval(sname + "area.value = showTable()");
  eval(sname + "s.value = status");
  eval(sname + "pro1.value = prompt1");
  eval(sname + "par1.value = par1");
  eval(sname + "pro2.value = prompt2");
  eval(sname + "par2.value = par2");
  eval(sname + "pro3.value = prompt3");
  eval(sname + "par3.value = par3");
}};

//.........................................................................

Distribution.prototype.update = function (index) { with (this) {
 switch (index) {
  case 0 :
      disttype = "Exp";
      prompt1 = "Mean";
      par1 = 1;
      prompt2 = "Unused";
      par2 = 0;
      prompt3 = "Unused";
      par3 = 0;
      break;
  case 1 :
      disttype = "User";
      prompt1 = "Unused";
      par1 = 0;
      prompt2 = "Unused";
      par2 = 0;
      prompt3 = "Unused";
      par3 = 0;
      break;
  case 2 :
      disttype = "Uni";
      prompt1 = "Min";
      par1 = 0;
      prompt2 = "Max";
      par2 = 1;
      prompt3 = "Unused";
      par3 = 0;
      break;
  case 3 :
      disttype = "Norm";
      prompt1 = "Mean";
      par1 = 3;
      prompt2 = "Std Dev";
      par2 = 1;
      prompt3 = "Unused";
      par3 = 0;
      break;
  case 4 :
      disttype = "Erlng";
      prompt1 = "Mean";
      par1 = 1;
      prompt2 = "Shape";
      par2 = 2;
      prompt3 = "Unused";
      par3 = 0;
      break;
  case 5 :
      disttype = "Triang";
      prompt1 = "Min";
      par1 = 1;
      prompt2 = "Likely";
      par2 = 2;
      prompt3 = "Max";
      par3 = 4;
      break;
  default : alert("Index out of range - update.");
 };
 eval(sname + "pro1.value = prompt1");
 eval(sname + "par1.value = par1");
 eval(sname + "pro2.value = prompt2");
 eval(sname + "par2.value = par2");
 eval(sname + "pro3.value = prompt3");
 eval(sname + "par3.value = par3");
}};

//.........................................................................

Distribution.prototype.testpar1 = function (tx) { with (this) {
  var x = parseFloat(tx);
  if (! syntaxOK(x)) { return false };
  switch (disttype) {
   case "User" :
      alert("Parameter not used.");
      return false;
   case "Exp" :
      if (x <= 0) {
        alert("The mean value must be positive.");
        return false;
      };
      break;
   case "Uni" :
      if (x > par2) {
        alert("The minimum value must not be bigger than the maximum one (" + par2 + ").");
        return false;
      };
      break;
   case "Norm" :
      break;
   case "Erlng" :
      if (x <= 0) {
        alert("The mean value must be positive.");
        return false;
      };
      break;
   case "Triang" :
      if (x > par2) {
        alert("The minimum value must not be bigger than the most likely one (" + par2 + ").");
        return false;
      };
      break;
   default : alert("Index out of range - test parameter 1.");
  };
  par1 = x;
  return true;
}};

//.........................................................................

Distribution.prototype.testpar2 = function (tx) { with (this) {
  var x = parseFloat(tx);
  if (! syntaxOK(x)) { return false };
  switch (disttype) {
   case "User" :
      alert("Parameter not used.");
      return false;
   case "Exp" :
      alert("Parameter not used.");
      return false;
   case "Uni" :
      if (x < par1) {
        alert("The maximum value must not be smaller than the minimum one (" + par1 + ").");
        return false;
      };
      break;
   case "Norm" :
      if (x < 0) {
        alert("The Standard Deviation must not be negative.");
        return false;
      };
      break;
   case "Erlng" :
      if (! testPosIntValue(tx)) { return false };
      x = parseInt(tx);
      eval(sname + "par2.value = x");
      break;
   case "Triang" :
      if (x < par1 || x > par3) {
        alert("The most likely value must not be out of the range [" + par1 + ", " + par3 + "].");
        return false;
      };
      break;
   default : alert("Index out of range - test parameter 2.");
  };
  par2 = x;
  return true;
}};

//.........................................................................

Distribution.prototype.testpar3 = function (tx) { with (this) {
  var x = parseFloat(tx);
  if (! syntaxOK(x)) { return false };
  switch (disttype) {
   case "User" :
      alert("Parameter not used.");
      return false;
   case "Exp" :
      alert("Parameter not used.");
      return false;
   case "Uni" :
      alert("Parameter not used.");
      return false;
   case "Norm" :
      alert("Parameter not used.");
      return false;
   case "Erlng" :
      alert("Parameter not used.");
      return false;
   case "Triang" :
      if (x < par2) {
        alert("The maximum value must not be smaller than the most likely one (" + par2 + ").");
        return false;
      };
      break;
   default : alert("Index out of range - test parameter 3.");
  };
  par3 = x;
  return true;
}};


//.........................................................................

Distribution.prototype.fixtable = function () { with (this) {
  if (enterMode=="Prob") {               // Entering probabilities
         fx[0] = px[0];
         for (var i = 1; i <= size; i++) {
            fx[i] = fx[i-1] + px[i] }
  } else {                               // Entering distr. function
         px[0] = fx[0];
         for (var i = 1; i <= size; i++) {
            px[i] = fx[i] - fx[i-1] }
  };
  return true;
}};

//.........................................................................

Distribution.prototype.last = function () { with (this) {
  if (size < 0) {
    return 0
  } else {
    return xv[size];
  };
}};

//.........................................................................

Distribution.prototype.compareWithLastValue = function (textElement) { with (this) {
  var x = parseFloat(textElement);
  if (! syntaxOK(x)) { return false };
  if (size >= 0) {
    if (x < xv[size]) {
        alert("The value ( " + x + " ) must not be smaller than the previous one ( "
              + xv[size] + " ).");
        return false;
    }
  };
  return true;
}};

//.........................................................................

Distribution.prototype.showTable = function () { with (this) {
   var y = " #  x         p(x)          F(x)" + "\n"
         + " ===================================" + "\n";
   var s = "";
   var l = "";
   for (var i = 0; i <= size; i++) {
     l = " " + i;
     l += spaces(4-l.length) + xv[i];
     s = "" + px[i];
     l += spaces(14-l.length) + s.slice(0,12);
     s = "" + fx[i];
     l += spaces(28-l.length) + s.slice(0,12) + "\n";
     y += l;
   };
   if (size==-1) {
      status = "Table Empty";
   } else {
     if (confirmed) {
      status = "Table OK";
     } else {
      status = "Editing Table";
     }
   };
   return y;
}};

//.........................................................................

Distribution.prototype.accept = function (tx,tp) { with (this) {
 if (enterMode == "Prob") {                // Entering probabilities
   confirmed = false;
   status = "Editing Table";
   size++;
   xv[size] = parseFloat(tx);
   px[size] = parseFloat(tp);
   if (size==0) {
     fx[size] = px[size];
   } else {
     fx[size] = fx[size-1] + px[size];
   }
 } else {                                  // Entering distr. function
   var fxx = parseFloat(tp);
   if (fxx < fx[size]) {
     alert("The F(x) value ( " + fxx + " ) must not be smaller than the previous one ( "
            + fx[size] + " ).");
     return false;
   } else {
     confirmed = false;
     status = "Editing Table";
     size++;
     xv[size] = parseFloat(tx);
     fx[size] = fxx;
     if (size==0) {
       px[size] = fx[size];
     } else {
       px[size] = fx[size] - fx[size-1];
     }
   }
 };
 scrupdate();
 return true;
}};

//.........................................................................

Distribution.prototype.edit = function () { with (this) {
  var n = parseInt(prompt("Enter the number of the entry to be edited:"));
  if ((isNaN(n))||(n<0)||(n>size)) {
     alert("Not a correct entry number or empty table.");
     return false;
  };
  var x = parseFloat(prompt("Enter the new value (x) of the entry " + n + " :",xv[n]));
  if (! syntaxOK(x)) { return false };
  if (enterMode=="Prob") {                         // Entering probabilities
       var p = parseFloat(prompt("Enter the new probability (p) of the entry "
                                 + n + " :",px[n]));
  } else {                                         // Entering distr. function
       var p = parseFloat(prompt("Enter the new cumulative probability (F) of the entry "
                                 + n + " :",fx[n]))
  };
  if (isNaN(p)) {
       alert("Syntax error - the probability is not a number.");
       return false;
  };
  if ((p < 0) || (p > 1)) {
       alert("The probability must be a non-negative number not greater than 1.");
       return false
  };                                               // All seems OK
  confirmed = false;
  status = "Editing Table";
  xv[n] = x;
  if (enterMode=="Prob") {                         // Entering probabilities
    px[n] = p;
  } else {
    fx[n] = p;
  };
  fixtable();
  scrupdate();
  return true;
}};

//.........................................................................

Distribution.prototype.insert = function insert() { with (this) {
  var n = parseInt(prompt("Enter the number of the entry to be inserted (the current entry with this number and all succeeding will be moved):"));
  if ((isNaN(n))||(n<0)||(n>size)) {
     alert("Not a correct entry number or empty table.");
     return false;
  };
  var x = parseFloat(prompt("Enter the value (x) of the entry " + n + " :",xv[n]));
  if (! syntaxOK(x)) { return false };
  if (enterMode=="Prob") {                         // Entering probabilities
       var p = parseFloat(prompt("Enter the probability (p) of the entry "
                                 + n + " :",px[n]));
  } else {                                         // Entering distr. function
       var p = parseFloat(prompt("Enter the cumulative probability (F) of the entry "
                                 + n + " :",fx[n]))
  };
  if (isNaN(p)) {
       alert("Syntax error - the probability is not a number.");
       return false;
  };
  if ((p < 0) || (p > 1)) {
       alert("The probability must be a non-negative number not greater than 1.");
       return false;
  };                                                  // All seems OK
  confirmed = false;
  status = "Editing Table";
  size++;
  for (var i = size; i > n; i--) {                    // Shift all up
       xv[i] = xv[i-1];
       px[i] = px[i-1];
       fx[i] = fx[i-1];
  };
  xv[n] = x;
  if (enterMode=="Prob") {                            // Entering probabilities
    px[n] = p;
  } else {
    fx[n] = p;
  };
  fixtable();
  scrupdate();
  return true;
}};

//.........................................................................

Distribution.prototype.deleteit = function () { with (this) {
  var n = parseInt(prompt("Enter the number of the entry to be deleted:"));
  if ((isNaN(n))||(n<0)||(n>size)) {
       alert("Not a correct entry number or empty table.");
       return false
  };
  confirmed = false;
  status = "Editing Table";
  if (size==0) {              // one item only
       size = -1;
       xv.length = 0;         // truncating the arrays
       px.length = 0;
       fx.length = 0;
  } else {
       for (var i = n; i <= size-1; i++) {        // Move the rest down
            xv[i] = xv[i+1];
            px[i] = px[i+1];
            fx[i] = fx[i+1];
       };
       size--;
       fixtable()
  };
  scrupdate();
  return true;
}};

//.........................................................................

Distribution.prototype.cleartable = function () { with (this) {
   size = -1;
   xv.length = 0;
   px.length = 0;
   fx.length  = 0;
   confirmed = false;
   status = "Table Empty";
   scrupdate();
   return true;
}};

//.........................................................................

Distribution.prototype.confirmtable = function () { with (this) {
   confirmed = false;
   if (size == -1) {
      alert("The table is empty !");
      return false;
   };
   for (var i = 1; i <= size; i++) {
      if (xv[i] < xv[i-1]) {
        alert("The values (x) in the table are not in non-decreasing order. This has to be corrected.");
        return false;
      }
   };
  if (enterMode=="Prob") {          // Entering probabilities
     fx[0] = px[0];
     var sofarOK = true;
     for (var i = 1; i <= size; i++) {
        fx[i] = fx[i-1] + px[i];
        if (fx[i]>1) {
          fx[i] = 1;
          sofarOK = false;
        }
     };
     if (! sofarOK) {
        alert("Sum of probabilities is greater than 1. That's why some F(x) were truncated to 1.");
     }
   } else {                         // Entering distr. function
     px[0] = fx[0];
     for (var i = 1; i <= size; i++) {
        if (fx[i] < fx[i-1]) {
          alert("The values of the cumulative distribution are not in non-decreasing order. This has to be corrected.");
          return false;
        };
        px[i] = fx[i] - fx[i-1];
     }
   };
   if ((px[0]>0) && (generMode=="Cont")) {
     if (! confirm("The first probability value of a continuous random variable is greater than 0. Note that the generator will assume the implicit starting point (0,0). Is this correct ?")) {
       return false;
     }
   };
   if (fx[size]<1) {
     if (confirm("Sum of probabilities is less than 1, but the last value of F(x) must be equal to 1. Do you want to set it to value 1 ?")) {
        fx[size] = 1;
     } else return false;
   };
   confirmed = true;
   status = "Table OK";
   scrupdate();
   return true;
}};

//.........................................................................

Distribution.prototype.generate = function () { with (this) {  // Generates random number
 switch (disttype) {
  case "User" :
   var x1 = 0, f1 = 0, i = 0;
   var x2 = xv[0], f2 = fx[0];     // Assumes OK table
   var r = Math.random();
   while (r > f2) {
        x1 = x2; f1 = f2;
        x2 = xv[++i], f2 = fx[i];
   };
   if (generMode=="Discr") {
     return x2;
   } else {                        // Interpolation
     return x1 + (x2-x1)*(r-f1)/(f2-f1);
   }
  case "Exp" :    return exponential(par1);
  case "Uni" :    return uniform(par1,par2);
  case "Norm" :   return normal(par1,par2);
  case "Erlng" :  return erlang(par1,par2);
  case "Triang" : return triangular(par1,par2,par3);
  default :
   alert("Index out of range - generate.");
   return false;
 };
}};

//.........................................................................

Distribution.prototype.save = function(cn) { with (this) {
// Saves distribution to cookie cn, Name = cn , Expiration 1 year
// Value = parameters, table(n (last index or -1), status, mode, x0, fx0, ... fxn),
   if (cn==null) {
     cn = dname;   // Default name
     cn = prompt("Confirm the default or modify the cookie name:",cn);
   };
   if (cn!=null) {
     writeCookie(cn,toString(),365);
   };
}};

//.........................................................................

Distribution.prototype.toString = function() { with (this) { // Converts distribution to string
   var cv = "";
   switch (disttype) {   // Number of parameters depends on type
    case "Exp" :
      cv += 0 + ",";
      cv += par1 + ",";
      break;
    case "User" :
      cv += 1 + ",";
      break;
    case "Uni" :
      cv += 2 + ",";
      cv += par1 + ",";
      cv += par2 + ",";
      break;
    case "Norm" :
      cv += 3 + ",";
      cv += par1 + ",";
      cv += par2 + ",";
      break;
    case "Erlng" :
      cv += 4 + ",";
      cv += par1 + ",";
      cv += par2 + ",";
      break;
    case "Triang" :
      cv += 5 + ",";
      cv += par1 + ",";
      cv += par2 + ",";
      cv += par3 + ",";
      break;
    default :
      alert("Error 1");
      return "";
   };
   if (disttype=="User") {
     cv += size + ",";      // Last index
     switch (status) {      // Status
      case "Table Empty" :
       cv += 0 + ",";
       break;
      case "Editing Table" :
       cv += 1 + ",";
       break;
      case "Table OK" :
       cv += 2 + ",";
       break;
      default :
       alert("Error 2");
       return "";
     };
     switch (generMode) {   // Generation mode
      case "Cont" :
       cv += 0 + ",";
       break;
      case "Discr" :
       cv += 1 + ",";
       break;
      default :
       alert("Error 3");
       return "";
     };
     for (var i = 0; i <= size; i++) {  // Table
        cv += xv[i] + "," + fx[i] + ",";
     };
   };
   return cv;
}};

//.........................................................................

Distribution.prototype.load = function(cn) { with (this) { // Loads distribution from cookie cn
   if (cn==null) {
     cn = dname;        // Default name
     cn = prompt("Confirm the default or modify the cookie name:",cn);
   };
   if (cn!=null) {
     var cv = readCookie(cn);
     if (cv == null) {
        alert("Cookie " + cn + " not found. Read Help.");
     } else {
        var dtype = fromString(cv);
        eval(sname + "dsel.selectedIndex = dtype"); // type on screen
        scrupdate();
        if (generMode == "Cont") {
          eval(sname + "gen[0].checked = true");
        } else {
          eval(sname + "gen[1].checked = true");
        };
        return true;
     };
   };
   return false;
}};

//.........................................................................

Distribution.prototype.fromString = function(cv) { with (this) {
// Loads distribution from string cv
// Value = type,[p1,[p2,]][n(last index or -1),status,mode, x0, fx0, ... fxn,]
// Returns distribution type
   var x = 0;
   var p = 0;       // number of parameters
   var from = 0;
   var to = cv.indexOf(",");
   var dtype = parseInt(cv.substring(from,to));    // distribution type
   switch (dtype) {
    case 0 :
      disttype = "Exp";
      prompt1 = "Mean";
      prompt2 = "Unused";
      par2 = 0;
      prompt3 = "Unused";
      par3 = 0;
      p = 1;
      break;
    case 1 :
      disttype = "User";
      prompt1 = "Unused";
      par1 = 0;
      prompt2 = "Unused";
      par2 = 0;
      prompt3 = "Unused";
      par3 = 0;
      p = 0;
      break;
    case 2 :
      disttype = "Uni";
      prompt1 = "Min";
      prompt2 = "Max";
      prompt3 = "Unused";
      par3 = 0;
      p = 2;
      break;
    case 3 :
      disttype = "Norm";
      prompt1 = "Mean";
      prompt2 = "Std Dev";
      prompt3 = "Unused";
      par3 = 0;
      p = 2;
      break;
    case 4 :
      disttype = "Erlng";
      prompt1 = "Mean";
      prompt2 = "Shape";
      prompt3 = "Unused";
      par3 = 0;
      p = 2;
      break;
    case 5 :
      disttype = "Triang";
      prompt1 = "Min";
      prompt2 = "Likely";
      prompt3 = "Max";
      p = 3;
      break;
    default :
      alert("Error 4");
      return false;
   };
   if (p>0) {    // 1st parameter
     from = ++to;
     to = cv.indexOf(",",to);
     par1 = parseFloat(cv.substring(from,to));
     if (p>1) {  // 2nd parameter
       from = ++to;
       to = cv.indexOf(",",to);
       if (disttype=="Erlng") {
         par2 = parseInt(cv.substring(from,to));
       } else {
         par2 = parseFloat(cv.substring(from,to));
       };
       if (p>2) { // 3rd parameter
         from = ++to;
         to = cv.indexOf(",",to);
         par3 = parseFloat(cv.substring(from,to));
       }
     }
   };
   if (disttype=="User") {
     from = ++to;
     to = cv.indexOf(",",to);
     size = parseInt(cv.substring(from,to)); // size
     xv = new Array(size+1);
     px = new Array(size+1);
     fx = new Array(size+1);
     from = ++to;
     to = cv.indexOf(",",to);
     x = parseInt(cv.substring(from,to)); // status
     switch (x) {
      case 0 :
       status = "Table Empty";
       break;
      case 1 :
       status = "Editing Table";
       break;
      case 2 :
       status = "Table OK";
       break;
      default :
       alert("Error 5");
       return false;
     };
     confirmed = (status == "Table OK");
     from = ++to;
     to = cv.indexOf(",",to);
     x = parseInt(cv.substring(from,to)); // mode
     switch (x) {
      case 0 :
       generMode = "Cont";
       break;
      case 1 :
       generMode = "Discr";
       break;
      default :
       alert("Error 6");
       return false;
     };
     for (var i = 0; i <= size; i++) { // table
       from = ++to;
       to = cv.indexOf(",",to);
       xv[i] = parseFloat(cv.substring(from, to));  // xv
       from = ++to;
       to = cv.indexOf(",",to);
       fx[i] = parseFloat(cv.substring(from, to));  // fx
       if (i==0) {                                  // px
         px[i] = fx[i];
       } else {
         px[i] = fx[i] - fx[i-1];
       };
     };
   };
   return dtype;
}};

//==================  User Defined Discrete Distribution ===================

function DiscreteDistributionProperties(name,obj) { // Creating properties
   obj.dname = name;                    // Name
   obj.sname = name;                    // Screen name (can be shared)
   obj.xv = new Array();                // Values
   obj.xv[0] = 1;                       // Initially 1 with probability 1
   obj.px = new Array();                // Probabilities
   obj.px[0] = 1;
   obj.fx = new Array();                // Cumulative Distribution
   obj.fx[0] = 1;
   obj.size = 0;                        // Last index (0 = 1 entry)
   obj.status = "Table OK";             // Others: Table OK, Editing Table
   obj.confirmed = true;
   obj.disttype = "User";               // Types: User,Exp,Uni,Norm
   obj.enterMode = "Prob";              // Entering mode (Prob,Cumul)
   obj.generMode = "Discr";             // Generation (Cont,Discr)
};

function DiscreteDistribution(name) {    // Constructor
   DiscreteDistributionProperties(name,this);
};

//............................... methods .................................

inherit(Distribution,DiscreteDistribution,
        'fixtable','last','compareWithLastValue','showTable',
        'accept','edit','insert','deleteit','cleartable','confirmtable',
        'generate','save','toString','fromString');

//......................... these are different ...........................

DiscreteDistribution.prototype.scrupdate = function () { with (this) {
  eval(sname + "area.value = showTable()");
  eval(sname + "s.value = status");
}};

//.........................................................................

DiscreteDistribution.prototype.load = function(cn) { with (this) {
                         // Loads distribution from cookie cn
   if (cn==null) {
     cn = dname;         // Default name
     cn = prompt("Confirm the default or modify the cookie name:",cn);
   };
   if (cn!=null) {
     var cv = readCookie(cn);
     if (cv == null) {
        alert("Cookie " + cn + " not found. Read Help.");
     } else {
        fromString(cv);
        scrupdate();
        return true;
     };
   };
   return false;
}};

//*************************************************************************
//**                          STATISTICS                                 **
//*************************************************************************

//=========================== Accumulator =================================

function accumulator(x) {  // Time integral object, x=initial value
  this.value = x;          // The value
  this.max = x;            // So far maximum value
  this.min = x;            // So far minimum value
  this.sum = 0;            // The integral
  this.sumSq = 0;          // Integral of squares
  this.initTime = time;    // Initialisation time
  this.lastTime = time;    // Last update time
};

//........................ accumulator methods ............................

accumulator.prototype.initiate = function(x) { with (this) {
  value = x;
  max = x;
  min = x;
  sum = 0;
  sumSq = 0;
  initTime = time;
  lastTime = time;
}};

accumulator.prototype.scrupdate = function(dname) { with (this) {
  eval(dname + "av.value = average()");
  eval(dname + "mi.value = min");
  eval(dname + "ma.value = max");
  eval(dname + "sd.value = stdDev()");
}};

accumulator.prototype.winupdate = function(stitle,w) { with (this) {
  w.writeln(stitle + " statistics:" + "<BR><UL>");
  w.writeln("<LI> Average: " + average());
  w.writeln("<LI> Minimum: " + min);
  w.writeln("<LI> Maximum: " + max);
  w.writeln("<LI> Std Dev: " + stdDev() + "</UL>");
}};

accumulator.prototype.updateto = function(newx) { with (this) {
  var d = (time - lastTime)*value;  // integral increment
  sum += d;
  sumSq += d*value;
  lastTime = time;
  value = newx;
  if (newx>max) max = newx;
  if (newx<min) min = newx;
}};

accumulator.prototype.updateby = function(delta) { with (this) {
  updateto(value+delta);
}};

accumulator.prototype.average = function() { with (this) {
  if (time>initTime) {
    updateby(0);
    return sum/(time - initTime);
  } else {
    return 0
  };
}};

accumulator.prototype.variance = function() { with (this) {
  if (time>initTime) {
    var a = average();
    return sumSq/(time - initTime) - a*a
  } else {
    return 0
  };
}};

accumulator.prototype.stdDev = function() { with (this) {
  return Math.sqrt(variance())
}};

//=============================== Tally ===================================

function tally() {      // Tally (no integral) object
  this.max = 0;         // So far maximum value
  this.min = 0;         // So far minimum value
  this.sum = 0;         // Sum of assigned values
  this.sumSq = 0;       // Sum of squares
  this.updated = 0;     // Number of updates
  this.empty = true;    // Initialized by first update
};

//............................ tally methods ..............................

tally.prototype.initiate = function() { with (this) {
  max = 0;
  min = 0;
  sum = 0;
  sumSq = 0;
  updated = 0;
  empty = true;
}};

tally.prototype.scrupdate = function(dname) { with (this) {
  eval(dname + "av.value = average()");
  eval(dname + "mi.value = min");
  eval(dname + "ma.value = max");
  eval(dname + "sd.value = stdDev()");
}};

tally.prototype.winupdate = function(stitle,w) { with (this) {
  w.writeln(stitle + " statistics:" + "<BR><UL>");
  w.writeln("<LI> Average: " + average());
  w.writeln("<LI> Minimum: " + min);
  w.writeln("<LI> Maximum: " + max);
  w.writeln("<LI> Std Dev: " + stdDev() + "</UL>");
}};

tally.prototype.update = function(newx) { with (this) {
  if (empty) {
    sum = newx;
    sumSq = newx*newx;
    max = newx;
    min = newx;
    updated = 1;
    empty = false;
  } else {
    updated++;
    sum += newx;
    sumSq += newx*newx;
    if (newx>max) max = newx;
    if (newx<min) min = newx;
  };
}};

tally.prototype.average = function() { with (this) {
  if (!empty) {
    return sum/updated
  } else {
    return 0
  };
}};

tally.prototype.variance = function() { with (this) {
  if (!empty) {
    var a = average();
    return sumSq/updated - a*a
  } else {
    return 0
  };
}};

tally.prototype.stdDev = function() { with (this) {
  return Math.sqrt(variance())
}};

//*************************************************************************
//**                    DATA STRUCTURES & QUEUES                         **
//*************************************************************************

//======================== Heap (balanced tree) ===========================

function Heap(name) {           // Constructor
   this.hname = name;           // name
   this.hsize = 0;              // last index used (1=top!) = actual length
   this.harray = new Array();   // heap array will grow
};

//................ Heap methods (SMALLEST FIRST) ..........................

Heap.prototype.empty = function() {return this.hsize == 0};

//.........................................................................

Heap.prototype.clear = function() {with(this){ // deletes heap from RAM
   hsize = 0;
   harray.length=0;
}};

//.........................................................................

Heap.prototype.insert = function(x) {with(this){ // inserts x to heap
   hsize++;
   var i=hsize;                   // all values must have comparable keys !
   while ((i>1)&&(harray[i>>1].key>x.key)){ // Sift Up, i>>1 = parent's index
      harray[i]=harray[i>>1];     // move parent down
      i=i>>1;
   };
   harray[i]=x;
}};

//.........................................................................

Heap.prototype.remove = function(x) {with(this){ // removes x from heap
   var i=0;
   for (i=1; i<=hsize; i++) {         // Search first
     if (harray[i]==x) break;
   };
   if (!(harray[i]==x)) return false; // Not found
   if (i==hsize) {                    // Was the last one
     hsize--;
   } else {
     harray[i] = harray[hsize--];     // Overwrite x by last
     if ((i>1)&&(harray[i>>1].key>harray[i].key)) {
       var t=harray[i];               // Restoring heap UP
       while ((i>1)&&(harray[i>>1].key>t.key)) {  // Sift Up, i>>1 = parent's index
         harray[i]=harray[i>>1];      // move parent down
         i=i>>1;
       };
       harray[i]=t;
     } else {
       restoreheap(i);                // Restoring heap DOWN
     };
   };
   harray.length=hsize+1;             // Truncate the array
   return true;
}};

//.........................................................................

Heap.prototype.restoreheap = function(i) {with(this){
   // restores heap properties from position i down
   var done=(hsize<=1);
   var left=0;
   var right=0;
   var smallest=0;
   while (!done) {                    // Sift Down
        left=i<<1;                    // the two children
        right=left+1;
        if (left>hsize) {             // left does not exist - done
          done=true
        } else {                      // left exists
          smallest = (harray[left].key<harray[i].key) ? left : i;
          if (right<=hsize) {         // right exists
             smallest = (harray[right].key<harray[smallest].key) ? right : smallest;
          };
          if (smallest!=i) {          // swap necessary
             var t=harray[i];
             harray[i]=harray[smallest];
             harray[smallest]=t;
             i=smallest;              // move down
          } else {
             done=true;               // sifting finished
          };
        };
     };
}};

//.........................................................................

Heap.prototype.getfirst = function() {with(this){
   if (empty()) {                     // removes and returns the first item
     return false
   } else {
     var x=harray[1];                 // top (smallest) item
     harray[1]=harray[hsize];         // last goes to top
     harray.length=hsize;             // Truncate the array
     hsize--;
     restoreheap(1);                  // restore heap properties
     return x;
   };
}};

// **************************** QUEUES ************************************

//=========================== Generic Queue ===============================

function GenQueueProperties(name,obj) {    // Creating properties
   obj.qname = name;                       // name
   obj.first = null;                       // first item
   obj.last = null;                        // last item
   obj.qlength = 0;                        // actual queue length
};

function GenQueue(name) {                  // Constructor
   GenQueueProperties(name,this);
};

//....................... Generic Queue methods ...........................

GenQueue.prototype.empty = function() {return this.qlength == 0};

GenQueue.prototype.geninit = function() { with (this) {
   first = null;
   last = null;
   qlength = 0;
}};

//========================== Statistics Queue =============================

function StatQueueProperties(obj)  // Creating properties
{  obj.qlength = 0;                // actual queue length
   obj.maxqlength = 0;             // maximum queue length
   obj.qlsum = 0;                  // queue length integral
   obj.qlsumsq = 0;                // queue length square integral
   obj.initTime = 0;               // initialization time
   obj.qlsumt = 0;                 // queue length integral last update
};

function StatQueue() {             // Constructor
   StatQueueProperties(this);
};

//...................... Statistics Queue methods .........................

StatQueue.prototype.statinit = function(tim) { with (this) {
   maxqlength = 0;
   qlsum = 0;
   qlsumsq = 0;
   qlsumt = tim;
   initTime = tim;
}};

StatQueue.prototype.scrupdate = function(dname) { with (this) {
  eval(dname + qname + "av.value = average()");
  eval(dname + qname + "ma.value = maxqlength");
  eval(dname + qname + "sd.value = stdDev()");
}};

StatQueue.prototype.winupdate = function(stitle,w) { with (this) {
  w.writeln(stitle + " length statistics:" + "<BR><UL>");
  w.writeln("<LI> Average: " + average());
  w.writeln("<LI> Maximum: " + maxqlength);
  w.writeln("<LI> Std Dev: " + stdDev() + "</UL>");
}};

StatQueue.prototype.accumulate = function() { with (this) {
   var x = qlength*(time-qlsumt);
   qlsum += x;
   qlsumsq += qlength*x;
   qlsumt = time;
}};

StatQueue.prototype.average = function() { with (this) {
   if (time>initTime) {
     accumulate();
     return qlsum/(time - initTime);
   } else {
     return 0;
   };
}};

StatQueue.prototype.variance = function() { with (this) {
  if (time>initTime) {
    var a = average();
    return qlsumsq/(time - initTime) - a*a;
  } else {
    return 0;
  };
}};

StatQueue.prototype.stdDev = function() { with (this) {
  return Math.sqrt(variance());
}};

//=========== FIFO Queue (Inherits from GenQueue AND StatQueue) ===========

function FifoQueueProperties(name,obj) { // Creating properties
   GenQueueProperties(name,obj);
   StatQueueProperties(obj);
};

function FifoQueue(name) {               // Constructor
   FifoQueueProperties(name,this);
};

//........................ FIFO Queue methods .............................

// "Inherited" methods: (Multiple inheritance)

inherit(GenQueue,FifoQueue);
inherit(StatQueue,FifoQueue);

// New FIFO Queue methods:

FifoQueue.prototype.initiate = function (time) { with (this) {
   geninit();
   statinit(time);
}};

FifoQueue.prototype.enqueue = function(x) { with (this) {
   accumulate();
   x.next = null;
   if (empty()) {
     first = x;
     last = x;
   } else {
     last.next = x;
     last = x;
   };
   qlength++;
   if (qlength > maxqlength) { maxqlength = qlength };
}};

FifoQueue.prototype.removefirst = function () { with (this) {
  if (empty()) {
   return null;
  } else {
   accumulate();
   qlength--;
   var x = first;
   first = x.next;
   if (first == null) { last = null };    // empty now
   return x;
  };
}};

//================== LIFO queue (Inherits from FIFO) ======================

function LifoQueueProperties(name,obj) {  // Creating properties
   FifoQueueProperties(name,obj);
};

function LifoQueue(name) {                // Constructor
   LifoQueueProperties(name,this);
};

//....................... LIFO queue methods ..............................

// "Inherited" methods:

inherit(FifoQueue,LifoQueue);

// Modified LIFO Queue method:

LifoQueue.prototype.enqueue = function(x) { with (this) {
   accumulate();
   x.next = first;          // first = top (last not used)
   first = x;
   qlength++;
   if (qlength > maxqlength) { maxqlength = qlength };
}};

//================ PRIORITY queue (Inherits from FIFO) ====================

function PriorityQueueProperties(name,obj) {  // Creating properties
   FifoQueueProperties(name,obj);
};

function PriorityQueue(name) {          // Constructor
   PriorityQueueProperties(name,this);
};

//....................... PRIORITY queue methods ..........................

// "Inherited" methods:

inherit(FifoQueue,PriorityQueue);

// Modified PRIORITY Queue method:

PriorityQueue.prototype.enqueue = function(x) { with (this) {
   // assumes x.priority exists (ascending ordering)
   accumulate();
   x.next = first;          // go to top (last not used)
   first = x;
   var done = false;
   var prev = null;         // previous
   while ((!done) && (x.next != null)) {
      if (x.priority >= x.next.priority) {
         if (prev==null) {  // move behind next
            first = x.next;
            prev = first;
         } else {
            prev.next = x.next;
            prev = x.next;
         };
         x.next = prev.next;
         prev.next = x;
      } else {
         done = true;
      };
   };
   qlength++;
   if (qlength > maxqlength) { maxqlength = qlength };
}};

PriorityQueue.prototype.clear = function() { with (this) {
   initiate();
}};

// Following methods make priorityqueue compatible with heap
// Operations repeated to avoid statistics

PriorityQueue.prototype.insert = function(x) { with (this) {
   x.next = first;          // go to top (last not used)
   first = x;
   var done = false;
   var prev = null;         // previous
   while ((!done) && (x.next != null)) {
      if (x.key >= x.next.key) {
         if (prev==null) {  // move behind next
            first = x.next;
            prev = first;
         } else {
            prev.next = x.next;
            prev = x.next;
         };
         x.next = prev.next;
         prev.next = x;
      } else {
         done = true;
      };
   };
   qlength++;
}};

PriorityQueue.prototype.remove = function(x) { with (this) {
   var tested = first;
   var prev = null;      // previous
   while ((tested != x) && (tested != null)) {
      prev = tested;
      tested = tested.next;
   };
   if (tested != null) {
     if (prev==null) {   // removing
       first = x.next;   // was first
     } else {
       prev.next = x.next;
     };
     qlength--;
     return true;
   } else {
     return false;
   };
}};

PriorityQueue.prototype.getfirst = function() { with (this) {
  // no test (assumed not empty)
  var x = first;
  first = x.next;
  qlength--;
  return x;
}};

//*************************************************************************
//**                        SIMULATION ENGINE                            **
//*************************************************************************

function evnotice() {        // Event notice : Constructor
   this.key = 0;             // event time (set by schedule)
};                           // other attributes added by user

//.........................................................................

function schedule(event,tim) {
   if (tim<time) tim=time;   // cannot go back in time
   event.key = tim;          // event time
   SQS.insert(event);        // enqueue
};

//.........................................................................

function cancel(event) {
   return SQS.remove(event); // remove from SQS
};

//.........................................................................

function initialize_run() {  // Simulatiom run initialization (not user part)
  time = 0;                  // simulated time
  SQS.clear();               // clearing sequencing set
};

//.........................................................................

function modeltime() {
   return time;              // Returns model time
};

//.........................................................................

function runduration() {
   var timenow = new Date();
   return timenow.getTime()-starttime.getTime();  // Run duration so far
};

//.........................................................................
function simulation_run(stats,length) {
  // Simulatiom run (reports time if stats=true, length used only for status))
  var event = null;
  var tail = " of " + length;
  var excontinue = true;     // Whether to continue the experiment
  if (!stats) {
     window.status = "Simulation in progress, wait please ...";
  };
  starttime = new Date();

  while (excontinue && (!SQS.empty())) {
     event = SQS.getfirst(); // Next event notice
     time = event.key;       // Update time
     excontinue = !finish_run();
     if (excontinue) {
       if (stats) {
        window.status = "Running - Time: " + Math.round(time) + tail;
       };
       eventroutine(event);  // The event (the rest is user responsibility)
     };
  };

  window.status = " ";
  var finish = new Date();
  alert('Experiment started at: '+starttime.toGMTString()+'\n'+
        'Experiment finished at: '+finish.toGMTString()+'\n'+
        'Duration in ms: '+runduration());
};


//*************************************************************************
//**                        RANDOM GENERATOR                             **
//*************************************************************************

function jsrand(n) {               // Random generator (stream n), n not tested for speed !
  rand_seed[n-1] = (rand_seed[n-1]*rand_a + rand_c) % rand_m; // x(i) = [a*x(i-1) + c] mod m
  return rand_seed[n-1]/rand_m;                               // u(i) = x(i)/m
};

//.........................................................................

function jsrandomize(n) {          // Randomize initial seed n
  rand_seed[n-1] = Math.floor(Math.random()*100000);
  return true;
};

//.........................................................................

function jsrandomizeall() {        // Randomize all initial seeds
  for (var i = 1; i <= streams; i++) {
    randomize(i);
  };
  return true;
};

//.........................................................................

function jsrandinit(m) {           // Create and randomize m streams
  rand_seed.legth = 0;               // truncating seed array
  rand_seed[m-1] = 0;                // fixing the array size
  rand_streams = m;
  jsrandomizeall();
  return true;
};

//.........................................................................

function jsseedinit(n,s) {           // Initialize seed (stream) n
  rand_seed[n-1] = s;
  return true;
};

//======================== jssim: Head Code ===============================

var time = 0;                        // simulated time
var starttime = 0;                   // experiment start real time
var SQS = new PriorityQueue("SQS");  // sequencing set
// Use priority queue for smaller models - up to about 100 sources of events
// var SQS = new Heap("SQS");
// Use heap for big models only
                                     // Default LCG = MINSTD
                                     // Formulae: x(i) = [a*x(i-1) + c] mod m
                                     //           u(i) = x(i)/m
var rand_m = 2147483647;             // m = 2^31-1
var rand_a = 16807;                  // a = 7^5
var rand_c = 0;                      // c = 0
                                     // one stream created and randomized:
var rand_streams = 1;                // number of random streams
var rand_seed = new Array();         // seeds of the random generator
rand_seed[0] = Math.floor(Math.random()*100000);   // Randomized seed 1

//=========================================================================


