//** G/G/s/c/p JavaScript Code (commented version): Random distributions **
// (c) Jaroslav Sklenar
//======================  Theoretical Distributions =======================

function uniform(min,max) {
   var r = Math.random();
   return min + r * (max - min)
};

//.........................................................................

function exponential(mean) {
   var r = Math.random();
   return - mean * Math.log(r);
};

//.........................................................................

function normal(mean, std) {
    var x = 0;
    for (var i = 1; i <= 12; i++) {
       x += Math.random();
    };
    return (x - 6)*std + mean;
};

//======================  User Defined Distribution =======================

function distribution(name) {            // Constructor
   this.dname = name;                    // Properties
   this.xv = new Array();                // Values
   this.px = new Array();                // Probabilities
   this.fx = new Array();                // Cumulative Distribution
   this.size = -1;
   this.status = "Table Empty";
   this.confirmed = false;
   this.disttype = "User";               // Types: User,Exp,Uni,Norm
   this.enterMode = "Prob";              // Default modes
   this.generMode = "Cont"               // Others: Cumul, Discr
   this.prompt1 = "Not used";            // Others: Mean,Min
   this.par1 = 0;
   this.prompt2 = "Not used";            // Others: Std,Max
   this.par2 = 0;
};

//............................... methods .................................

function update(index) { with (this) {
 switch (index) {
   case 0 :
      disttype = "User";
      prompt1 = "Not used";
      par1 = 0;
      prompt2 = "Not used";
      par2 = 0;
      break;
   case 1 :
      disttype = "Exp";
      prompt1 = "Mean";
      par1 = 1;
      prompt2 = "Not used";
      par2 = 0;
      break;
   case 2 :
      disttype = "Uni";
      prompt1 = "Min";
      par1 = 0;
      prompt2 = "Max";
      par2 = 1;
      break;
   case 3 :
      disttype = "Norm";
      prompt1 = "Mean";
      par1 = 3;
      prompt2 = "Std Dev";
      par2 = 1;
      break;
   default :
      alert("Index out of range - update.");
 }
 return true;
}};

distribution.prototype.update = update;

//.........................................................................

function testpar1(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 < 0 || x >= par2) {
        alert("The minimum value must be non-negative and smaller than the maximum.");
        return false;
      };
      break;
   case "Norm" :
      if (x <= 0) {
        alert("The mean value must be positive.");
        return false;
      };
      break;
   default :
      alert("Index out of range - update.");
  };
  par1 = x;
  return true;
}};

distribution.prototype.testpar1 = testpar1;

//.........................................................................

function testpar2(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 < 0 || x <= par1) {
        alert("The maximum value must be greater than the minimum.");
        return false;
      };
      break;
   case "Norm" :
      if (x <= 0) {
        alert("The Standard Deviation must be positive.");
        return false;
      };
      break;
   default :
      alert("Index out of range - update.");
  };
  par2 = x;
  return true;
}};

distribution.prototype.testpar2 = testpar2;

//.........................................................................

function fixtable() { 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.fixtable = fixtable;

//.........................................................................

function last() { with (this) {
  if (size < 0) {
    return 0
  } else {
    return xv[size];
  }
}};

distribution.prototype.last = last;

//.........................................................................

function compareWithLastValue(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.compareWithLastValue = compareWithLastValue;

//.........................................................................

function showTable() { with (this) {
   var y = " #  x         p(x)          F(x)" + "\n"
         + " ===================================" + "\n";
   var s = "";
   for (var i = 0; i <= size; i++) {
     var 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.showTable = showTable;

//.........................................................................

function accept(tx,tp) { with (this) {
 if (enterMode == "Prob") {                // Entering probabilities
   confirmed = false;
   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;
     size++;
     xv[size] = parseFloat(tx);
     fx[size] = fxx;
     if (size==0) {
       px[size] = fx[size];
     } else {
       px[size] = fx[size] - fx[size-1];
     }
   }
 };
 return true;
}};

distribution.prototype.accept = accept;

//.........................................................................

function edit() { 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;
  xv[n] = x;
  if (enterMode=="Prob") {                         // Entering probabilities
    px[n] = p;
  } else {
    fx[n] = p;
  };
  fixtable();
  return true;
}};

distribution.prototype.edit = edit;

//.........................................................................

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;
  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();
  return true;
}};

distribution.prototype.insert = insert;

//.........................................................................

function deleteit() { 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;
  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()
  };
  return true;
}};

distribution.prototype.deleteit = deleteit;

//.........................................................................

function cleartable() { with (this) {
   size = -1;
   xv.length = 0;
   px.length = 0;
   fx.length  = 0;
   confirmed = false;
   return true;
}};

distribution.prototype.cleartable = cleartable;

//.........................................................................

function confirmtable() { 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) {
       if (! confirm("Sum of probabilities is greater than 1. That's why some F(x) are truncated to 1. Is this OK ?")) {
         return false;
       }
     }
   } 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;
   return true;
}};

distribution.prototype.confirmtable = confirmtable;

//.........................................................................

function generate() { with (this) {      // Generates random number
 switch (disttype) {
  case "User" :
   var x1 = 0, f1 = 0, i = 0;            // xv = array with values
   var x2 = xv[0], f2 = fx[0];           // fx = cumulative distribution
   var r = Math.random();
   while (r > f2) {                      // search for c.d.f. not smaller than r
        x1 = x2; f1 = f2;
        x2 = xv[++i], f2 = fx[i];
   };
   if (generMode=="Discr") {
     return x2;                          // return the value for a discrete variable
   } else {
     return x1 + (x2-x1)*(r-f1)/(f2-f1); // Interpolation for a continuous variable
   }
  case "Exp" :
   return exponential(par1);
  case "Uni" :
   return uniform(par1,par2);
  case "Norm" :                          // Must be non-negative
   var x = normal(par1,par2);
   while (x<0) {
      x = normal(par1,par2);
   };
   return x;
  default :
      alert("Index out of range - update.");
      return false;
 };
}};

distribution.prototype.generate = generate;

//=========================================================================