// a variety of math, matrix manipulation, array statistics, and other such functions

//=================
// seedable random number generator 
// somewhat deprecated: see makeURandom at bottom of file
var uRandom = 
{
 seed : 123345,
 //Returns a random number between 0 and 1
 next : function(lower,upper)
 {
  var maxi = Math.pow(2,32);
  this.seed = (134775813 * (this.seed + 1))
     % maxi;
  var num = (this.seed) / maxi;
  if(typeof lower!='undefined')
  {
   var range = upper - lower;
   num *= range;
   num += lower;
  }
  return num;
 }
}

var mathDoSorteds={} ;          // for sorting in parseMathDo
var byInfo={};                   // for BY processing in parseMathDoStats

//========================================
//      ====== Probability functions ===
//========================================

// normal, inverse normal     
// from http://www.ciphersbyritter.com/JAVASCRP/NORMCHIK.HTM#Normal  

// NORMAL
function NormalP( x ) {
   // Abramowitz & Stegun 26.2.19
   var
      d1 = 0.0498673470,
      d2 = 0.0211410061,
      d3 = 0.0032776263,
      d4 = 0.0000380036,
      d5 = 0.0000488906,
      d6 = 0.0000053830;

   var a = Math.abs(x);
   var t = 1.0 + a*(d1+a*(d2+a*(d3+a*(d4+a*(d5+a*d6)))));

   // to 16th power
   t *= t;  t *= t;  t *= t;  t *= t;
   t = 1.0 / (t+t);  // the MINUS 16th

   if (x >= 0)  t = 1 - t;
   return t;
   }

function InvNormalP( p ) {
   // Odeh & Evans. 1974. AS 70. Applied Statistics. 23: 96-97
   var
      p0 = -0.322232431088,
      p1 = -1.0,
      p2 = -0.342242088547,
      p3 = -0.0204231210245,
      p4 = -0.453642210148E-4,
      q0 =  0.0993484626060,
      q1 =  0.588581570495,
      q2 =  0.531103462366,
      q3 =  0.103537752850,
      q4 =  0.38560700634E-2,
      pp, y, xp;

   // p: 0.0 .. 1.0 -> pp: 0.0 .. 0.5 .. 0.0
   if (p < 0.5)  pp = p;  else  pp = 1 - p;

   if (pp < 1E-12)
      xp = 99;
   else {
      y = Math.sqrt(Math.log(1/(pp*pp)));
      xp = y + ((((y * p4 + p3) * y + p2) * y + p1) * y + p0) /
               ((((y * q4 + q3) * y + q2) * y + q1) * y + q0);
      }

   if (p < 0.5)  return -xp;
   else  return  xp;
   }


//========================================
//      ====== Sortingfunctions ===
//========================================

// ===============================
// numeric sort -- returns new array numerically sorted
function sortNum(anArray) {
var newArray=[];
for (var m=0;m<anArray.length;m++) newArray[m]=anArray[m];
newArray.sort(sortNum2)
return newArray ;

function sortNum2(a,b) {
  return a- b ;
}
}


//=============================
// indexSort(array,type)
// Sort an array, return index.
// type: 'A' = alphabetic
//       'N' = numeric 	 	(the default)
//	'ND' = descending numeric

function indexSort(aa,type) {
  if (arguments.length<2) type='N' ;		// default sort is numeric
  type=type.toUpperCase();
  var m,n;
  var ind=[],goo=[];
  for (var m=0;m<aa.length;m++) ind[m]=m ;
  for (var n=0;n<aa.length;n++) {
     goo[n]=[];
     goo[n][0]=aa[n];
     goo[n][1]=ind[n];
  }
  if (type.indexOf('N')>-1) {		// numeric sort
     if (type.indexOf('D')>-1) {
	goo.sort(sortnumD) ;		// descending numeric
      } else {
	goo.sort(sortnumA) ;	 // asending numeric
      }
   } else {				// alphabetical sort
	goo.sort();
   }
   for (var m=0;m<aa.length;m++) ind[m]=goo[m][1];
   return ind ;

  function sortnumA(a,b) { return a[0]-b[0]} ;
  function sortnumD(a,b) { return b[0]-a[0]} ;

}

// ====================
// given an index in theIndex, rearrange the "theVals" array and return in a new array (which will be sorted)
// theInd could be an  arrays returned by indexSort; or a row from a matrix returend by indexSort2
// ifrom and ito are optional:; they can be start and end row in theInds (inclusive)
// If not specified, all rows are used.
// If theInds is a 2d matrix (say, as returned by sortIndex2), the rows will be concatenated.
//   If ifrom is specified but not ito, then read from ifrom to the end
//   To read just one row, ifrom and ito should have the same value
//    Note: ifrom and ito are constrained to lie between 0 and theInds.length-1

function sortByIndex(theVals,theInds,ifrom,ito) {

 if (arguments.length<3 || ifrom==='' )ifrom=0;
 if (arguments.length<4 || ito==='' ) ito=theInds.length-1;

  if (isNaN(ifrom) || isNaN(ito)) { 
        alert('Error in sortByIndex: non numeric bounds =  '+ifrom+','+ito);
        return null ;
  }
  ifrom=Math.max(ifrom,0);
  ito=Math.min(ito,theInds.length-1);
  var cc=[];
  for (var m=ifrom;m<=ito;m++) {
    cc=cc.concat(theVals[theInds[m]]);                  // note that concat will concatenate arrays OR values
  }
  return cc;
}


//=======================================
// Returns a 2d index matrix whose elements point to rows of vec1 and vec2
//  Each row points to elements with the same vec2 value; within each row the columns point to vec1 values that are sorted
//  And, the rows are sorted by vec2
//  Notes:
//    *  the value of the vec2 element, for a given row, is always the same.
//    *  the size of each row vector can vary.
// Example:
//              1  2  3  4  5  6  7  8
//       vec1=[11 22 131 51 23  9 35  5 ]
//       vec2=[ 1  2   1  1  2  4  1  4 ]
// ind2=indexSort2[vec1,vec1,'N') yields:
//       ind2[0]=[ 1 7 4 3]   (vec2[]=1)
//       ind2[1]=[ 2 5]       (vec2[]=2)
//       ind2[2]=[ 8 6 ]      (vec2[]=4)
//
// Note that atype is used to sort both vectors. N=numeric, ND=descending numeric, A=alphabetic. Not specified: numeric

function indexSort2(vec1,vec2,atype) {

var lookups,ilooks,ii2,ii,igroup,tval,gval,m,newi;
if (arguments.length<3) atype='N';
var ii=indexSort(vec1,atype);                                // sort index of 1st vector (values?)

newi=[];                                               // results
lookups={};                                             // lookup table for 2nd vector unique values

ilooks=0;
var ii2=indexSort(vec2,atype);                 // create sort index for 2nd vector (groups?)
for (var m=0;m<vec2.length;m++) {          // find unique elements in 2nd vector, record order of occurrence
  igroup=vec2[ii2[m]];
  if (typeof(lookups[igroup])=='undefined') {
      lookups[igroup]=ilooks;
      ilooks++ ;
  }
}

for (var m=0;m <vec1.length;m++) {
  gval=vec2[ii[m]] ;
  irow=lookups[gval];
  if (typeof(newi[irow])=='undefined') newi[irow]=[];
  newi[irow]=newi[irow].concat(ii[m]);
}
return newi ;
}


// =================================================
//      ========== Statistics on arrays functions ====
// =================================================

/* =======
  maxArray(array,amiss)               : returns maximum of an array, ignoring undefineds and nulls
  minArray(array,amiss)               : returns minimum of an array, ignoring undefineds and nulls
  meanArray(array,amiss)              : returns average of an array, ignoring undefineds and nulls, ignoring non-numerics
  sumArray(vv,amiss)               : returns sum of all elements in array or matrix
  missArray(array)               : number of non-numeric values in arrray
  medianArray(array,amiss)            : returns median value of an array, ignoring undefineds and nulls, ignoring non-numerics
  varArray(array,amiss)                : returns variance value of an array, ignoring undefineds and nulls, ignoring non-numerics

Alternate syntax for the above (except for missArray):
        funcname(array,missval)
By default, missing values (undefined or non-numeric) are ignored.  If you specify missval, then its value is used for all
"missing values". 

More functions ...

  uniqueArray(varray,isort)     : vector of unique values founds in this array  
  freqArray(vv,vclass)          : compute frequencies from vv, using values in vclass 


 ======= */

function maxArray(vv,amiss) {
  var mm,mx,aval,tval,igot=0 ;
  var domiss=1;
  if (arguments.length<2) domiss=0;
  for (var mm=0;mm<vv.length;mm++) {
     aval=vv[mm]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     if (igot==0) {
        igot=1;
        mx=aval;
     } else {
        if (aval>mx) mx=aval
     }
  }
  return mx ;
}

function maxArray_row(vv,amiss) {
  var mm,mx,aval,tval,igot=-1 ;
  var domiss=1;
  if (arguments.length<2) domiss=0;
  for (var mm=0;mm<vv.length;mm++) {
     aval=vv[mm]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     if (igot==-1) {
        igot=0;
        mx=aval;
     } else {
        if (aval>mx) {
          mx=aval
          igot=mm;
        }
     }
  }
  return igot ;
}


// ============
function minArray(vv,amiss) {
  var mm,mx,aval,tval,igot=0 ;
  var domiss=1;
  if (arguments.length<2) domiss=0;
 
  for (var mm=0;mm<vv.length;mm++) {
     aval=vv[mm]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     if (igot==0) {
        igot=1;
        mx=aval;
     } else {
        if (aval<mx) mx=aval
     }
  }
  return mx ;
}

// ============
function minArray_row(vv,amiss) {
  var mm,mx,aval,tval,igot=-1 ;
  var domiss=1;
  if (arguments.length<2) domiss=0;
 
  for (var mm=0;mm<vv.length;mm++) {
     aval=vv[mm]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     if (igot==-1) {
        igot=0;
        mx=aval;
     } else {
        if (aval<mx) {
            mx=aval
            igot=mm
        }
     }
  }
  return igot ;
}



// ============
function sumArray(vv,amiss) {
  var mm,aval,tval,igot=0,aval0,tval0,mm2,asum ;
  var mx=[];
  var  ismtx=0;
  var domiss=1;
  if (arguments.length<2) domiss=0;
  for (var mm=0;mm<vv.length;mm++) {
     aval0=vv[mm]; tval0=typeof(aval0);
     if (tval0!='object' ) aval0=[aval0];                                   // a hack: make into an array
     if (aval0.length>1) ismtx=1;
     for (var mm2=0;mm2<aval0.length;mm2++) {
           aval=aval0[mm2]; tval=typeof(aval);
           if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
                if (domiss==0)    continue ;
                aval=amiss;
           }
          igot++;
          if (typeof(mx[mm2])=='undefined') mx[mm2]=0;
          mx[mm2]=(1*aval)+mx[mm2];
      }                                         // mm2
  }                                             //mm
  if (igot==0)  return asum ;                    // all missing, return null  
  if (ismtx==0) return mx[0] ;                   // vv is an array, return a scalar
  return mx ;                                   // vv is a matrix, return an array
}


// ============
function meanArray(vv,amiss) {
  var mm,aval,tval,igot=0,aval0,tval0,mm2,asum ;
  var mx=[];
  var igot=[];
  var  ismtx=0;
  var domiss=1;
  if (arguments.length<2) domiss=0;
  for (var mm=0;mm<vv.length;mm++) {
     aval0=vv[mm]; tval0=typeof(aval0);
     if (tval0!='object' ) aval0=[aval0];                                   // a hack: make into an array
     if (aval0.length>1) ismtx=1;

     for (var mm2=0;mm2<aval0.length;mm2++) {
           aval=aval0[mm2]; tval=typeof(aval);
           if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
                if (domiss==0)    continue ;
                aval=amiss;
           }
           if (typeof(mx[mm2])=='undefined') {
                mx[mm2]=0; igot[mm2]=0;
           }
          igot[mm2]++;
          mx[mm2]=(1*aval)+mx[mm2];
      }                                         // mm2
  }                                             //mm
  if (igot==0)  return asum ;                    // all missing, return null  
  if (ismtx==0) return (mx[0]/igot) ;                   // vv is an array, return a scalar

  for (var mm=0;mm<mx.length;mm++) mx[mm]=mx[mm]/igot[mm];
  return mx ;                                   // vv is a matrix, return an array
}


// ============
function missArray(vv) {
  var mm,mx,aval,amean,tval,igot=0 ;
  for (var mm=0;mm<vv.length;mm++) {
     aval=vv[mm]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') igot++ ;
  }
  return igot ;
}

// ============
function medianArray(anArray,amiss) {

var m,aval,tval,ind1,m2 ;
  var domiss=1;
  if (arguments.length<2) domiss=0;

var newArray=[];
for (var m=0;m<anArray.length;m++) {
     aval=anArray[m]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     newArray=newArray.concat(aval);
}    
ind1=sortNum(newArray);

m2=Math.ceil((ind1.length)/2)-1 ;

return(ind1[m2]);
}


// ============
function medianArray_row(anArray,amiss) {

var m,aval,tval,ind1,m2 ;
  var domiss=1;
  if (arguments.length<2) domiss=0;

var newArray=[];
for (var m=0;m<anArray.length;m++) {
     aval=anArray[m]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     newArray=newArray.concat(aval);
}    

ind1=indexSort(newArray);

m2=Math.ceil((ind1.length)/2)-1 ;

return(ind1[m2]);
}


// ============
function  varArray(vv,amiss)    {
  var tt,vv,mx,vsum=0,bval,igot=0,aval,tval,mm;
  var domiss=1;
  if (arguments.length<2) domiss=0;

  mx=meanArray(vv) ;
  for (var mm=0;mm<vv.length;mm++) {
     aval=vv[mm]; tval=typeof(aval);
     if (tval=='undefined' || tval=='null' || isNaN(aval) || aval==='') {
        if (domiss==0)    continue ;
        aval=amiss
     }
     aval=1*aval;
     igot++;
     bval=(aval-mx); 
     vsum=vsum+(bval*bval);
  }
  bval=vsum/igot ;
  return (Math.sqrt(bval)) ;

}


//===========
// vector of unique values of this array. If isort=1, sorted
function uniqueArray(vv,isort) {
  if (arguments.length<2) isort=0;
  var i,ii,ilist=[] ;
  if (typeof(vv)!=='object' || typeof(vv.length)=='undefined') {
        alert('Error in uniqueArray: not an array ');
         return vv ;
  }
  var vhold={} ;
  for (var i=0;i<vv.length;i++) {
     ii=vv[i];
     vhold[ii]=ii;
   }
  for (i in vhold) ilist=ilist.concat(vhold[i]);
  if (isort==1) ilist=sortNum(ilist);
  return ilist;
}


//===========
// compute frequencies from vv, using values in vclass
// indexM controls what is kind of array is returned returned (call this array vreturn)
// 0 (default) : vreturn[i] contains number of elements equal to vclass[i]
//                      Thus, vreturn.length vclass.length
// 1           : vreturn[vclass[i]] contains of elements equal to vclass[i]
//                     Thus vreturn.length =max(vclass)

function freqArray(vv,vclass,indexM) {

var stuff={};
if (arguments.length<2) vclass=uniqueArray(vv);
if (arguments.length<3) indexM=0;

var i1,i,vnew;
for (var i=0;i<vclass.length;i++) {
   i1=strip(vclass[i]);
   stuff[i1]=0;
}
for (var i=0;i<vv.length;i++) {
   i1=strip(vv[i]);
   if (typeof(stuff[i1])!=='undefined') stuff[i1]++ ;   // if not defined in vclass, skip
}

if (indexM==1) {                                // indices of vnew are the values of vclass
 vnew=[];
 for (var i in stuff) {
        vnew[i]=stuff[i];
 }
 for (var i=0;i<vnew.length;i++) {
        if (typeof(vnew[i])=='undefined') vnew[i]=0
 }
 return vnew ;

}

vnew=[];
for (var i=0;i<vclass.length;i++) {                     // vnew.length = vclass.length
   i1=strip(vclass[i]);
   vnew[vnew.length]=stuff[i1];
}
return vnew ;
}

//===========
// generate an n element array of random numbers. If aseed specified, use a random number generate with aseed as a seed
// min: min value of random number
// max : maximum value
// fmt: resolution. 0=integer, 1=x.1, etc.
// otherwise, true random number

function rndu(amin,amax,afmt) {
var vv,vv2 ;  

  if (arguments.length<1) amin=0;
  if (arguments.length<2) amax=parseFloat(amin)+1;
  if (arguments.length<3) afmt=8;

  vv=Math.random() ;
  vv2=parseFloat(amin)+ ((amax-amin)*vv) ;
  return vv2.toFixed(afmt) ;

}

//===========
// generate an n element array of random numbers. If aseed specified, use a random number generate with aseed as a seed
// otherwise, true random number
function rnduArray(n,aseed) {
  var vv=[];
  var i ;
  if (arguments.length>1) uRandom.seed=aseed  ;

  for (var i=0;i<n;i++) {
     if (arguments.length<2) {
        vv[i]=Math.random() ;
     } else {
        vv[i]=uRandom.next(0,1);
     }
  }

  return vv ;        

}


//===========
// generate an n element array of random numbers. If aseed specified, use a random number generate with aseed as a seed
// otherwise, true random number
function rndnArray(n,aseed) {
  var vv=[];
  var i,y ;
  if (arguments.length>1) uRandom.seed=aseed  ;

  for (var i=0;i<n;i++) {
     if (arguments.length<2) {
        y=Math.random() ;
     } else {
        y=uRandom.next(0,1);
     }
     vv[i]=InvNormalP(y);
  }

  return vv ;        

}

// ==============
// choose n values randomly from array v.
// if repOk=1, then with replacement. Else, without replacement (in which case n<v.length)
function choseRandomArray(n,v,repOk) {
var  newv=[] ;
var i,aval,v2,iv2;

if (arguments.length<3) repOk=0;

if (repOk==1) {
  for (var i=0;i<n;i++) {
      ith=Math.floor(Math.random()*v.length);
      newv[i]=v[ith];        
  }
  return newv ;
}

// without replacement
  v2=rnduArray(v.length);
  iv2=indexSort(v2);
  for (var i=0;i<Math.min(n,v.length);i++) { 
       newv[i]=v[iv2[i]] ;
  }
  return newv ;

}


// ======================================= 
/// ======== Functions to intialize arrays     ==========
// ======================================= 


// ==============
// initialize an array to a length and  value
function initArray(nlen,defval) {
 var i;
 var v=[];
 for (var i=0;i<nlen;i++) {
   v[i]=defval;
}
return v;
}  


//===========
// create an array containing a sequence of numbers: from i1 to i2 by istep (i1 and i2 are inclusive)
// so seqArray(0,5,1) yields [0,1,2,3,4,5])
// if not specified, istep=1
function seqArray(i1,i2,istep) {
var v,i;
 if (arguments.length<3) istep=1 
 v=[];
 for (var i=i1;i<=i2;i=i+istep) v[v.length]=i;   
return v;
}


// ======================================= 
/// ======== Functions to select (using logic rules) rows of a matrix  work     ==========
// ======================================= 


//=============
//  return a subset of a matrix, using a selection equation
//  arguments:
//  vv  = NxK array of values (a subset of these are returned
//  acond= A logical condition that determines which rows to select. You can refer to the value in a column  by using  $n
//  vvcomp = NxJ matrix used by acond. IF not specified, or if a '', vv is used. 
//  iidx  = If a 1, return a 0/1 index indicating which rows pass the acond test. If 0, return matrix of rows that pass the test
//        If 2, return an array pointing to which rows pass the test (first row has index value of 0)
// Notes on acond
//  *   the standard dSurvey logical equation synatx, and specify general and control variables
//  *   the n in a $n n is the column of vvcomp to examine (n can range from 0 to 9). Note that each row is examined seperately, using the nth
//      column in the row when needed.
// 
//  Example. vv is a 4 x 2 matrix=  10 1 , 12 1, 313 2, 1.5 1    (, signals new row)
// Then  newvv=selectRows(vv,'$2 eq 1 ')
// will return   10 1, 12 1,  1.5 1
// Note: if n (in  bcond=acond $n) is > # of columns in a row, unpredictable results will occur (the $n will be NOT be replaced by a value)
// OPTION: if vvcomp is specified, then $n refers to the n'th colum of vvcomp (not of vv)
// shortcut: $ by itself is a synonym for $0. This is expecially useful if vvcomp is a vector

function   selectRows(vv,acond,vvcomp,iidx) {

 if (arguments.length<3 || vvcomp==='') vvcomp=vv;
 if (arguments.length<4) iidx=0;

 var bcond,qq,i,n,rr,arow,vrow,iuse,useit;
var vnew=[];
var toLook=[];toUse=[];

_originalEquation='';

  for (var i=0;i<10;i++) {                               // find all $n (n= 0 to 9) arguments, and create a regexp to match them
    if (acond.indexOf('$'+i)>-1) {
         rr=new RegExp('\\$'+i,'g');
         toLook=toLook.concat(rr);
         toUse=toUse.concat(i);
    }
  }
  if (acond.indexOf('$')>-1) {                         // LOOK FOR  $
         rr=new RegExp('\\$\\s','g');
         toLook=toLook.concat(rr);
         toUse=toUse.concat(0);
   }

 for (var n=0;n<vv.length;n++) { 
    arow=vv[n];
    if (n>=vvcomp.length) break ;                 //vvcomp too short, so drop trailing rows of vv
    vrow=vvcomp[n]; if (typeof(vrow)!='object') vrow=[vrow] ;
    bcond=acond ;
    qq=1;
    for (var i=0;i<toLook.length;i++) {
        iuse=toUse[i];
        if (iuse>=vrow.length) {                   // no such column, so this skip this row 
                qq=0 ;
                break ;
        }
        useit=vrow[iuse];
        if (useit==='') useit="''";
        bcond=bcond.replace(toLook[i],useit+' '); 
    }

    if (qq==1) {
       qq=E2_doMath(bcond);
    }
    if (iidx==1) {                      // create a 0/1 index
        vnew[vnew.length]=qq ;
    } else if (iidx==2) {               // create index that points to rows (first row is row 0)
        if (qq) vnew[vnew.length]=n ;
    } else {                            // return selected rows of matrix 
        if (qq) vnew[vnew.length]=arow;
    }
}                       // n (rows in vv)
return vnew ;
}

// ======================================= 
/// ======== Functions that work with submittedData and workData    ==========
// ======================================= 

// for dataset whichData, extract all values for round iround for variables avars; and return a matrix of the results
// whichData can be 'w' or 's' (work for workData, submitted for submittedData'
// avars can be a variable, or a space delimited list of variable names.
//
// Special value of avar: $ID returns the client ids
// note that length of the array will depend on the round (how many ids in this round) --
// All clients in clist, or all clients if no clist, are checked. 
// If a client did NOT have any variables saved in a round, and clist is specified, defval is used.
//    if clist not specified,  it is skipped (nothing is added to the returned matrix)
// If a client exists in a round, and a variable is missing, then
//  a) if defval is NOT defined, the client is skipped
//  b) if defval is specified, the defval is used for all "missing" variables
// You can use * as the round, to get default values for this variable (for all IDS that have such a variable defined as their default)
// Note that for submittedData, the "final" values are returned (intermediate records are ignored)
// Note that if clist is specified, then defval is set to undefined it for some reason it was not specified

function extractRoundData(whichData,iround,avars,defval,clist) {    
var vv,aid,icc,useClist=0,doList ;
var nodef=0,okay,avar,vtmp,iv;

if (arguments.length<4) nodef=1 

if (arguments.length>4) {
     useClist=1 ;
     if (nodef==1) {
        defval=undefined;
        nodef=0;
     }
}

whichData=strip(whichData).toUpperCase() ;
whichData=whichData.substr(0,1);

if (arguments.length>1) iround=strip(iround) ;
if (arguments.length>2) avars=getWord(avars.toUpperCase()) ;  

vv=[];

if (whichData==='W') {                          // extract from workData 
   if (typeof(workData)=='undefined') return vv ;               // no workData   
// alert('ssss '+workData['rounds']);
   if (arguments.length<2) return workData['rounds'];           // return list of all rounds that have some data 

   if (typeof(workData[iround])=='undefined') return vv ;               // no such round

   if (useClist==1) {
       doList=clist ;
   } else {
       doList=workData['ids']
   }

   if (arguments.length<3) {                                 // return list of IDS that have data for this round?
       for (var icc=0;icc<doList.length;icc++) {                   // for all ids
          aid=doList[icc];
          if (typeof(workData[iround][aid])!='undefined'){
             vv=vv.concat(aid);                             // add id to array, and nothing else
          }
       }
       return vv ;
    }

   for (var icc=0;icc<doList.length;icc++) {                   // for all ids
      aid=doList[icc]; 
      if (typeof(workData[iround][aid])=='undefined'){          // id not in this round
           if (useClist==0)    continue   ;                // no such id for this round, so skip
           vtmp=[];                                     // clist id, use defval for all vars for this id
           for (var iv=0;iv<avars.length;iv++) {
              vtmp=vtmp.concat(defval);
           }
           vv[vv.length]=vtmp;                   // add row to matrix
           continue;
       }

// id exists
       vtmp=[]; okay=1;
       for (var iv=0;iv<avars.length;iv++) {
              avar=strip(avars[iv]);   
              if (avar=='$ID') {                          // special case: include the participant id
                  vtmp=vtmp.concat(aid);                              
              } else if (avar=='$ROUND') {                          // sort of silly, but wtf
                  vtmp=vtmp.concat(iround);                              
              } else if (avar.substr(0,1)=='=') {                          // sort of silly, but wtf
                  vtmp=vtmp.concat(avar.substr(1)); 
              } else {                                    // a variable name.
                 if (avar=='$TIME') avar='$time';
                 if (typeof(workData[iround][aid][avar])=='undefined') {     // this variable does not exist?
                   if (nodef==1) { okay=0 ;break  ;   }                //  and no default: skip this id
                   vtmp=vtmp.concat(defval);                               // otherwise, use the defval
                } else {                                            // varible does exist, so use its value
                  vtmp=vtmp.concat(workData[iround][aid][avar]);
                }                                       // aid avar
              }                                       /// avar==$ID
       }                                    // for iv
       if (okay==1)     vv[vv.length]=vtmp;                   // add row to matrix

    }                           // icc
    return vv;
}                                       // W 

if (whichData==='S') {                          // extract from workData 
   if (typeof(submittedData)=='undefined') return vv ;                       

   if (arguments.length<2) return submittedData['$roundList'];           // return list of all rounds that have some data 

   if (typeof(submittedData['$rounds'])=='undefined') return vv ;              

   if (typeof(submittedData['$rounds'][iround])=='undefined') return vv ;               // no such round

   if (arguments.length<3) {                           // return list of IDS that have data for this round?
      return submittedData['$rounds'][iround] ;
   }


   if (useClist==1) {
       doList=clist ;
   } else {
       doList=submittedData['$rounds'][iround] ;
   }

   for (var icc=0;icc<doList.length;icc++) {                   // for all ids with some data in this round
        aid=doList[icc];
        vtmp=[]; okay=1 ;

        if (typeof(submittedData[iround][aid])=='undefined'){          // id not in this round
           if (useClist==0)    continue   ;                // no such id for this round, so skip
           for (var iv=0;iv<avars.length;iv++) {
              vtmp=vtmp.concat(defval);
           }
           vv[vv.length]=vtmp;                   // add row to matrix
           continue ;
       }

// this id exists         

       for (var iv=0;iv<avars.length;iv++) {
             avar=strip(avars[iv]);   
             if (avar=='$ID') {                          // special case: include the participant id
                vtmp=vtmp.concat(aid);                               
             } else if (avar=='$ROUND') {                          // special case: include the round
                vtmp=vtmp.concat(iround);                             
             } else if (avar.substr(0,1)=='=') {                          // sort of silly, but wtf
                  vtmp=vtmp.concat(avar.substr(1)); 
             } else {                                    // a variable name.
                if (avar=='$TIME') avar='$time';
                if (typeof(submittedData[iround][aid][avar])=='undefined') {     // this variable does not exist?
                   if (nodef==1) {okay=0; break} ;                                //  and no default: skip this id
                   vtmp=vtmp.concat(defval);                               // otherwise, use the defval
                } else {                                            // varible does exist, so use its value
                  vtmp=vtmp.concat(submittedData[iround][aid][avar]);
                }                                       // aid avar
             }                                       /// avar==$ID
       }                                       // for iv
       if (okay==1)  vv[vv.length]=vtmp;                   // add row to matrix


   }                                  // icc
}                               // S

return vv ;                     // not S or W 
}


//====================
// extract the list of rounds, return as an array.
// first argument is S or W (submittedData or workData)
//  If no 2nd argument, or =*:  all rounds for (using all participants)
//  If a second argument (ne *), all rounds for this participant id 
//  If a third argument is 1, and 2nd arg='*' (or no 2nd arg), then do NOT return a '*' round in the
//   list of rounds.

function extractRoundList(whichData,iid,noast) {    
 var foo,foonew,i;
 if (arguments.length<2 || iid=='*') {
    foo=extractRoundData(whichData) ;
  } else {
     foo=extractClientData(whichData,iid); 
  }

  if (arguments.length<2  || noast!==1)   return foo ;          // do NOT remove *

  foonew=[];
  for (var i=0;i<foo.length;i++) {
         if (foo[i]!=='*') foonew[foonew.length]=foo[i];
  }    
  return foonew ;

}

//====================
// extract the list of ids. If no 2nd argument, all ids. If a second argument, all ids for this participant round
// first argument is S or W (submittedData or workData)
function extractClientList(whichData,irnd,noast) {    
 var foo,foonew,i;

 if (arguments.length<2 || irnd=='*' ) {
    foo=extractClientData(whichData) ;
  } else {
    foo=extractRoundData(whichData,irnd); 
  }
  
  if (arguments.length<2  || noast!==1)   return foo ;          // do NOT remove *

  foonew=[];
  for (var i=0;i<foo.length;i++) {
         if (foo[i]!=='*') foonew[foonew.length]=foo[i];
  }    
  return foonew ;

}
  


// =======================================
// for dataset whichData, extract all values for iclient participant id  for variables avars; and return 
// a matrix of the results (each row is an array containing a round's results: so each element of a row-array
// contains the corresponding variable value for this round). Note that vv=extractClientData(..), then vv[0]
// contains first round
//
// whichData: 'w' or 's' (work for workData, s for submittedData)
// iclient:  participant id 
//  avars: variables to extract, in a space delimited string
//        these variable names can be x[*] == which is converted to x[0], x[1], ... x[x.length-1]
//        Special value of avar: $ROUND returns the round number
//  defval : default value to use if missing info (see below for details)
//  rlist: Optional. If specified, return values for all rounds listed in rlist. 
//          If rlist specified, defval is set to undefined if was not specified
//
// If the iclient client did not participate in a round listed in rlist, return the defval value for 
// all the avars variables (in this round). Note that if you specify a rlist you MUST also specify a defval. 
//      note that length of the array will depend on the partcipant (how many rounds thid participant was part of) --
// All rounds are checked. If a client did NOT have any variables saved in a round, it is skipped 
// (nothing is added to the returned matrix)
// If a client exists in a round, and a variable is missing, then
//    a) if defval is NOT defined, the round is skipped
//    b) if defval is specified, the defval is used for all "missing" variables
// You can use * as the participant id, to get default values for this variable 
// (for all rounds that have such a variable defined as their default)
// Note that for submittedData, the "final" values are returned (intermediate records are ignored)


function extractClientData(whichData,iclient,avars,defval,rlist) {    
var vv,icc,irnd,useRlist=0,doList ;
var nodef=0,okay,avar,vtmp,iv;

if (arguments.length<4) nodef=1 

if (arguments.length>4) {
     useRlist=1 ;
     if (nodef==1) {
        defval=undefined;
        nodef=0;
     }
}

whichData=strip(whichData).toUpperCase() ;
whichData=whichData.substr(0,1);

iclient=strip(iclient);
if (arguments.length>2) {                               // variables list (check for *)
  if (strip(avars)!='*') {
     if (typeof(avars)!='object') avars=getWord(avars.toUpperCase());
 } else {                                       // *
        if (whichData=='W') avars=workData['vars'];
        if (whichData=='S') avars=submittedData['$varList'];
 }
}
   

vv=[];

if (whichData==='W') {                          // extract from workData 
   if (typeof(workData)=='undefined') return vv ;               // no workData 
 
   if (arguments.length<2) return workData['ids'];           // return list of all rounds that have some data 

   if (useRlist==1) {
       doList=rlist ;
       if (typeof(doList)!='object') doList=[doList];
   } else {
       doList=workData['rounds']
   }

   if (arguments.length<3) {                                 // return list of  rounds this ID participated in?
      for (var icc=0;icc<doList.length;icc++) {                   // for all ids
         irnd=doList[icc];
         if (typeof(workData[irnd][iclient])=='undefined') continue   ;                // no such id for this round, so skip
         vv=vv.concat(irnd)
      }
      return vv ;
   }

   for (var icc=0;icc<doList.length;icc++) {                   // for all ids
      irnd=doList[icc]; 
      if (typeof(workData[irnd])=='undefined'){          // no such round
         alert('ERROR in extractClientData. No data for round '+irnd+' in workdata ');
         continue ;
      }
      if (typeof(workData[irnd][iclient])=='undefined'){          // id not in this round
           if (useRlist==0)    continue   ;                // no such id for this round, so skip
           vtmp=[];                                     // clist id, use defval for all vars for this id
           for (var iv=0;iv<avars.length;iv++) {
              vtmp=vtmp.concat(defval);
           }
           vv[vv.length]=vtmp;                   // add row to matrix
           continue ;
      }

// legit iclient
      vtmp=[]; okay=1;
      for (var iv=0;iv<avars.length;iv++) { 
            avar=strip(avars[iv]);   
            if (avar=='$ROUND') {                          // special case: include the round number
                  vtmp=vtmp.concat(irnd);   
            } else if (avar=='$ID') {                          // special case: include the id (sort of silly, but wtf)
                  vtmp=vtmp.concat(iclient) ; 
            } else if (avar.substr(0,1)=='=') {                          // sort of silly, but wtf
                  vtmp=vtmp.concat(avar.substr(1)); 
            } else {                                    // a variable name.
               if (avar=='$TIME') avar='$time';
               if (typeof(workData[irnd][iclient][avar])=='undefined') {     // this variable does not exist?
                  if (nodef==1) { okay=0 ;break  ;   }                //  and no default: skip this id
                  vtmp=vtmp.concat(defval);                               // otherwise, use the defval
               } else {                                            // varible does exist, so use its value
                  vtmp=vtmp.concat(workData[irnd][iclient][avar]);
               }                                       // this var exists for this client
            }                                       //  a var name (not $ID or $ROUND)
      }                                       // for iv
      if (okay==1)         vv[vv.length]=vtmp;                   // add row to matrix
   }                           // icc
   return vv;
}                                       // W 

if (whichData==='S') {                          // extract from workData 
   if (typeof(submittedData)=='undefined') return vv ;                       
   if (arguments.length<2) return submittedData['$roundList'];           // return list of all rounds that have some data 

   if (typeof(submittedData['$ids'])=='undefined') return vv ;              

   if (typeof(submittedData['$ids'][iclient])=='undefined') return vv ;               // no such client

   if (arguments.length<3) {                           // return list of rounds that this id participated in?
      return submittedData['$ids'][iclient] ;
   }

   if (useRlist==1) {  
       doList=rlist ;
       if (typeof(doList)!='object') doList=[doList];

   } else {
       doList=submittedData['$ids'][iclient] ;
   }

   for (var icc=0;icc<doList.length;icc++) {                   // for all ids with some data in this round
        irnd=doList[icc];
        vtmp=[]; okay=1 ;

       if (typeof(submittedData[irnd])=='undefined') continue          // no such round

      if (typeof(submittedData[irnd][iclient])=='undefined'){          // id not in this round
           if (useRlist==0)    continue   ;                // no such id for this round, so skip
           vtmp=[];                                     // clist id, use defval for all vars for this id
           for (var iv=0;iv<avars.length;iv++) {
              vtmp=vtmp.concat(defval);
           }
          vv[vv.length]=vtmp;                   // add row to matrix
           continue ;
      }

// round exists
        for (var iv=0;iv<avars.length;iv++) {
            avar=strip(avars[iv]);   
            if (avar=='$ID') {                          // special case: include the participant id
               vtmp=vtmp.concat(iclient);                               
            }else if (avar=='$ROUND') {                          // special case: include the round
               vtmp=vtmp.concat(irnd);                               
            } else if (avar.substr(0,1)=='=') {                          // sort of silly, but wtf
                  vtmp=vtmp.concat(avar.substr(1)); 
            } else {                                    // a variable name.
               if (avar=='$TIME') avar='$time';
               if (typeof(submittedData[irnd][iclient][avar])=='undefined') {     // this variable does not exist?
                  if (nodef==1) {okay=0; break} ;                                //  and no default: skip this id
                  vtmp=vtmp.concat(defval);                               // otherwise, use the defval
               } else {                                            // varible does exist, so use its value
                 vtmp=vtmp.concat(submittedData[irnd][iclient][avar]);
               }                                       // iclient avar
            }                                       /// avar==$ID
        }                                       // for iv
        if (okay==1)             vv[vv.length]=vtmp;                   // add row to matrix


   }                                  // icc
}                               // S

return vv ;                     // not S or W 
}


// ===============================================
//      ====== Functions that work with matrices ======
// ===============================================

//==========
// extract a colun from  matrix
function extractCol(vv,icol) {
var vnew,i;
vnew=[];
for (var i=0;i<vv.length;i++) {
    if (icol>=vv[i].length) {
            vnew=vnew.concat(undefined)
       } else {
            vnew=vnew.concat(vv[i][icol] )
       }
}
return vnew ;
}

//==========
// extract a row  matrix
// useflag=0 (or not specified): rlist contains an array containing the row numbers  to extract (0 is the first row)
// useflag=1 : rlist contains 0/1 "flags" -- 1 means 'keep this row', 0 means "discard this row"
// useflag=2 : rlist is a 2 element array: the first and last rows (inclusive) to keep. If rlist[0] or rlist[1] are "out of bounds", 0 and vv.length-1 are used 
// Note:  useflag is similar to the iidx argument in selectRows

function extractRows(vv,rlist,useFlag) {

if (arguments.length<3) useFlag=0;
if (typeof(rlist)=='number') rlist=[rlist]; 

var vnew,i,irow;
vnew=[];
if (useFlag==0 ) {
  for (var i=0;i<rlist.length;i++) {
     irow=rlist[i];
     if (irow<0 || irow>=vv.length) continue ;           // skip impossible rows
     vnew[vnew.length]=vv[irow];
  }
} else if (useFlag==2  || strip(useflag)==':' ) {
   var i1=Math.max(0,rlist[0]);
   var i2=Math.min(vv.length-1,rlist[1]);
   for (var i=i1;i<=i2;i++) {
      vnew[vnew.length]=vv[i];
    }
} else {
  for (var i=0;i<vv.length;i++) {
      if (i>=rlist.length) continue ;   // rlist is too short, so discard
      if (rlist[i]==0) continue ;
      vnew[vnew.length]=vv[i];
   }   
}

return vnew ;
}


//==========
// append matrix b to end of matrix a (may have different column sizes). THis is a vertical append: the number of rows increases
function appendRows(amtx,bmtx) {
var i;
for (var i=0;i<bmtx.length;i++) {
   amtx[amtx.length]=bmtx[i];
}
return amtx;
}


//==========
// append array anarray to end of matrix amtx. If only one variable, create a matrix using array specified in amtx (first) argument
//   This is a vertical append: the number of rows increases
function appendArray(amtx,anarray) {
var i;
if (arguments.length<2) {
        anarray=amtx;
        amtx=[];
}
var ii=amtx.length;
amtx[ii]=[];
for (var i=0;i<anarray.length;i++) {
   amtx[ii][i]=anarray[i];
}

return amtx;
}


//==========
// append matrix b to right of matrix a (may have different row sizes). THis is a horizontal append: the number of columns increases
function appendCols(amtx,bmtx) {
var i,alen,blen,row1,row2,vnew,clen,i;
var vnew=[] ;
alen=amtx.length ; blen=bmtx.length;
clen=Math.max(alen,blen);

for (var i=0;i<clen;i++) {
   row1=[]; row2=[];
   if (i<alen) row1=amtx[i];  
        if (typeof(row1)!='object') row1=[row1]; 
   if (i<blen) row2=bmtx[i];   
        if (typeof(row2)!='object') row2=[row2]; 
   row1=row1.concat(row2);
   vnew[vnew.length]=row1;
}
return vnew ;
}

//==========
// replace column icol in amtx with bvals
function replaceCol(amtx,icol,bvals) {
var useconst=0;
var i;

if (isNaN(icol)) {
  alert('replaceCol error: icol not a number='+icol);
   return amtx ;
}

if (typeof(bvals)!=='object' || typeof(bvals.length)=='undefined') {
        useconst=1;
} else {
  if (amtx.length != bvals.length) {
        alert('replaceCol error: amtx length='+amtx.length+', bvals.length='+bvals.length);
        return amtx ;
   }
}

for (var i=0;i<amtx.length;i++) {
   if (useconst==1) {
      amtx[i][icol]=bvals;
   } else {
      amtx[i][icol]=bvals[i];
   }
}
return amtx;
}



//==========
// replace rows in rlist with with row from bvals
function replaceRows(amtx,rlist,bvals) {
var useconst=0;
var i,m,irow ;

if (typeof(bvals)!=='object' || typeof(bvals.length)=='undefined') {
        useconst=1;
} else {
  if (rlist.length != bvals.length) {
        alert('replaceRows error: rlist length='+rlist.length+', bvals.length='+bvals.length);
        return amtx ;
   }
}

for (var i=0;i<rlist.length;i++) {
  irow=rlist[i];
  if (irow<0 || irow>=amtx.length) continue ;                   // ignore if not pointing to existing row of amtx
   if (useconst==1) {
      for (var m=0;m<amtx[irow].length;m++) amtx[irow][m]=bvals;
   } else {
      amtx[irow]=bvals[i];
   }
}
return amtx;
}


// ===============================================
//      ====== Functions that perform math operations using on columnts of a matrix ======
// ===============================================


//==========
// shortcut into  mtxMath: define two arrays that are used to construct a 2 column matrix.
// Hence, the mathEqu rules are the same, with $0 refering to the v1 array, and $1 to the v2 array
// Note: one of v1 or v2 can be a constant 
function mtxMath2(v1,v2,mathEqu,defval) {
  var vv,v3 ;
  if (typeof(v1)=='undefined') return v1 ;
  if (typeof(v2)=='undefined') return v2 ;
  if (arguments.length<3) defval=0;
  if ( (typeof(v1)=='number'|| typeof(v1)=='string' ) && (typeof(v2)=='number'|| typeof(v2)=='string' ) ) {
        v1=[v1]; v2=[v2];
  } else {
     if (typeof(v1)=='number'|| typeof(v1)=='string' ) v1=initArray(v2.length,v1);
     if (typeof(v2)=='number' || typeof(v2)=='string') v2=initArray(v1.length,v2);
  }
  var v3=appendCols(v1,v2);
  vv=mtxMath(v3,mathEqu,defval);
  return vv ;
}

//==========
// do math compuations on colums of vv, return in an array
function mtxMath(vv,mathEqu,defval) {
 var i,rr,tolook=[],toUse=[];
 var newv=[],n,bequ,vrow,iuse ;
 var toLook=[],toUse=[];
_originalEquation='';

  if (arguments.length<3) defval=0;

  for (var i=0;i<10;i++) {                               // find all $n (n= 0 to 9) arguments, and create a regexp to match them
    if (mathEqu.indexOf('$'+i)>-1) {
         rr=new RegExp('\\$'+i,'g');
         toLook=toLook.concat(rr);
         toUse=toUse.concat(i);
    }
  }
  if (mathEqu.indexOf('$ ')>-1) {                         // LOOK FOR  $
         rr=new RegExp('\\$\\s','g');
         toLook=toLook.concat(rr);
         toUse=toUse.concat(0);
   }
 for (var n=0;n<vv.length;n++) { 
    vrow=vv[n];
    bequ=mathEqu ;
    for (var i=0;i<toLook.length;i++) {
        iuse=toUse[i];
        if (iuse>=vrow.length) {                   // no such column, so this skip this row 
           useit=defval ;
        } else {
           useit=vrow[iuse];
        }
        if (useit==='') useit="''";
        bequ=bequ.replace(toLook[i],useit+' '); 
    }
    newv[n]=E2_doMath(bequ);
} 
return newv ;
}

//=========
// return a capitalized, no spaces, list of names given an array or a space delimited string
 function makeVnames(vname0,douni) {
var m,varlist ;
if (arguments.length<2) douni=1;

if (typeof(vname0)!=='object' && !isNaN(vname0))   {
   return [vname0] ;              // special case
}

if (typeof(vname0)=='object' && typeof(vname0.length)!=='undefined') {
   if (vname0.length==0) {
         return vname0 ;
   }
   for (var m=0;m<vname0.length;m++) {
        vname0[m]=strip(vname0[m]).toUpperCase() ;
   }
   if (douni==1) vname0=uniqueArray(vname0);
   return vname0 ;
} else {
   varlist=getWord(vname0);
   for (var m=0;m<varlist.length;m++) {
        varlist[m]=strip(varlist[m]).toUpperCase() ;
   }

   if (douni==1) varlist=uniqueArray(varlist);
   return varlist ;
}

}


//===============================
// ================ DO (data object) functions =======
//===============================

//===============================
// create a "dataObject"
// A dataObject is collection of arrays accessible by variable names.
// Each array MUST have the same number of rows -- the idea is that a given row of an array defines a unique "entry".
// For example, each "entry" could be a "ticket" submitted by a participant
// 
//  createDO, standard syntax: takes one variable: nlen OR idArrays
//   nlen:  the number of entries to initialize.
//           A randomly generated set of entry-ids will be assigned to each of the nlen entries initialized in the dataObject
//           that will be added using addDO.  Adding an array that has a length <> this length is an ERROR.
//   idArrays: an array of identirifiersIDS. The dataObject is initialized to be the length of this array (i.e.; an nlen of idArrays.length is used)
//            and the values of idArrays are used as the entry-IDs of each entry (in order of appearance in idArrays)
//
//  SPECIAL SYNTAX: 2 parameters
//       This is a short cut to create a 1 row matrix ( with multiple varaibles)
//        var ado=createDO(vvals,varnames)
//      where vvals is K length array (0... K-1), and varnames is a space delimited string containing K variable names
//      Note: the following are the same:
//           vvals=[1,3,5]                                      // a row of 3 variables 
//           var znew=createDO(vvals,'My1 My2 My3 ');           // create a data object containing one record, with three variables
//     and
//           vvals=[1,3,5]                                  // a row of 3 variables 
//           var vvals2=appendArray(vvals);                // make a 1 row matrix using vvals
//           var znew=createDO(1)                         // create a data object
//           znew.add(vvals2,'My1 My2 My3');          // add a matrix to this data object
//
//  After using createDO, you can use  the
//     add, addConstant, extract and update methods (of the object created by createDO) to maninipulate it
//   You can use get to extract an array (or a matrix)
//
//   Note: when using add (and other methods) to add data-arrays to a dataObject, the length of the dataArray must match
//         the length of the dataObject. So one must be properly specify nlen, or idArrays

function createDO(arg1,arg2) {
 if (arguments.length<2) arg2='' ;
 var nlen,i,j ;
 var z={} ;
 var ilist={} ;
 var idlist=[] ;

// 2nd argument?
  if (arg2!=='') {
    nlen=0;
    myid=(' '+ Math.random()).substr(3) ;
    idlist[j]=myid;
    ilist[myid]=j ;
  }   else {                                            // no 2nd arg
    if (typeof(arg1)=='object' && typeof(arg1.length)=='number') {                 // arg1 is idarrays
        nlen=arg1.length;
        for (var i=0;i<arg1.length;i++)  {
           idlist[i]=arg1[i] ;
           ilist[arg1[i]]=i;
        }
    } else {                                       // arg1 is a number
       nlen=arg1;
       for (var j=0;j<nlen;j++) {
          myid=(' '+ Math.random()).substr(3) ;
          idlist[j]=myid;
          ilist[myid]=j ;
       }
    }
 }                              // no 2nd arg
 z.nentries=nlen;
 z['$indices']=ilist ;                  // given a unique id, return the row number
 z['$idlist']=idlist ;                  // given a row number, return the  id
 z['$varlist']=[];
 z['$varlookup']={};

 z['$constlist']=[];


  z.get=getDOf ;
  z.add=addDOf ;
  z.removeVars=removeVarsDOf ;
  z.math=mathDOf ;
  z.addRecord=addRecordDOf ;
  z.addConstant=addDOcf ;
  z.update=updateDOf ;
  z.extractID=extractDOidf ;
  z.updateRecord=updateDOrecordf ;
  z.display=displayDOf;
  z.extract=extractDOf ;
  z.extractRule=extractDOrulef ;
  z.combine=combineDOf ;
  z.save=saveDOf ;

  if (arg2!=='') {                      // 2nd arg: add a 1 row to the data object
     z.addRecord(arg1,arg2);
  }

 return z ;

}   

//============== constlist
// get an array (or a matrix) from a data object
// The array (or matrix) arrays WILL have a length equal to the nlen used when creating the dataObject
// vnames: a space delimited list of names. Or an array of names
// amess: error message to display (if no such varname). If not specified, a generic error message is created
//        if '', then return undefined (no error message)
// vv : the array, or matrix, created  

// NOTE: if unknown variable listed in vnames, error occurs


function getDOf(vnames,amess) {

var varlist,m,vname,stuff,mm,vvals;
var z ;
z=this ;

stuff=[];

varlist=makeVnames(vnames) ;

for (var m=0;m<varlist.length;m++) {
  vname=varlist[m] ;
  if (typeof(z[vname])=='undefined') {
        if (arguments.length<2) {
           alert('Error in .get(): non-existent variable: '+vname+ '\n[available vars: '+ z['$varlist'].join(' | ') ) ;
        } else {
           if (strip(amess)=='') return undefined ;
           alert(amess)
        }
        return undefined ;
   }
}

if (varlist.length==1) {
  vname=varlist[0] ;
  vvals=z[vname];
//alert(varlist+' xstuff===='+vvals);
  return vvals ;
}

for (var m=0;m<varlist.length;m++) {
  vname=varlist[m] ;
  vvals=z[vname];
  stuff[stuff.length]=vvals ;
}
//alert(varlist+' stuff===='+stuff);
return stuff;
}




//==============
// add an array (or a matrix) to a data object
// The array (or matrix) arrays MUST have a length equal to the nlen used when creating the dataObject
// vv : the array, or matrix  to add
//       Short cut: if a constant, use the constant value for all elements of an array
// vname0: a space delimited list of names. Or an array of names
//   Each row (or word) of vname corresponds to a column in vv 
//   If a row of vv has fewer values than the length of vname, undefined is used for missing values
// NOTE: variables are replaced if they exist. Otherwise, a new variable is added
//
//  To add a rows, see addRecordDO

function addDOf(vv,vname0) {

var n,i,m,vname,useval,arow
var isconst=0;
var newvs=[] ;
var z ;
z=this ;

if (typeof(z)!='object' || typeof(z.nentries)!='number') {
        alert('Error in add: z is not a dSurvey data object ') ;
        return 0;
}

varlist=makeVnames(vname0) ;

  if (typeof(vv)!='object' || typeof(vv.length)=='undefined' ) {
        isconst=1 ;
  } else {
     if (vv.length!=z.nentries) {                  // this is an array (possibly multi dimensional)
        alert('Error in add: for '+vname0+', length of array-to-add ('+vv.length+') is not '+z.nentries);
        return 0;
     }
   }
   for (var i=0;i<varlist.length;i++) {         // initazliase 
           vname=strip(varlist[i]) ;
           if (typeof(z[vname])=='undefined') {
                 z[vname]=[];
                 newvs=newvs.concat(vname);
           } 
   }

   for (var n=0;n<z.nentries;n++) {
      for (var i=0;i<varlist.length;i++) {
          vname=varlist[i];
          if (isconst==1 ) {
                useval=vv ;
          } else {
             if (typeof(vv[n])=='object') {
                if (i>vv[n].length) {
                     useval=undefined;
                } else {
                     useval=vv[n][i];
                }
             } else {
                 if (i>0) {
                     useval=undefined;
                 } else {
                     useval=vv[n] ;
                 }
              }
          }
          z[vname][n]=useval ; 
      }
    }               

    z['$varlist']=z['$varlist'].concat(newvs);                // add variable names to internal list
    z['$varlookup']=updateVarLookup(z) ;
   return 1 ;
}


// =================== constlist
// remove variables from a data object
function removeVarsDOf(vlist,iskeep) {

var n,i,m,vname,useval,arow
var tmp,aname,iv,adrop;

var z ;
z=this ;

if (typeof(z)!='object' || typeof(z.nentries)!='number') {
        alert('Error in removeVars: z is not a dSurvey data object ') ;
        return 0;
}

if (arguments.length<2) iskeep=0;

vlist=makeVnames(vlist) ;


if (iskeep==1) {                        // vlist is the KEEP list. So drop variable NOT in it
 adrop={};
 for (var iv=0;iv<z['$varlist'].length;iv++) {
    aname=z['$varlist'][iv];
    adrop[aname]=1;
 }
 for (var iv=0;iv<z['$constlist'].length;iv++) {
    aname=z['$constlist'][iv];
    adrop[aname]=1;
 }

 for (var iv=0;iv<vlist.length;iv++) {
    aname=vlist[iv];
    adrop[aname]=0;
 }
 tmp=[];
 for (var iv in adrop) {
   if (adrop[iv]==1) tmp=tmp.concat(iv);                // not set to 0 (by varname in KEEP), so drop it
 }
 vlist=tmp ;
}

if (vlist.length>0) {
  z['$varlist']=removeVars(z['$varlist'],vlist);
  z['$varlookup']=updateVarLookup(z) ;

  z['$constlist']=removeVars(z['$constlist'],vlist);

}
return 1 ;

}

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

// =======================
// compute equation(s) in anequ.
// If just one argument: The equation typically contains variables stored in the z dataobject. 
// Thus, each record of z will be directly  modified (with perhaps records discarded)
// 2 arguments: then the equation refers to records (and their variables) in zuse0.
// Actually, a copy of zuse0 is made, and then modifed (with perhaps records discarded).
// The variables in the records in this copy will be used to modify z.
//  Thus, no records are discarded from z, and the contents of zuse0 is NOT modified permanently
// 
// anequ can be an equation that contains variable names from the zuse data object.
// basic syntax of anequ:
//   avar = an equation  
// The avar variable will be created and added to each record in zuse. If an equation contains variables
// that appear in zuse, then the values will be used. Note that a value is created for each record in zuse,
// and will use the record specific values of "variables that appear in zuse". Note that a variety of 
// statistical and logical functions can appear in anequ
// Examples:
//       new1= bid.le(150) * (bid / bid.mean() )
//       new2 =  Math.max(50,prodcost.min()) + ( (prodcost.max()- prodcost.min() ) * this.random(0,1) )  - bid ; 

function mathDOf(anequ,cvarlist,cvallist) {
var avar,eqUse,eqlist,aequ0,aequ1,j,allequs,eqsnew;
var wrds,w1;
var n,i,m,vname,useval,arow,ir,aval,iv,tmp,adef,nifs=0 ;
var isConst,ifEqu;
var newvs=[] ;
var z,zuse ;
var atype,isTemp,vv,aval,avar ;
var tempList=[],cTempList=[] ;
var zIf=0;
var jj,tval;


if (arguments.length<2) cvarlist=[];
if (arguments.length<3) cvallist=[];

byInfo={} ; byInfo.ON=0;

z=this ;


if (typeof(z)!='object' || typeof(z.nentries)!='number') {
        alert('Error in math: z is not a dSurvey data object: '+typeof(z) +', ' + typeof(z.nentries)) ;
        return 0;
}

zuse=z.extract() ;                  // start with  clone of z

if (arguments.length>1) cvarlist=makeVnames(cvarlist,0);

if (arguments.length>2) cvallist=makeVnames(cvallist,0); 

for (var iv=0;iv<cvarlist.length;iv++) { 
  avar=strip(cvarlist[iv]).toUpperCase(); cvarlist[iv]=avar;
  aval=cvallist[iv];
  if (typeof(aval)!=='object') {                // make a scalar constant
        zuse.addConstant(aval,avar);
  } else {                                      // make an array constant
        zuse.addConstant(aval,avar,1);
  }
}

allequs=anequ.split(';');
for (var j=0;j<allequs.length;j++) {
  aequ0=allequs[j];

  if (strip(aequ0)=='') continue;
  if (strip(aequ0).substr(0,2)=='//') continue ;  

  wrds=getWord(aequ0);          // check for COMMENT ; 
  w1=strip(wrds[0]).toUpperCase();

  if (w1=='COMMENT'  ) continue ;

   _originalEquation=aequ0 ;  

  if (w1=='IF') {                            // conditonal exectuion?
    ithen=aequ0.toUpperCase().indexOf(' THEN '); 
    if (ithen>-1) {

        ifEqu=aequ0.substr(0,ithen); 
        ifEqu=strip(ifEqu); ifEqu=ifEqu.substr(2);           
        eqlist=parseMathDO(zuse,ifEqu,1);               // check logical condition (force return as constant

        if (eqlist.length==0 ) {               // error
          alert('parseMathDO error: IF logical condition returns empty string: '+ifEqu);
          return  0;
        }
        qq=E2_doMath(eqlist[0]);
        if (qq<=0) {
            continue ;                // skip equation
        }

        aequ0=aequ0.slice(ithen+6);          // otherwise, do this equation (strip out stuff before the THEN)
        eqsnew=aequ0.split('~');

        tmp=[];
        for (var i=0;i<=j;i++) tmp[i]=allequs[i] ;
        for (var i=0;i<eqsnew.length;i++) tmp[j+1+i]=eqsnew[i];
        for (var i=j+1;i<allequs.length;i++) tmp=tmp.concat(allequs[i]);
        allequs=tmp;

        continue ;                      // goto next j
    }
  }

  aequ1=aequ0.split('=');

  if (aequ1.length!==2) {

// check for special commands: SELECT IF KEEP CONSTANT ARRAY RETURN BY
     w1=strip(wrds[0]).toUpperCase();

     if (w1=='IF' || w1=='SELECT') {                            // convert to IF=equation (recognized below)

         w2=strip(wrds[1]).toUpperCase();
         if (w2=='RESET') {                     // or reset 
             if (nifs<=0) {
                alert(w1+' RESET used, but no prior '+w1+' : '+anequ);
                return 0;
             }
             adef=undefined;
             if (wrds.length>2) {
                 adef=strip(wrds[2]);
                 zuse=zIf.combine(zuse,adef);
             } else {
                 zuse=zIf.combine(zuse);
             }
             nifs=0;
             continue ;
         } else {
            wrds[0]='IF=';
            aequ0=wrds.join(' ');
            aequ1=aequ0.split('=');
            nifs++ ;
            if (nifs==1) {
                zIf=zuse.extract() ;   // only create backup if this is top level if
           }
         }


     } else if (w1=='DROP') {                    // no equation: process rest of words
         wrds[0]='';
         aequ0=wrds.join(' ');
         zuse.removeVars(aequ0);
//        alert(' Dropping : '+aequ0+' \n Variables now are: '+zuse['$varlist']);
        continue ;

     } else if (w1=='KEEP') {                    // no equation: process rest of words
         wrds[0]='';
         aequ0=wrds.join(' ');
         zuse.removeVars(aequ0,1);
//        alert(' Keeping variables: '+zuse['$varlist']);
        continue ;

     } else if (w1=='BY') {                    //  set, or unset, BY info (for subsetting rows)
        if (byInfo.ON==1) {
             byInfo=0;
        } else {                                // 
          if (wrds.length<2) {
                alert('math error: no variable after a BY ');
                return 0;
          }
          avar=strip(wrds[1]).toUpperCase();
          if (typeof(zuse[avar])=='undefined') {
                alert('math error: no such variable after a BY: '+avar);
                return 0;
          }
//          alert('BY== '+avar+' = '+zuse[avar]);
          byInfo.ON=1;
          byInfo.varName=avar;       // name of by variable (not currently used)
          byInfo.varVals=[];            // the values of the by variable (not currently used)
          byInfo.valList=[];            // list of unique values of by variable
          byInfo.indices={};            // list of arrays, each array points to rows of by var with a valList value
          byInfo.statvals=[];                   // values of the stat function, for subsets defined by the by variable (set when func analyzed
          for (var jj=0;jj<zuse[avar].length;jj++) {
                tval=zuse[avar][jj];
                byInfo.varVals[jj]=tval;
                if (typeof(byInfo.indices[tval])=='undefined') {
                        byInfo.valList=byInfo.valList.concat(tval);
                        byInfo.indices[tval]=[];
                        byInfo.indices[tval][0]= jj;
                } else {
                        byInfo.indices[tval]=byInfo.indices[tval].concat(jj);
                }
               byInfo.valList=sortNum(byInfo.valList)
          }                                     // for all rows in zuser[avar]
        }
        continue ;

     } else if (w1=='RETURN') {               // no equation, return a variable or constant. Or just stop
         if (wrds.length==1) {                // stop processing, return new data object
           if (cTempList.length>0) {
               zuse['$constlist']=removeVars(zuse['$constlist'],cTempList); 
           }
           if (cvarlist.length>0)   zuse['$constlist']=removeVars(zuse['$constlist'],cvarlist);
           return zuse ;
        }                              

// else, return a dataVariable or constant, possibly as a matrix
        vv=[];
        for (var m=1;m<wrds.length;m++) {
           avar=strip(wrds[m]).toUpperCase();           
           if (typeof(zuse[avar])=='undefined') {

var jj,goob='';
for (var jj in zuse ) {
       goob=goob+jj+' '+ typeof(zuse[jj])+' | ';
}
                alert('parseMathDo error: RETURN a non-existent dataObject variable: '+avar+'\n vars= '+goob); 
                return 0;
           }
           aval=zuse[avar];
           if (wrds.length==2) return aval ;  // one variable, return as is
          
           vv[vv.length]=aval ;
        }
        return vv  ;

     } else {
        alert('math error: equation does not have an aVar=anEquation format. \n'+aequ0);
        return 0;
     }
  }
  avar=strip(aequ1[0]).toUpperCase();
  wrds=getWord(avar);


  isConst=0;isTemp=0;
  if (wrds.length>1) {                                  // save as a CONSTANT ?
     w1=strip(wrds[0]).toUpperCase();
     if (w1=='CONSTANT' || w1=='CONST' ) {
         isConst=1 ;
         avar=strip(wrds[1]).toUpperCase();
     } else if (w1=='ARRAY' ) {
         isConst=2 ;
         avar=strip(wrds[1]).toUpperCase();
     } else {
        alert('math error: bad variable name= ' +avar);
        return 0;
     }
  }

// look for shortcuts in avar
  if (avar.indexOf('.')>-1) {
       wrds=parseAt(avar,'.');
       avar=wrds[0];
     w1=strip(wrds[1]).toUpperCase();         
      if (w1=='1' || w1.substr(0,1)=='C' ) {
          isConst=1 ;
      } else if (w1=='2' || w1.substr(0,1)=='A' ) {
         isConst=2;
      } else {
        alert('Error: bad variable name = '+wrds.join('.'));
        return 0;
     }
  }

  eqUse=strip(aequ1[1]);

  if (avar.substr(0,1)=='_' && isConst==1) {
       cTempList=cTempList.concat(avar);
  }


  eqlist=parseMathDO(zuse,eqUse,isConst);               // this does the work

//  if (eqlist.length==0) return 0;                      // an error?

  qq=[];
  if (eqlist.length==0 ) {               // no equations to analyze
      qq=[];
  } else {
    for (var ir=0;ir<eqlist.length;ir++) {                // this processes the work 
       qq[ir]=E2_doMath(eqlist[ir]);
    }
  }
 if (avar=='IF') {                      // selection

     zuse=zuse.extractRule(qq,'gt',0);  

  } else {

    if (isConst==0) {                           // data array
          zuse.add(qq,avar);
          mathDoSorteds[avar]=='';                  // zap sorted 
          
    } else if (isConst==1) {                    // single valued constant
        zuse.addConstant(qq[0],avar);
        mathDoSorteds[avar]=='';                  // zap sorted 

    } else if (isConst==2) {                    // array constant
          zuse.addConstant(qq,avar,1);
          mathDoSorteds[avar]=='';                  // zap sorted   

     } else {
        alert(' isConst bad ');
    }

  }                     // IF

//showMathDO(zuse,avar,eqUse);  

}                       // next equation 


if (cTempList.length>0) {
  zuse['$constlist']=removeVars(zuse['$constlist'],cTempList); 
}


if (cvarlist.length>0)   zuse['$constlist']=removeVars(zuse['$constlist'],cvarlist);         // remove "arguments"


return zuse ;


}


//==================== domath
/*
   parse an equation in  dataobject.
   Expand to nentries rows; replace data-object variable with row-specific values
   z: data object to use
   eqString: equation string
   isconst : if 1, just do one row (using the first row of z.var values)
   returns an array, one line per row in z (z.nentries). Each row is a string that can be analyzed by 
                     or just one line (if isconst=1)


Create a new dataObject variable
  newz=  _N()     -- row number
  newz=   RANDOM(min,max,nddec) -- Uniform random number between min and max (with ndec digits after the . )

Create a new array using some kind of stat on variable 
  newz=   z.freq(a b c,) or freq(arrrayConstant)  : frequency on values a,b, (or values in arrayConstant)
  newz=   z.unique(a b c .. ,0/1) or z.unique(arrayConstant,0/1) : array of unique values found in z (if ,1; returned sorted)
  newz=   z.sort()   --  nentries x 1 : order stat of this row (value of 0 means "lowest value")

Create an arbitrary array
   newArray = array(n1,n2,n3,...)
   n1, etc can be scalarConstants or controlVariable (the values will be used).
   Or, it can be a dataVariable array or an arrayConstant -- the list will be expanded with their values.
   
Lots of "stats" on variables functions: 
    These return an array, all elements having the same value.
       newz1=z.func()
    where func can be one of:     
        MAXVAL() MINVAL() MEDIAN() SUM() MEAN() VAR()  SD() MISS() LENGTH() NOTMISS() 
        MAXVAL_ROW() MINVAL_ROW() MEDIAN_ROW() 
        ORDER(nth) ORDER_ROW(nth)
        #EQ(aval) #NE(aval) #GT(aval) #LE(aval) #LT(aval) #GT(aval)
        IN(i1,i2,..)

There are 3 steps:
  A) Parse the equation, finding variables, constants, and functions.  Save the specs of these variables
     to a vreps array
  B) Loop through the vreps array and resolve the specified variables, etc. Creates an array that will be read
     in the next step 
  C) For each row to be created, regenerate the equation using the replacemens computed in step 2. Save
    this generated equation

*/

function parseMathDO(z,eqString,isconst) {


var varlist,astring,astring1,constlist;
var stuff='';
var i1=-1;
var varCharsPre='ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_.';
var varCharsPost='ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_';
var kk,ifoo,farg,iv3,jmax,ss;

var eqList=[];

var vreps=[];
var apre,apost,ii,tmp,in1,in2 ;
var ito,aindex,tt,t1,ival,itype,useval,compval,afunc,aval ;
var useval,iat,tmp,aa,thevar,ivr,skips=[] ;

var arrayVars={};

var zMakeFuncs=['_N','RANDOM','NORMAL','BY_VALS','BY_STATS'];
var zStat2Funcs=['UNIQUE','FREQ','SORT','FIND','IN','KEEP','IF','FIX','REPLACE','MAX','MIN','SLICE'];

var iv,ivv,jdo,zvar,anarg ;
var newargs,anargs;

mathDoSorteds={};                  // set a global

dostring=' '+strip(eqString).toUpperCase()+' ' ;

varlist=z['$varlist']  ;
constlist=z['$constlist']  ;
astring1=[] ;
for (var iv=0;iv<dostring.length;iv++) {
   astring1[iv]=dostring.substr(iv,1);                  // will contain "processed" equation string
   skips[iv]=0;                                 // used to avoid redundancies
}


// ===== STEP A ====

// make the vreps "replacement" array. Each element of vreps is an array, with the following syntax
// element 0: type of thing (ZVAR, ZVAR_ELEMENT, ZVAR_STAT or ZVAR_STAT2
//         1: name of thing
//         2: where to insert results in equation string
//         3: arguments (depends on itype)
//         4 : blank (for later use)

// There are 4 sub-steps (for four different subsets of replacements)

//  ::::: (A.1) : look for dataObject variables (possibly with element or statsFUnction modifiers  ::::

for (var ivr=0;ivr<varlist.length;ivr++) {
  avar=strip(varlist[ivr].toUpperCase());
  i1=-1 ;
  for (var ivv=0;ivv<100000;ivv++) {            // multiple instances of avar ?
     i1=dostring.indexOf(avar,i1+1);
     if (i1<0) break ;                      // no match? get next variable

     apre=dostring.substr(i1-1,1);
     apost=dostring.substr(i1+avar.length,1);
     if (varCharsPre.indexOf(apre)>-1) continue                ;       // a valid var char exists before this string
     if (varCharsPost.indexOf(apost)>-1) continue                ;       // a valid var char exists after this string

// got a matching variable (or function name)! Look for modifiiers
     itype='ZVAR';                                  // by default, assume it's a simple dataVariable
     ito=avar.length+i1;                       // is this a substring of some other variable?
     aindex=0 ;  

// if apost=[, then pull out a particular value, or a stat
     if (apost=='[') {                          // varname[n]
           tt=dostring.indexOf(']',i1);
           if (tt<0) {
                alert('parseMathDO  error: missing ] : '+eqString);        return 0;
           }
           aindex=dostring.slice(i1+avar.length+1,tt);         // which element to use
           itype='ZVAR_ELEMENT';                  // a scalar, using the n'th row of the dataVariable 
           ito=tt+1 ;
     }

    if (apost=='.') {                                           // varname.func(arg)
            tt=dostring.indexOf(')',i1);
            if (tt<0) {
               alert('parseMathDO  error: missing ) : '+eqString);         return 0;
            }
            aindex=dostring.slice(i1+avar.length+1,tt+1);         // function AND argument list 
            itype='ZVAR_STAT';                           //  a scalar, using a stats function on a dataVariable

            tmp=parseUsing(strip(aindex).toUpperCase(),'(',')',1);
            for (var iv2=0;iv2<zStat2Funcs.length;iv2++) {              // zStat2Funcs=['UNIQUE,'FREQ','SORT','FIND','IN','KEEP','IF','FIX','REPLACE','MAX','MIN','SLICE'];
                if (zStat2Funcs[iv2]==tmp[0]) {
                   itype='ZVAR_STAT2';
                   break;
                }
             }
            ito=tt+1 ;
    }

    vreps[vreps.length]=[itype,avar,i1,aindex,0];    // add to the vreps temp storage

// clear out space covered by this variable (with possible modifiers)
    for (var ii=i1+1;ii<ito;ii++) {                       
         astring1[ii]='';     // clear out the variable name, and any function or element modifier
         skips[ii]=1;         // if a variable, function, etc  is in here, ignore it
    }
 
 }                                                   // for all instances of a variable name (IVV)
}                                               // search for all variables define for this data object (IVR)


//  ::: (A.2) Search for Z_MAKEFUNC functions (standalone functions) :::::::::::

for (var ivr=0;ivr<zMakeFuncs.length;ivr++) {
  avar=strip(zMakeFuncs[ivr].toUpperCase());
  i1=-1 ;
  for (var ivv=0;ivv<100000;ivv++) {            // multiple instances of avar ?
     i1=dostring.indexOf(avar,i1+1);
     if (i1<0) break ;                      // no match? get next variable


     ito=avar.length+i1;                       // is this a substring of some other variable?
     apre=dostring.substr(i1-1,1);
     apost=dostring.substr(i1+avar.length,1);
     if (varCharsPre.indexOf(apre)>-1) continue                ;       // a valid var char exists before this string
     if (varCharsPost.indexOf(apost)>-1) continue                ;       // a valid var char exists after this string

// got a zMakeFuncs function. Look for argument list
    if (apost!=='(') {                                           // func(arg)
          alert('parseMathDO  error: missing ( in create function: '+eqString);
          return 0;
    }
    tt=dostring.indexOf(')',i1);
    if (tt<0) {
           alert('parseMathDO  error: missing ) in create function: '+eqString);
           return 0;
    }

    farg=dostring.slice(i1+avar.length+1,tt);         // function argument
    vreps[vreps.length]=['Z_MAKEFUNC',avar,i1,farg,0];

    ito=tt+1 ;
    for (var ii=i1+1;ii<ito;ii++) {
         astring1[ii]='';     // clear out the variable name, and any function or element modifier
         skips[ii]=1;         // if a variable, function, etc  is in here, ignore it
    }

  }                                                    // for all instances of a z_func (IVV)
}                                               // search for all zCreateFuncs (IVR)

//  ::::: (A.3) create an arrayConstant

  avar='ARRAY';
  i1=-1 ;
  for (var ivv=0;ivv<100000;ivv++) {            // multiple instances of avar ?
     i1=dostring.indexOf(avar,i1+1);
     if (i1<0) break ;                      // no match? done 

     ito=avar.length+i1;                       // is this a substring of some other variable?
     apre=dostring.substr(i1-1,1);
     apost=dostring.substr(i1+avar.length,1);
     if (varCharsPre.indexOf(apre)>-1) continue                ;       // a valid var char exists before this string
     if (varCharsPost.indexOf(apost)>-1) continue                ;       // a valid var char exists after this string

// got a zcreate function. Look for argument list
     if (apost!=='(') {                                           // func(arg)
          alert('parseMathDO  error: missing ( in ARRAY(): '+eqString);
          return 0;
     }
     tt=dostring.indexOf(')',i1);
     if (tt<0) {
           alert('parseMathDO  error: missing ) in ARRAY(): '+eqString);
           return 0;
     }

     farg=dostring.slice(i1+avar.length+1,tt);         // function argument
     vreps[vreps.length]=['MAKEARRAY',avar,i1,farg,0];

     ito=tt+1 ;
     for (var ii=i1+1;ii<ito;ii++) {
         astring1[ii]='';     // clear out the variable name, and any function or element modifier
         skips[ii]=1;         // if a variable, function, etc  is in here, ignore it
     }

}                                           // search for all isntances of ARRAY(...) (IVV)

// :::: (A.4) process constants  :::::

for (var ivr=0;ivr<constlist.length;ivr++) {
  try {
     avar=strip(constlist[ivr].toUpperCase());
  } 
  catch(ierr) {
        alert(ivr+' error '+ierr+'='+constlist.length+':'+typeof(constlist[0])+', constlist[0].length='+constlist[0].length);
  }
  i1=-1 ;
  for (var ivv=0;ivv<100000;ivv++) {            // multiple instances of avar ?
     i1=dostring.indexOf(avar,i1+1);
 
     if (i1<0) break;                          // next instance of constant not found -- skip 

     ito=avar.length+i1;
     apre=dostring.substr(i1-1,1);
     apost=dostring.substr(i1+avar.length,1);
     if (varCharsPre.indexOf(apre)>-1) continue                ;       // a valid var char exists before this string
     if (varCharsPost.indexOf(apost)>-1) continue                ;       // a valid var char exists after this string
     aindex=0 ;

     itype="SCALAR";
     if (typeof(z[avar])=='object') itype="ARRAY";

     if (apost=='[') {                          // constname[n]
            tt=dostring.indexOf(']',i1);
            if (tt<0) {
                alert('parseMathDO  error: missing ] : '+eqString);
                return 0;
            }
            if (itype!=='ARRAY') {                    // error: this is not an arrayConstant
                alert('parseMathDo error: referring to an element on  a scalarConstant  ('+avar+'): '+eqString);
                return 0 ;
            }
            aindex=dostring.slice(i1+avar.length+1,tt);
            ito=tt+1 ;
            itype="ARRAY_ELEM" ;
    }

    if (apost=='.') {                                           // constname.func(arg)
            if (itype!=='ARRAY') {                    // error: this is not an arrayConstant
                alert('parseMathDo error: referring to a stats function on  a scalartConstant element ('+avar+'): '+eqString);
                return 0
            }

            tt=dostring.indexOf(')',i1);
            if (tt<0) {
               alert('parseMathDO  error: missing ) in '+avar+' : '+eqString);         return 0; 
            }
            aindex=dostring.slice(i1+avar.length+1,tt+1);         // function AND argument list 
            itype='CONST_STAT';                           //  a scalar, using a stats function on a dataVariable

            tmp=parseUsing(strip(aindex).toUpperCase(),'(',')',1);
            for (var iv2=0;iv2<zStat2Funcs.length;iv2++) {              // zStat2Funcs=['UNIQUE,'FREQ','SORT','FIND','IN','KEEP','IF','FIX','REPLACE','MAX','MIN','SLICE'];
                if (zStat2Funcs[iv2]==tmp[0]) {
                   itype='CONST_STAT2';
                   break;
                }
             }
            ito=tt+1 ;
    }

    for (var ii=i1+1;ii<ito;ii++) {
             astring1[ii]='';          // clear string (just use i1 element to store stuff)
             skips[ii]=1;         // if a variable, function, etc  is in here, ignore it
    }
      
    vreps[vreps.length]=[itype,avar,i1,aindex,0];

 }                                                    // for all instances of a constant name (IV)
}                                                     // search for all constants define for this data object (IVR)

// ===== STEP B ====


// === Now process the "replacements" specified in VREPS.
//     Most of the replacements "replacing" an element in the string with a value -- a value that may depend on the record
//     Some of the repacements mean creating values in the arrayVars object
// The "types" of replacements are:
// ZVAR, ZVAR_ELEMENT, ZVAR_STAT, ZVAR_STAT2, MAKEARRAY, ARRAY, SCALAR, ARRAY_ELEM, Z_MAKEFUNC, CONST_STAT CONST_STAT2
// element 0: type of thing 
//         1: varname
//         2: location in astring to place the "replacement value"
//         3: depends on type
// and a 5th is added
//         4: value to use


for (var iv=0;iv<vreps.length;iv++) {

    t1=vreps[iv];

    ito=t1[2] ;
    if (skips[ito]==1) {
         continue ;               // this "replacement" is inside of another replacement. Not allowed (so ignore)
     }

    itype=t1[0];
    avar=t1[1];

    if (itype=='ZVAR') {                       //  create dataObject varaible (an nentries long array)
        vreps[iv][4]=z[avar];
        continue ;
    }                                           // ZVAR

    if (itype=='ZVAR_ELEMENT') {         // zvar element?
        anarg=parseMathDoExpandArgs(t1[3],z,1) ;
        if (typeof(anarg)=='object') {
             alert('parseMathDo error: ZVAR_ELEMENT error ('+anarg[0]+'): '+eqString); return 0;
        }
        vreps[iv][4]=z[avar][anarg];
        continue ;
    }                                           // zvar_element 

    if (itype=='SCALAR') {         // a scalarConstant
        vreps[iv][4]=z[avar];
        continue ;
    }

    if (itype=='ARRAY') {                       //  extract  arrayConstant 
        vreps[iv][4]=z[avar];
        continue ;
    }                                   // ARRAY

    if (itype=='ARRAY_ELEM') {         // element of an arrayConstant
        anarg=parseMathDoExpandArgs(t1[3],z,1) ;
        if (typeof(anarg)=='object') {
             alert('parseMathDo error: ARRAYELEM error ('+anarg[0]+'): '+eqString); return 0;
        }
        vreps[iv][4]=z[avar][anarg];
        continue ;
    }

    if (itype=='MAKEARRAY') {                       //  create arrayConstant (indeterminate length)
        anargs=parseMathDoExpandArgs(t1[3],z) ;
        vreps[iv][4]=anargs ;
        continue ;
    }                                   // MAKEARRAY 


    if (itype=='Z_MAKEFUNC') {                 //  ['_N','RANDOM','NORMAL','BY_VALS','BY_STATS'];

       anargs=parseMathDoExpandArgs(t1[3],z) ;
       useval=parseMathDoMAKEFUNC(avar,anargs,z.nentries) ;    // avar is the function name     
       if (typeof(useval)=='undefined') {
           alert('parseMathDo error: unable to process makeFunc '+avar+' function: ' +eqString);    return 0; 
       }
       vreps[iv][4]=useval;
       continue ;
    }

    if (itype=='ZVAR_STAT2' || itype=="CONST_STAT2") {                 // ['UNIQUE,'FREQ','SORT','FIND','IN','KEEP','IF','FIX','REPLACE','MAX','MIN','SLICE'];

       tmp=parseUsing(strip(t1[3]).toUpperCase(),'(',')',1);
       afunc=tmp[0];
       anargs=parseMathDoExpandArgs(tmp[1],z) ;
       useval=parseMathDoStat2Funcs(afunc,z[avar],anargs,avar) ;         // avar used by SORT
       if (typeof(useval)=='undefined') {
           alert('parseMathDo error: unable to analyze '+afunc+' function: ' +eqString);    return 0; 
       }
       vreps[iv][4]=useval;
       continue ;
    }


    if (itype=='ZVAR_STAT' || itype=="CONST_STAT") {                  // scalar stat function

       tmp=parseUsing(strip(t1[3]).toUpperCase(),'(',')',1);
       afunc=tmp[0];
       anargs=parseMathDoExpandArgs(tmp[1],z) ;
      if (z[avar].length==0) {                  // empty array, so can't do stats
          useval=undefined;
          if (afunc=='LENGTH') useval=0 ;               // but length is 0, so return that
      } else {
         useval=parseMathDoStats(afunc,z[avar],anargs,avar);               // avar used by ORDER
  
         if (typeof(useval)=='undefined') {
             alert('parseMathDo error: not able to analyze '+afunc+' function: ' +eqString);    return 0;
         }
       }

       vreps[iv][4]=useval;
       continue ;
   }                       // vreps (stats functions)

   alert(' unknown itype: '+itype);
   
}                                       // vreps (iv); 
 

// ===== STEP C==== 
// fill in array of equation strings with actual values
//
// ZVAR, ZVAR_STAT2, Z_MAKEFUNC :  nentries x 1 
// MAKEARRAY, ARRAY, CONST_STAT2;  m x 1 (m depends on the array)  
// ZVAR_ELEMENT, ZVAR_STAT, CONST_STAT : 1 x 1
//   SCALAR, ARRAYELEM CONST_STAT2: 1x 1

iz=-1; jmax=1;
if (isconst==0) jmax=z.nentries ;        // if dataObject var, always return nentries array.

for (var ifoo=1;ifoo>0;ifoo++) {                // now do for all rows (i.e.; nentries)
 iz++;
 if (iz >= jmax) break ;                        // all done 

 for (var iv=0;iv<vreps.length;iv++) {                // now do z.variables (or functions of variables)

     t1=vreps[iv];
     avar=t1[1];
     itype=t1[0];

     vv=t1[4];
     ito=t1[2];

     if (skips[ito]==1) {
         continue ;               // this "replacement" is inside of another replacement. Not allowed (so ignore)
     }


     if (typeof(vv)=='undefined' || (typeof(vv)=='object' && vv.length==0) ) {  // empty array? skip this equation?
         astring1[ito]='' ;
         if (itype=='ZVAR' || itype=='ZVAR_STAT' || itype=='ZVAR_STAT2') {
            break;
         }
      }


     if (itype=='ZVAR' ||   itype=='Z_MAKEFUNC') {

           if (iz>=vv.length) {
                alert('parseMathDO error: dataVariable length too long ('+vv.length+' vs. ' + jmax); return 0;
           }
           aval=vv[iz];
           if (isNaN(aval) || strip(aval)==='' ) aval="'"+aval+"'";
           astring1[ito]=aval;
           if (isconst==2)   {
                if (ifoo==1) jmax=Math.max(jmax,vv.length);
           }

     } else if (itype=='ARRAY' || itype=='MAKEARRAY' || itype=='CONST_STAT2' || itype=='ZVAR_STAT2') {
           if (iz>=vv.length) {
                alert('parseMathDO error: an '+itype+' ('+t1[1]+':'+t1[3]+') length too small (array is '+vv.length+', expecting ' + jmax); return 0;
           }
           if (isconst==2)   {
                if (ifoo==1 && typeof(vv.length)!=='undefined') jmax=Math.max(jmax,vv.length);
           }
           aval=vv[iz];
           if (isNaN(aval) || strip(aval)===''  ) aval="'"+aval+"'";
           astring1[ito]=aval;

     } else  if (itype=='ZVAR_ELEMENT' || itype=='ZVAR_STAT' || itype=='CONST_STAT' ) {                   // ZVAR_ELEMENT, ZVAR_STAT 
           if (typeof(vv)=='object') {
                aval=vv[iz];
          } else {
                aval=vv;
          }

           if (isNaN(aval) || strip(aval)==='' ) aval="'"+aval+"'";
           astring1[ito]=aval ;

     } else {                   //     SCALAR, ARRAYELEM
           if (typeof(vv)=='object') {
                aval=vv[iz];
          } else {
                aval=vv;
          }
           if (isNaN(aval) || strip(aval)==='' ) aval="'"+aval+"'";
           astring1[ito]=aval ;
     }   
  }                                     // iv      

  ss=astring1.join('');
  if (strip(ss)!=='') {
     eqList[iz]=ss  ;               // expand to make "processed" equation for this row
  }

  if (isconst==1) break;             // constant: just do the first row

}                                       // iz

return eqList ;

}


//========== jmax
// expand/convert an argument list 

function parseMathDoExpandArgs(anargs,zz,noarray) {
  var newargs,ivv,anarg,iv3;
  if (arguments.length<3) noarray=0;

  newargs=[];
  if (strip(anargs)=='') return newargs ;

  anargs=anargs.split(',');

  for (var ivv=0;ivv<anargs.length;ivv++) {
      anarg=anargs[ivv];
      if (anarg=='') continue ;                         // ignore blanks
      if (typeof(zz[anarg])!=='undefined') {         // z variable, or (more likely) constant
               anarg=zz[anarg];
       }
       if (typeof(anarg)=='object') {             // a  arrayConstant variable, or (rarely?) a z variable
                for (var iv3=0;iv3<anarg.length;iv3++) {
                   newargs=newargs.concat(anarg[iv3]);
                }
       } else {
                if (isNaN(anarg)) anarg=getVarInfo(anarg,'value');                // allow use of control variables as indices
                newargs=newargs.concat(anarg);
       }
    }                               // anargs

    if (noarray==1 && newargs.length==1) return newargs[0];

    return newargs ;
}

//================
// compute stats functions 

/* statFunctions are specified as varname.func(anarg). Anarg is often left out.

 These create a scalar, which are used to create a temporary .nentries sized array 
 (with each value in the  array equal this scalar.

 Or, if byInfo.ON=1, then an .nentries vector is returned, with rows in the same subset having the
 same values (that is, the values in the .nentries vector will be the same for all rows with the
 same value of the BY variable).

  afunc : statFunction name
  vv0 : list of values. If byInfo.ON=1, then will be subsetted, 
  anarg : argument provided to the stat function (often, this is not used)
  avar : variable stats are applied to. This usually has values in vv0, but sometimes having varname is useful

 Supported statFunctions:
   MAXVAL()   -- maximum of array
   MINVAL()   -- minimum of array
   MEDIAN() -- median of array
   MAXVAL_ROW()   -- row # of maximum of array (start at 0)
   MINVAL_ROW()   -- row # of minimum of array  (start at 0)
   MEDIAN_ROW() --row # of median of array  (start at 0)

   SUM()    -- sum
   MEAN()   -- mean
   VAR()    -- variance
   SD()    --  standard deviation
   MISS()  --  # of missing values
   LENGTH() -- number of values
   NOTMISS() -- number of non-missing values

   ORDER(NTH)  -- value of nth biggest (nth=0 is the same as MIN
   ORDER_ROW(NTH)  -- row value of nth biggest (nth=0 is the same as MINVAL_ROW) (start at 0)

   #EQ(aval) -- number of values equal to aval   
   #NE(aval) -- number of values not equal to aval
   #LE(aval) -- number of values less than or equal to aval
   #GE(aval) -- number of values greater than or equal to aval
   #LT(aval) -- number of values less  aval
   #GT(aval) -- number of values greater  aval

jan 2011: if byInfo enabled, return a nentries x  1 with  "group" specific values
Else, just return a constant (same value for all nentries)


*/

function parseMathDoStats(afunc,vv0,anarg,avar) { 

var useval,vv,nn,usevals,jj,by1,byind;
useval=undefined ;
// MAXVAL MINVAL MEDIAN SUM MEAN VAR  SD MISS LENGTH NOTMISS ORDER(nth)  #EQ(aval) #NE(aval) #GT(aval) #LE(aval) #LT(aval) #GT(aval)

//byInfo.On, byInfo.varName, byInfo.varVals=[], byInfo.valList=[], byInfo.indices[aval]=[]

if (byInfo.ON==1)  {                    // do it BY 
  ndo=byInfo.valList.length;             // number of subsets
  usevals=[];
  byInfo.statvals=[] ;
} else {
  ndo=1;
}

for (var nn=0;nn<ndo;nn++) {

    if (byInfo.ON==0) {
         vv=vv0;
   } else {
        by1=byInfo.valList[nn] ;
        byind=byInfo.indices[by1];
        vv=[];
        for (var jj=0; jj<byind.length;jj++) {
               vv[jj]=vv0[byind[jj]];
        }
   }
 
    if (afunc=='MAXVAL' ) {
                useval=maxArray(vv);

    } else if (afunc=='MAXVAL_ROW' ) {
            useval=maxArray_row(vv);

    } else if (afunc=='MINVAL' ) {
                useval=minArray(vv);

    } else if (afunc=='MINVAL_ROW' ) {
            useval=minArray_row(vv);

    }  else if (afunc=='MEDIAN') {
               useval=medianArray(vv);

    }  else if (afunc=='MEDIAN_ROW') {
               useval=medianArray_row(vv);

    } else if (afunc=='SUM' ) {
                useval=sumArray(vv);
    } else if (afunc=='MEAN' || afunc=='AVERAGE') {
                useval=meanArray(vv);
    } else if (afunc=='VAR' || afunc=='VARIANCE') {
                useval=varArray(vv);
    } else if (afunc=='SD'  || afunc=='STDDEV') {
                useval=varArray(vv);
                useval=Math.sqrt(useval);
    } else if (afunc=='MISS' ) {
                useval=missArray(vv);
    } else if (afunc=='LENGTH' ) {
                useval=vv.length;

    } else if (afunc=='NOTMISS' || afunc=='NMISS' ) {
                useval=vv.length - missArray(vv);

    } else if (afunc=='ORDER' ) {
       if (anarg.length<1) return useval ;
       if (isNaN(anarg) || anarg>=vv.length) {
                alert('parseMathDo error: bad argument (perhaps too long?) in ORDER: var='+avar+' args= ' +anarg);
                return useval;
       }
       if (typeof(mathDoSorteds[avar])=='undefined' || mathDoSorteds[avar]==='') {
            mathDoSorteds[avar]=indexSort(vv);
            mathDoSorteds['$$'+avar]=[];
            for (var ifoo=0;ifoo<vv.length;ifoo++) {
                kk=mathDoSorteds[avar][ifoo];
                mathDoSorteds['$$'+avar][kk]=ifoo
             }
        }
        useval=vv[mathDoSorteds[avar][anarg]];


    } else if (afunc=='ORDER_ROW' ) {
       if (anarg.length<1) return useval ;
       if (isNaN(anarg) || anarg>=vv.length) {
                alert('parseMathDo error: bad argument (perhaps too long?) in ORDER_ROW: var='+avar+' args= ' +anarg);
                return useval;
       }
       if (typeof(mathDoSorteds[avar])=='undefined' || mathDoSorteds[avar]==='') {
            mathDoSorteds[avar]=indexSort(vv);
            mathDoSorteds['$$'+avar]=[];
            for (var ifoo=0;ifoo<vv.length;ifoo++) {
                kk=mathDoSorteds[avar][ifoo];
                mathDoSorteds['$$'+avar][kk]=ifoo
             }
        }
        useval=mathDoSorteds[avar][anarg];

// eq le ne ge lt gt
    } else if (afunc=='#EQ') {
           if (anarg.length<1) return useval ;
           useval=0;
           for (var ivv=0;ivv<vv.length;ivv++) {
              useval=useval + (vv[ivv]==anarg[0]   )
           }

    } else if (afunc=='#NE') {
           if (anarg.length<1) return useval ;
           useval=0;
           for (var ivv=0;ivv<vv.length;ivv++) {
              useval=useval + (vv[ivv]!=anarg[0]   )
            }

     } else if (afunc=='#LE') {
           if (anarg.length<1) return useval ;
           useval=0;
           for (var ivv=0;ivv<vv.length;ivv++) {
              useval=useval + (vv[ivv]<=anarg[0]   )
           }

     } else if (afunc=='#GE') {
           if (anarg.length<1) return useval ;
           useval=0;
           for (var ivv=0;ivv<vv.length;ivv++) {
              useval=useval + (vv[ivv]>=anarg[0]  )
           }

     } else if (afunc=='#LT') {
           if (anarg.length<1) return useval ;
           useval=e0;
           for (var ivv=0;ivv<vv.length;ivv++) {
              useval=useval + (vv[ivv]<anarg[0]   )
            }

     } else if (afunc=='#GT') {
           if (anarg.length<1) return useval ;
           useval=0;
           for (var ivv=0;ivv<vv.length;ivv++) {
              useval=useval + (vv[ivv]>anarg[0]   )
          }


    } else {
        alert('parseMathDo error: no such function: '+afunc+'('+anarg+')' );
    }

    if (byInfo.ON!==1)   return useval ;                

// else, copy to appropriate rows of usevals
    byInfo.statvals=byInfo.statvals.concat(useval);
    for (var jj=0; jj<byind.length;jj++) {
               usevals[byind[jj]]=useval;
     }

  }                                             // subset of by vars

  return usevals ;

}  

//=======  anarg avar

// zStat2Funcs=['UNIQUE,'FREQ','SORT','FIND','IN','KEEP','IF','FIX','REPLACE','MAX','MIN','SLICE'];


function parseMathDoStat2Funcs(afunc,vv,arglist,avar)  {


  var  uu=undefined;
  var dosort=0,alist;
  var ifoo,kk,i1;
  var v1,v2 ;

  if (afunc=='UNIQUE' ) {
        if (arglist.length>0 ) dosort=arglist[0];
        uu=uniqueArray(vv,dosort)  ;
   }

   if (afunc=='FREQ') {
       if (arglist.length==0 || arglist[0]=='*') arglist=uniqueArray(vv,1)  ;
        uu=freqArray(vv,arglist)  ;
   }


   if (afunc=='FIND') {
        uu=[];
        for (var iz=0;iz<vv.length;iz++) {
          iin=-1;
          for (var iv=0;iv<arglist.length;iv++) {
             if (vv[iz]==arglist[iv]) {
                 iin=iv;
                 break;
             }
          }
          uu[iz]=iin;
        }
   }

   if (afunc=='IN') {
        uu=[];
        if (arglist.length==1 && arglist[0]==='') {
                return uu ;
        }
        for (var iz=0;iz<vv.length;iz++) {
          iin=0;
          for (var iv=0;iv<arglist.length;iv++) {
             if (vv[iz]==arglist[iv]) {
                 iin=1 ;
                 break;
             }
          }
          uu[iz]=iin;
        }
   }

   if (afunc=='KEEP') {
        uu=[];
        if (arglist.length==1 && arglist[0]==='') {
                return uu ;
        }
        for (var iv=0;iv<arglist.length;iv++) {
          if (arglist[iv]>=vv.length || arglist[iv]<0) {  
                alert('parseMathDo error: KEEP value ('+arglist[iv]+') greater than array length ('+vv.length+')');
                return 0;
          }
          uu=uu.concat(vv[arglist[iv]]);
        }
   }

   if (afunc=='SLICE') {
        uu=[];
        if (arglist.length<1)  {
            alert('parseMathDo error in SLICE: must specify  arguments: '+arglist);
            return 0;
        }
        v1=strip(arglist[0]);
        if (arglist.length<2) {
           v2=vv.length-1;
        } else {
           v2=strip(arglist[1]);
        }
        v2=Math.min(vv.length-1,v2);
        uu=[];
        i1=0;
        for (var iz=v1;iz<=v2;iz++) {
            uu[i1]=vv[iz];
            i1 ++ ;
         }
   }

   if (afunc=='MAX') {                   // if iz'th argument >0, include this value
        if (arglist.length<1)  {
            alert('parseMathDo error in MAX: must specify a maximum: '+arglist);
            return 0;
        }
        v1=strip(arglist[0]);
        uu=[];
        for (var iz=0;iz<vv.length;iz++) {
                uu[iz]=Math.max(vv[iz],v1)  ;
        }
   }


   if (afunc=='MIN') {                   // if iz'th argument >0, include this value
        if (arglist.length<1)  {
            alert('parseMathDo error in MIN: must specify a minimum: '+arglist);
            return 0;
        }
        v1=strip(arglist[0]);
        uu=[];
        for (var iz=0;iz<vv.length;iz++) {
                uu[iz]=Math.min(vv[iz],v1)  ;
        }
   }

   if (afunc=='REPLACE') {                   // if iz'th argument >0, include this value
        iin=0;                  // assume strip decimal 
        if (arglist.length<2)  {
            alert('parseMathDo error in REPLACE: must specify two arguments: '+arglist);
            return 0;
        }
        v1=strip(arglist[0]);
        v2=strip(arglist[1]);
        uu=[];

        for (var iz=0;iz<vv.length;iz++) {
          if (vv[iz]===v1) {
                uu[iz]=v2  ;
           } else {
               uu[iz]=vv[iz];
           }
        }
   }

   if (afunc=='FIX') {                   // if iz'th argument >0, include this value
        iin=0;                  // assume strip decimal 
        if (arglist.length>0) iin=arglist[0];
        if (isNaN(iin) || iin<0) { alert
                alert('parseMathDo error: FIX argument is bad: '+arglist);
                return 0;
         }
         uu=[];
         for (var iz=0;iz<vv.length;iz++) {
           if (isNaN(vv[iz]) || vv[iz]=='') {
              uu[iz]=vv[iz] ;
           } else {
               uu[iz]=vv[iz].toFixed(iin);
           }
         }
   }
          
   if (afunc=='IF') {                   // if iz'th argument >0, include this value
        uu=[];
        for (var iz=0;iz<vv.length;iz++) {
          iin=0;
          if (iz>=arglist.length) {
                alert('parseMathDo error: IF arguments ('+arglist.length+') fewer than array length ('+vv.length+')');
                return 0;
          }
          if (arglist[iz]>0) {
            uu=uu.concat(vv[iz]); 
          }
        }
   }


   if (afunc=='SORT') {
       if (typeof(mathDoSorteds[avar])=='undefined' || mathDoSorteds[avar]==='') {
            mathDoSorteds[avar]=indexSort(vv);
            mathDoSorteds['$$'+avar]=[];
            for (var ifoo=0;ifoo<vv.length;ifoo++) {
                kk=mathDoSorteds[avar][ifoo];
                mathDoSorteds['$$'+avar][kk]=ifoo
             }
        }
        uu=mathDoSorteds['$$'+avar];  
   }

  return uu;

}


//======= 
// process ['_N','RANDOM','NORMAL','BY_VALS','BY_STATS'];

function parseMathDoMAKEFUNC(afunc,anargs,nentries) {

  var iv,i1,i2,i3,igoo,i4,vv;
  var  uu=undefined;


   if (afunc=='_N') {
        if (anargs.length<2) {
           uu=[];
           for (var iv=0;iv<nentries;iv++) uu[iv]=iv;
        } else {
           i1=parseInt(anargs[0]); i2=parseInt(anargs[1]); i3=1 ;
           if (anargs.length>2) i3=parseInt(anargs[2]);
           if (i3<=0) {
                alert('parseMathDo error in _n() : step value <=0 : '+anargs);
                return 0;
            }
           uu=[];
           igoo=-1;
           for (var iv=i1;iv<=i2;iv=iv+i3) {
                 igoo++;
                 uu[igoo]=iv;
           }
        }
   }


   if (afunc=='BY_VALS') {
        if (byInfo.ON==0) {
          alert('BY_VALS() called when a BY is not active ');
          return 0;
        }
        return byInfo.valList;
  }

   if (afunc=='BY_STATS') {
        if (byInfo.ON==0) {
          alert('BY_STATS() called when a BY is not active ');
          return 0;
        }
        return byInfo.statvals;
   }

   if (afunc=='RANDOM') {
      i1=0; i2=1 ; i3=nentries ; i4=0 ;
      if (anargs.length>0)  i1=anargs[0] ; 
      if (anargs.length>1)  i2=anargs[1]; 
      if (anargs.length>2) i3=anargs[2];
      if (anargs.length>3) i4=anargs[3];
      if (isNaN(i1) || isNaN(i2) || isNaN(i3) || isNaN(i4) ) {
         alert('Error in NORMAL: non-numeric argument: '+anargs);
        return uu;
      }
      i1=parseFloat(i1); i2=parseFloat(i2)
      i3=parseInt(i3) ; i4=parseFloat(i4);

      uu=[];

      if (i4 !==0 ) {
             vv=rnduArray(i3,i4);
      } else {
             vv=rnduArray(i3);
      }
      igoo=i2-i1;
      for (var iv=0;iv<i3;iv++) {
          uu[iv]=i1+ (igoo*vv[iv]) ;
      }
  }


   if (afunc=='NORMAL') {
      i1=0; i2=1 ; i3=nentries ; i4=0 ;
      if (anargs.length>0)  i1=anargs[0] ; 
      if (anargs.length>1)  i2=anargs[1]; 
      if (anargs.length>2) i3=anargs[2];
      if (anargs.length>3) i4=anargs[3];
      if (isNaN(i1) || isNaN(i2) || isNaN(i3) || isNaN(i4) ) {
         alert('Error in NORMAL: non-numeric argument: '+anargs);
         return uu;
      }
      uu=[];
      i1=parseFloat(i1); i2=parseFloat(i2)
      i3=parseInt(i3) ; i4=parseFloat(i4);

      if (i4 !==0 ) {
             vv=rndnArray(i3,i4);
      } else {
             vv=rndnArray(i3);
      }

      for (var iv=0;iv<vv.length;iv++) {
          uu[iv]=(vv[iv]*i2)+i1 ;
      }
  }


  return uu ;


}


//==============
// add a record to a data object
// if vname0 contains fewer words (space delimited list) than rows in vv, skip the values in vv
// NOTE: this is a "row concatentate".   VV is is expected to hold the values of serveral variables for one record.
//            To add a column (1 variable for serveral records) rows, see addDO
// Note: if vname0 does NOT exist in the dataobject, a new data is added (with undefineds for records already in the dataobject

function addRecordDOf(vv,vname0) {

var z ;
z=this ;
if (typeof(z)!='object' || typeof(z.nentries)!='number') {
        alert('Error in addRecord: z is not a data object ') ;
        return 0;
}

var n=z.nentries ;
z.nentries=n+1 ;

var varlist=makeVnames(vname0);

var newvs=[];

for (var m=0;m<varlist.length;m++) {
  if (m>=vv.length) break ;
  vname=varlist[m] ;
  if (typeof(z[vname])=='undefined') {
        z[vname]=[]; newvs=newvs.concat(vname)  ;               // note: prior records will have undefined for this variable
   }

  z[vname][n]=vv[m];
}
if (newvs.length>0) {
     z['$varlist']=z['$varlist'].concat(newvs);                // add variable names to internal list
     z['$varlookup']=updateVarLookup(z) ;
}
 
return 1 ;
}

//============== 
// add a constant, to a data object
// vv : the variable(s) to add
// varlist: name  variable, or a space delimited list of names
//  vv should be an array with the same length as the number of names in vname
//                   each row of vv can be constants, numbers, or an array

function addDOcf(vv,vname0,isarray) {
var varlist;
var z ;
z=this ;

var n,i,m,vname,useval,arow,ic,oldc;

if (arguments.length<3) isarray=0;

if (typeof(z)!='object' || typeof(z.nentries)!='number') {
        alert('Error in addConstant: z is not a data object ') ;
        return 0;
}

var varlist=makeVnames(vname0);

if (isarray!==1  && typeof(vv)!=='object') vv=[vv];

for (var i=0;i<varlist.length;i++) {
    vname=varlist[i];
    if (isarray==1) {
        useval=vv;
    } else {
      if (i<vv.length) {
               useval=vv[i] ;
           } else {
                useval=undefined ;
          }
      }
      z[vname]=useval ;
      oldc=0;
      for (var ic=0;ic<z['$constlist'].length;ic++) {
            if (z['$constlist'][ic]==vname) {oldc=1;break;}
      }
      if (oldc==0)   z['$constlist']=z['$constlist'].concat(vname);                // add variable names to internal list

   }

   return 1 ;
}

//====================  z
// remove variables from an array, shrink array
function removeVars(varlist,remlist) {
var ir,itmp,newv,avar,iv;
var robj={};

// one of the list is missing, give up
if (typeof(varlist)=='undefined' || typeof(remlist)=='undefined') return varlist;


for (var ir=0;ir<remlist.length;ir++) robj[remlist[ir]]=1 ;

newv=[];
for (var iv=0;iv<varlist.length;iv++) {
  avar=varlist[iv];
  if (typeof(robj[avar])=='undefined') newv=newv.concat(avar);
}
return newv ;
}



//==========================
// update the parent data object (z1) using the entries stored in the z2 data object
// Note that z2 MUST be a descendant of z1 -- it must have been created by a z1=extractDO(z1,...),
//  or by a sequence of extractDO in which z2 followed z1
//  It can have new variables (they will be added to the z1 variable list)
// Shortcut : if z2 is a string or a number, then set the value of all variables in vlist for all 
//  entries in the data-object, to the value of z2
//  I.e.: z.update(5,'status') will set the z.nentries elements ofthe  z.status array  equal to '5' 
//
// z2: dataObject containing information to update z with. Or a constant
// vlist: list of variables, in z2, to update to z (if not specified, or *, use all variables in z2)
//        this can include dataVariables and constants (either scalarConstants or arrayConstants)
//        If z2 is a constant, vlist MUST be specified
//        If z2 is a number, vlist that does not refer to variables in z are used to create constant
// adef : if a dataVariable is added: value to use for z records if a matching z2 record does not exist
//        if not specified, use undefined
// ahint : short string to use in error messages (i.e.; entryId in z2 not found in z)

function updateDOf(z2,vlist,adef,ahint) {

var v1,v2,jind,avar,ilist,z1row,z2row,i,i2,z2Const=0,iv,vname,avar2,z2id;
var z1Lookup,c1Lookup,iz,iv,vlist,z2VarsLookup;


var z1 ;
z1=this ;


if (arguments.length<3) adef=undefined ;
if (arguments.length<4) ahint='';

if (typeof(z2)=='string' || typeof(z2)=='number') {
   z2Const=1                    // use this constant for all the values 
   if (arguments.length<2) {
        alert('update error: updating with a constant but no variable(s) specified: '+z2);
        return 0;
   }
   vlist=makeVnames(vlist);
} else {
  if (arguments.length<2 || vlist=='*') {                // update using all variables (data or constant) in z2
      vlist=Concat_Arrays(z2['$varlist'],z2['$constlist']); 
  } else {                                        
     vlist=makeVnames(vlist);
  }
}

if (vlist.length==0) {
    alert('update error: empty or missing variable list: '+z2);
    return z1 ;
}



// z2=constant is special. Take care of it here
if (z2Const==1) {
  z1Lookup={};
  for (iv=0;iv<z1['$varlist'].length;iv++) z1Lookup[z1['$varlist'][iv]]=1 ;

  c1Lookup={};
  for (iv=0;iv<z1['$constlist'].length;iv++) c1Lookup[z1['$constlist'][iv]]=1 ;

  for (var iv=0;iv<vlist.length;iv++) {
    avar=vlist[iv];
    if (typeof(z1Lookup[avar])!=='undefined') {          // found a matching variable in z 
      for (var z1row=0; z1row<z1.nentries; z1row++) {              // for every entry in z2
          z1[avar][z1row]=z2;
      }
      continue ;
    }                            // else, treat it as a constant
    z1[avar]=z2 ;
    if (typeof(c1Lookup[avar])=='undefined') z1['$constlist']=z1['$constlist'].concat(avar);   
  }   

  return 1 ;     
}

// if here, z2 is a dataObject
// make lookup table for vlist variables 
z2VarsLookup={};
for (var i2=0;i2<vlist.length;i2++) {       
    avar=vlist[i2] ;
    z2VarsLookup[avar]=1 ;
}


// z['$indices']=ilist ; z['$indices'][uid] yields the row (entry) number associated with uid
// z['$idlist']=idlist ; z['$idlist'][irow]  yields the id associated with row irow

// for every dataVariable in z2, see if it should be used in the update
for (var iv=0;iv<z2['$varlist'].length;iv++) {

  avar2=z2['$varlist'][iv];
  if (typeof(z2VarsLookup[avar2])=='undefined') continue ;     // don't update this variable
  z2VarsLookup[avar2]=2 ;                                       // signal this is being used

  if (typeof(z1[avar2])=='undefined') {              // no such data var in z? Add it
        z1['$varlist']=z1['$varlist'].concat(avar2) ;
        z1[avar2]=[] ;
        for (var iz=0; iz<z1.nentries;iz++) z1[avar2][iz]=adef ;  // initialize this new data variable in z
  }

  if (z2.nentries==0) continue ;                // no records in z2 -- go to next variable

// for this variable, use z2 records to update z records (assuming z2 has records

  ilist=z2['$idlist'];          // for each "entry" in z2, use its "unique id" to find matching "entry" in Z1

  for (var z2row=0; z2row<ilist.length; z2row++) {              // for every entry in z2

    z2id=ilist[z2row];                        // the id of this Z2 entry
    if (typeof(z1['$indices'][z2id])=='undefined') {
        alert('update Error: adding entry #'+z2row+' of '+ilist.length+' (with id='+z2id+') has no match in target object (varlist= '+vlist+'; hint='+ahint);
        return 0;
    }
    z1row=z1['$indices'][z2id];                // the row in z1 for entry with id of z2id
    z1[avar2][z1row]=z2[avar2][z2row];

 }              // for   entries in z2
}               // a dataVariable in z2

// update constants defined in z2

// for every dataVariable in z2, see if it should be used in the update
for (var iv=0;iv<z2['$constlist'].length;iv++) {

  avar2=z2['$constlist'][iv];
  if (typeof(z2VarsLookup[avar2])=='undefined') continue ;     // don't update this variable
  z2VarsLookup[avar2]=3 ;                                       // signal this is being used
//xxx=confirm('const lookup '+avar2); if (!xxx) foobarx();
  if (typeof(z1[avar2])=='undefined') {              // no such constant in z? Add it
//alert('addnewvaarname = '+avar2);
        z1['$constlist']=z1['$constlist'].concat(avar2) ;
  }
  if (typeof(z2[avar2])=='object') {      // arrayConstant?
//alert(' new array');
        z1[avar2]=[] ;
        for (var iz=0;iz<z2[avar2].length;iz++) {
          z1[avar2][iz]=z2[avar2][iz] ;
        }
  } else {
//alert('e new constnt ='+z2[avar2]);

        z1[avar2]=z2[avar2] ;
  }
}                       // constants 



// check for unused vars in vlist (neither constants or datavariables)
avar2='';
for (var iz in z2VarsLookup) {
  if (z2VarsLookup[iz]==1) avar2=avar2+iz+' ';
}
if (avar2!=='') alert('update error: variables to be updated do not exist (in object used to do the updating): '+avar2);

//alert('doneee '+z1['$varlist']);

return 1 ;
}


// hint
//==========================
// update a record in the the z1 data object (DO) using the values in vv
// idVal: Find the (first) record that has an id that matches the value of idVal
// For example, idVal  is the client's ID. And the list of client id's was used as the idArrays in createDO
// vv: an array containing the values to update
// avars: the variables to update (using the values in vv 
//        If avars is not specified, then update the variables in order of appearance (as stored in z['$varlist']
// defval: optional. A value to use if vv is too short.
// idvar: optional. If not specified, use the entry-id. If specified, use the idVar variable (in the dataobject)

function updateDOrecordf(idVal,vv,avars,defval,idvar) {

var z1,vok=0,i,ithdo=-1,avar,cvar,useval,useEid=0 ;
z1=this ;

if (arguments.length<5) useEid=1 ;
if (arguments.length<3) avars=z1['$varlist'];

// some housekeeping: if no $varlookup, create it
z1['$varlookup']=updateVarLookup(z1) ;


// check useEid
if (useEid==0) {                        // a specified variable, or a 0 (signals use of ith entry
   if (strip(idVar)=='0') {
        ithdo=idVal;
   } else {                             // look for match 
       if (typeof(z1['$varlookup'][idVar])=='undefined') return '0 no such idVar ='+idVar ;    
       for (var i=0;i<z1.nentries;i++) {             // search for match
          if (z1[idVar][i]==idVal ) {
            ithdo=i; break;
          }
       }                          // i=0 ..
   }                                // look for match in entries
} else     {                    // look in entry ids 
    if (typeof(z1['$indices'][idVal])!='undefined') ithdo=z1['$indices'][idVal]
}
if (ithdo>=z1.nentries) return '0 idVal too small or too large: '+idVal ;                   // no such record, give up

if (ithdo<0) return '0 no  '+idVar+' with a value of '+idVal;                  // no match, give up

avars=makeVnames(avars);
       
for (var i=0;i<avars.length;i++) {
   cvar=avars[i] ;
   if (typeof(z1['$varlookup'][cvar])=='undefined') continue ;          // skip if no such variable
   useval=defval;
   if (i<vv.length) useval=vv[i]; 
   z1[cvar][ithdo]=useval ;
}
return 1 ;
}


//============
// update varLookup in a dataobject

function updateVarLookup(z1) {
var alook={},i,avar;
for (var i=0;i<z1['$varlist'].length;i++) {
    avar=strip(z1['$varlist'][i]).toUpperCase() ;
    alook[avar]=i
}
return alook ;
}

//==========================
// create a new data object (DO) containing selected tickets from an existing dataObject.
//  Toget is an array that points to which "entries" in the z "data object" to use in the new data object 
//        i.e.; value of 0 means "get first row", 1 "get second row", etc.
//        * means "get all rows" (basically, clone the dataObject, perhaps with not all the variables
//
// vlist: the arrays to extract from. Optional. If not specified, extract all "arrays" added to the data object
// clist: list of constants to copy. * means all. If not specified, copy all, 0 means "copy none"
// Note that the new data object also contains an nentries constant, that is equal to toget.length

function extractDOf(toget,vlist,clist) {

var i,avar,aid,tmp,iz,vname,iv;

var z ;
var newz={},zz={} ; newz.nentries=0;

z=this ;

if (arguments.length<1 || strip(toget)=='*') {
  if (z.nentries==0) {                  // an empty dataobject
      vlist=[];
      newz.entries=0;
   } else {
     toget=[];
     for (var iz=0;iz<z.nentries;iz++) toget[iz]=iz;
   }
} else {
  if (typeof(toget)=='number') toget=[toget] ;                    // expects arrays 
}

if (arguments.length<2) {
     vlist=[];
     if (typeof(z['$varlist'])!='undefined') vlist=z['$varlist'];
} else {
     vlist=makeVnames(vlist);
}

if (arguments.length<3) {
  clist=z['$constlist'];
}
if (clist !== 0) {
    clist=makeVnames(clist);
}    

if (vlist.length==0) {
        alert('extract error: empty or missing variable list');
        return newz ;
}
for (var i=0;i<vlist.length;i++) {
   avar=vlist[i];
   if (typeof(z[avar])=='undefined') continue ;         // skip undefined variables 
   if (z.nentries==0) {
        newz[avar]=[];
   } else {
      newz[avar]=extractRows(z[avar],toget);
   }
}

newz['$varlist']=vlist ;
newz['$varlookup']=updateVarLookup(newz) ;

if (z.nentries>0)  {
   newz.nentries=toget.length;
   tmp=extractRows(z['$idlist'],toget);    

   for (var i=0;i<tmp.length;i++) {
       aid=tmp[i];  
      zz[aid]=i ;
   }
   newz['$indices']=zz ;                  // given a unique id, return the row number
   newz['$idlist']=tmp ;                  // given a row number, return the  id
 }


 newz['$constlist']=[];
 if (clist!==0 && typeof(clist)=='object' && clist.length>0) {
      for (var iv=0;iv<clist.length;iv++) {
          vname=clist[iv];
          newz[vname]=z[vname] ;
         newz['$constlist']=newz['$constlist'].concat(vname);
      }
 }

  newz.get=getDOf ;
  newz.add=addDOf ;
  newz.removeVars=removeVarsDOf ;

  newz.math=mathDOf ;

  newz.addRecord=addRecordDOf ;
  newz.addConstant=addDOcf ;

  newz.update=updateDOf ;
  newz.extractID=extractDOidf ;
  newz.updateRecord=updateDOrecordf ;

  newz.display=displayDOf;

  newz.extract=extractDOf ;
  newz.extractRule=extractDOrulef ;
  newz.combine=combineDOf ;
  newz.save=saveDOf ;


return newz ;

} 

// extractR
//========================== 
// create a new data object (DO) containing selected tickets from an existing dataObject.
//  idlist points to the entry-ids of the  "entries" in the z "data object" to use in the new data object
// vlist: the arrays to extract from. Optional. If not specified, extract all "arrays" added to the data object
// Note that the new data object also contains an nentries constant, that is equal to toget.length

function extractDOidf(idlist,vlist,clist) {

var z,toget=[];
z=this ;

if (typeof(idlist)!=='object') idlist=[idlist] ;                    // expects arrays 

var newz={},zz={} ;
var i,avar,aid,tmp;

if (arguments.length<2) {
        vlist=[];
        if (typeof(z['$varlist'])!='undefined') vlist=z['$varlist'];
} else {
  vlist=makeVnames(vlist);
}

if (arguments.length<3) {
  clist=z['$constlist'];
}
if (clist !== 0) clist=makeVnames(clist);


if (vlist.length==0) {
        alert('extract error: empty or missing variable list');
        return newz ;
}

// convert list of entryIds to list of row numbers
for (i=0;i<idlist.length;i++) {
   if (typeof(z['$indices'][idlist[i]])=='undefined') continue ;                // no such entry-id for this dataobject
   toget[toget.length]=z['$indices'][idlist[i]] ;
}

for (var i=0;i<vlist.length;i++) {
   avar=vlist[i] ;
   if (typeof(z[avar])=='undefined') continue ;         // skip undefined variables 
   newz[avar]=extractRows(z[avar],toget);
}

newz.nentries=toget.length;
newz['$varlist']=vlist ;
newz['$varlookup']=0 ;
tmp=extractRows(z['$idlist'],toget);    

for (var i=0;i<tmp.length;i++) {
   aid=tmp[i];  
   zz[aid]=i ;
}
newz['$indices']=zz ;                  // given a unique id, return the row number
newz['$idlist']=tmp ;                  // given a row number, return the  id

 if (clist!==0 && clist.length>0) {
      newz['$constlist']=[];
      for (var iv=0;iv<clist.length;iv++) {
          vname=clist[iv];
          newz[vname]=z[vname] ;
          newz['$constlist']=newz['$constlist'].concat(vname);
      }
 }

  newz.get=getDOf ;
  newz.add=addDOf ;
  newz.removeVars=removeVarsDOf ;

  newz.math=mathDOf ;

  newz.addRecord=addRecordDOf ;
  newz.addConstant=addDOcf ;

  newz.update=updateDOf ;
  newz.extractID=extractDOidf ;
  newz.updateRecord=updateDOrecordf ;

  newz.display=displayDOf;

  newz.extract=extractDOf ;
  newz.extractRule=extractDOrulef ;
  newz.combine=combineDOf ;
  newz.save=saveDOf ;

return newz ;

} 


//==========================
// create a new data object (DO) containing selected tickets from an existing dataObject.
//  vv: an nentries lengthed array (typically a data-array from the data object) to examine
// arule: a simple rule used to examine vv: LARGE SMALL EQUAL5
// aparam: depends on the arule
//    large: pull out aparam largest (aparam=1 if not specified). Return in descending order
//    small: pull out aparam smallest (aparam=1 if not specified). Return in ascending order
//    nth  : pull out the entry with the nth largest value of aparam (where 0 means "smallest")
//    eq: pull out all entries where vv == aparam
//   ge le lt gt ne : similar to eq, but using ge, etc.
// vlist: the arrays to extract from. Optional. If not specified, extract all "arrays" added to the data object
// clist: constants to copy
// Note that the new data object also contains an nentries constant

function extractDOrulef(vv,arule,aparam,vlist,clist) {

var z ;
z=this ;

arule=strip(arule).toUpperCase() ;

if (arguments.length<2) {
   alert('error in extractRule: no rule specified ');
   return 0;
}

if (arguments.length<3) aparam='';

var newz={},zz={} ;newz.nentries=0;

var i,avar,aid,tmp;
var ilist,glist,i1,i2 ;

if (arguments.length<4) {
        vlist=[];
        if (typeof(z['$varlist'])!='undefined') vlist=z['$varlist'];
} else {
       vlist=makeVnames(vlist);
}

if (arguments.length<5) {
  clist=z['$constlist'];
}

if (typeof(clist)!=='undefined' && clist !== 0) {
   clist=makeVnames(clist);
}

if (vlist.length==0) {
        alert('extractRrule error: empty or missing variable list');
        return newz ;
}

// use arule, vv, and aparam to create a glist
if (arule.substr(0,5)=='LARGE') {
    ilist=indexSort(vv);
    if (aparam=='') aparam=1 ;
    i2=vv.length-1 ;

    i1=vv.length-aparam ;
    if (i1>i2) i1=i2 ;
    if (i1<0) i1=0 ;

    glist=[];
    for (var i=i2; i>=i1; i--) glist=glist.concat(ilist[i]);
}       
if (arule.substr(0,5)=='SMALL') {
    ilist=indexSort(vv);
    if (aparam=='') aparam=1 ;
    if (aparam>vv.length) aparam=vv.length ;
    if (aparam<1) aparam=1 ;
    glist=[];
    for (var i=0; i<aparam;i++) glist=glist.concat(ilist[i]);
}
if (arule.substr(0,2)=='EQ') {
   glist=selectRows(vv,'$0 eq '+aparam,vv,2);
}
if (arule.substr(0,2)=='GE') {
   glist=selectRows(vv,'$0 ge '+aparam,vv,2);
}
if (arule.substr(0,2)=='GT') {
   glist=selectRows(vv,'$0 gt '+aparam,vv,2);
}
if (arule.substr(0,2)=='LE') {
   glist=selectRows(vv,'$0 le '+aparam,vv,2);
}
if (arule.substr(0,2)=='LT') {
   glist=selectRows(vv,'$0 lt '+aparam,vv,2);
}
if (arule.substr(0,2)=='NE') {
   glist=selectRows(vv,'$0 ne '+aparam,vv,2);
}
if (arule.substr(0,5)=='NTH') {
    ilist=indexSort(vv);
    if (aparam=='') aparam=0 ;
    if (aparam>=vv.length) aparam=vv.length
    if (aparam<0) aparam=0 ;
    glist=ilist[aparam];
}

// now call extract  with constucted glist (glist might be [], which means newz is empty)

newz=z.extract(glist,vlist,clist);

return newz ;

}
 

// ======================== constlist
// combine data objects
function combineDOf(z2,defval,vlist,ikeep) {
var z1,vlist1,vlist2,z1list,z2list,z1indices,z2indices,i,m,vlistuse,irznew,vlistuse,v1,tmp;
var allzs,avar,iz,z1id,iv,z2id,clistuse ;
var irz0,irznew ;

z1=this ;

var znew={} ;

// assume use all vars (remove at end of process)

 vlistuse=Concat_Arrays(z1['$varlist'],z2['$varlist']);
 vlistuse=makeVnames(vlistuse);

znew['$varlist']=vlistuse ;                     // variables in new data-object

z1list=z1['$idlist'];                         //  list of entryIds in z1
z2list=z2['$idlist'];                         //  list of entryIds in z2

z1indices=z1['$indices'];                         //  lookuptable-object that connects a Z1 entryID to its entryrow
z2indices=z2['$indices'];                         //  lookuptable-object that connects a Z1 entryID to its entryrow

tmp=z1list;
tmp=uniqueArray(tmp.concat(z2list));

znew.nentries=tmp.length  ;         // number of entries in newz will be sum of z1 and z2

// first: initalize all the data-arrays in znew (to the defval)
for (var iv=0;iv<vlistuse.length;iv++) {
  avar=vlistuse[iv];
  znew[avar]=initArray(znew.nentries,defval);
}

// first, add values from Z1 entries to znew. 
znew['$indices']={} ;
znew['$idlist']=[] ;

for (var i=0;i<z1list.length;i++) {
    z1id=z1list[i];
    znew['$indices'][z1id]=i;
    znew['$idlist'][i]=z1id ;
    if (typeof(z2indices[z1id])=='undefined') {                 // this entryID is unique to Z1
        for (var iv=0;iv<vlistuse.length;iv++) {
           avar=vlistuse[iv];
           if (typeof(z1[avar])=='undefined')  continue ;            // no such variable in z1. Skip (use the defval)
           znew[avar][i]=z1[avar][i]   ;                        // note that order in z1 matches order in znew
        }
    } else {                           // this entryID is shared by both z1 and z2. Use z2 values if the exist, otherwise use z1 values (see below)
        for (var iv=0;iv<vlistuse.length;iv++) {
           avar=vlistuse[iv];
           if (typeof(z2[avar])=='undefined') {                 // no z2 variable, use z1 variable (if one exists)
              if (typeof(z1[avar])!='undefined')  znew[avar][i]=z1[avar][i]   ;             // no z2 variable, use z1 variable (if one exists)
           }                                            // else use Z2 value (see below)
        }                                       // all vars in znew
    }                                           // entry in z2 also
}                                               // all entries in z1

// now add the z2 entries (either as new entries or as overwrites

for (var i=0;i<z2list.length;i++) {
    z2id=z2list[i];
    if (typeof(znew['$indices'][z2id])!='undefined') {                 // this entryID is NOT unique to Z2 (so update
           irz0=znew['$indices'][z2id];
           for (var iv=0;iv<vlistuse.length;iv++) {
              avar=vlistuse[iv];
              if (typeof(z2[avar])!='undefined') {                 // got z2 variable, so use it (possibly overwrite Z1 value
                 znew[avar][irz0]=z2[avar][i]   ;               
              }
           }
   } else {                                             // new entry, add row to znew
       irznew=znew['$idlist'].length;
       znew['$indices'][z2id]=irznew;
       znew['$idlist'][irznew]=z2id ;
       for (var iv=0;iv<vlistuse.length;iv++) {
           avar=vlistuse[iv] ; 
           if (typeof(z2[avar])!='undefined') {                 // got z2 variable, so use it
               znew[avar][irznew]=z2[avar][i]   ;               // add to end of znew 
           }                                    // var exists in z2
       }                                        // for all variables in znew
   }                                            // new entry
}                                       // entries in z2


znew['$varlookup']=updateVarLookup(znew) ;

clistuse=Concat_Arrays(z1['$constlist'],z2['$constlist']);
clistuse=makeVnames(clistuse);

znew['$constlist']=[];
for (var iz=0;iz<clistuse.length;iz++) {
  znew['$constlist']=znew['$constlist'].concat(clistuse[iz]);
  znew[clistuse[iz]]=defval ;
}

for (var iz=0;iz<z1['$constlist'].length;iz++) {
     avar=z1['$constlist'][iz];
     znew[avar]=z1[avar];
}
for (var iz=0;iz<z2['$constlist'].length;iz++) {
        avar=z2['$constlist'][iz];
        znew[avar]=z2[avar];
}

  znew.get=getDOf ;
  znew.add=addDOf ;
  znew.removeVars=removeVarsDOf ;

  znew.math=mathDOf ;

  znew.addRecord=addRecordDOf ;
  znew.addConstant=addDOcf ;
  znew.update=updateDOf ;
  znew.extractID=extractDOidf ;
  znew.updateRecord=updateDOrecordf ;
  znew.display=displayDOf;
  znew.extract=extractDOf ;
  znew.extractRule=extractDOrulef ;
  znew.combine=combineDOf ;
  znew.save=saveDOf ;



// now remove vars?

  if (arguments.length>2) {
     vlist1=makeVnames(vlist);
     if (vlist1.length>0) 
     if (arguments.length<4 || ikeep!==1) {
         znew.removeVars(vlist1,1);
     } else {
        znew.removeVars(vlist1,0);      // keep
     }
  }

return znew;
}


//=========== vlist extract entries
// z['$indices']=ilist ;                  // (object) given a unique id, return the row number
// z['$idlist']=idlist ;                  // (array) given a row number, return the  id

// combine data objects
function xxcombineDOf(z2,defval,vlist) {

var z1,vlist1,vlist2,z1list,z2list,z1indices,z2indices,i,m,vlistuse,irznew;
var allzs,avar,iz;
var vLookup,avar,ii,iv;

z1=this ;

var znew={} ;

if (arguments.length<3) {                               // no vlist, then concatenate varlists in old and new
   vlist1=[]; vlist2=[];
   vlistuse=Concat_Arrays(z1['$varlist'],z2['$varlist']);
} else {                                        
  vlistuse=vlist;
}

vlistuse=makeVnames(vlistuse);

vLookup={};
for (var iv=0;iv<vlistuse.length;iv++) vLookup[vlistuse[iv]]=1 ;

ii=0;
znew['$varlist']=[];                            // create varlist for new dataObject
for (var iv=0;iv<z1['$varlist'].length;iv++) {
  avar=z1['$varlist'][iv];
  if (typeof(vLookup[avar])=='undefined') continue ;    // skip
  znew['$varlist'][ii]=avar;
  ii++;
}
for (var iv=0;iv<z2['$varlist'].length;iv++) {
  avar=z2['$varlist'][iv];
  if (typeof(vLookup[avar])=='undefined') continue ;    // skip
  znew['$varlist'][ii]=avar;
  ii++;
}
znew['$varlookup']=updateVarLookup(znew) ;


z1list=z1['$idlist'];                         //  list of entryIds in z1
z2list=z2['$idlist'];                         //  list of entryIds in z2

z1indices=z1['$indices'];                         //  lookuptable-object that connects a Z1 entryID to its entryrow
z2indices=z2['$indices'];                         //  lookuptable-object that connects a Z1 entryID to its entryrow

tmp=z1list;
tmp=uniqueArray(tmp.concat(z2list));

znew.nentries=tmp.length  ;         // number of entries in newz will be sum of z1 and z2

// first: initalize all the data-arrays in znew (to the defval)
for (var iv=0;iv<vlistuse.length;iv++) {
  avar=vlistuse[iv];
  znew[avar]=initArray(znew.nentries,defval);
}

// first, add values from Z1 entries to znew. 
znew['$indices']={} ;
znew['$idlist']=[] ;

for (var i=0;i<z1list.length;i++) {
    z1id=z1list[i];
    znew['$indices'][z1id]=i;
    znew['$idlist'][i]=z1id ;
    if (typeof(z2indices[z1id])=='undefined') {                 // this entryID is unique to Z1
        for (var iv=0;iv<vlistuse.length;iv++) {
           avar=vlistuse[iv];
           if (typeof(z1[avar])=='undefined')  continue ;            // no such variable in z1. Skip (use the defval)
           znew[avar][i]=z1[avar][i]   ;                        // note that order in z1 matches order in znew
        }
    } else {                           // this entryID is shared by both z1 and z2. Use z2 values if the exist, otherwise use z1 values (see below)
        for (var iv=0;iv<vlistuse.length;iv++) {
           avar=vlistuse[iv];
           if (typeof(z2[avar])=='undefined') {                 // no z2 variable, use z1 variable (if one exists)
              if (typeof(z1[avar])!='undefined')  znew[avar][i]=z1[avar][i]   ;             // no z2 variable, use z1 variable (if one exists)
           }                                            // else use Z2 value (see below)
        }                                       // all vars in znew
    }                                           // entry in z2 also
}                                               // all entries in z1

// now add the z2 entries (either as new entries or as overwrites

for (var i=0;i<z2list.length;i++) {
    z2id=z2list[i];
    if (typeof(znew['$indices'][z2id])!='undefined') {                 // this entryID is NOT unique to Z2 (so update
           irz0=znew['$indices'][z2id];
           for (var iv=0;iv<vlistuse.length;iv++) {
              avar=vlistuse[iv];
              if (typeof(z2[avar])!='undefined') {                 // got z2 variable, so use it (possibly overwrite Z1 value
                 znew[avar][irz0]=z2[avar][i]   ;               
              }
           }
   } else {                                             // new entry, add row to znew
       irznew=znew['$idlist'].length;
       znew['$indices'][z2id]=irznew;
       znew['$idlist'][irznew]=z2id ;
       for (var iv=0;iv<vlistuse.length;iv++) {
           avar=vlistuse[iv] ; 
           if (typeof(z2[avar])!='undefined') {                 // got z2 variable, so use it
               znew[avar][irznew]=z2[avar][i]   ;               // add to end of znew 
           }                                    // var exists in z2
       }                                        // for all variables in znew
   }                                            // new entry
}                                       // entries in z2

// now add constants from z1 and z2 (z2 will overwrite z1)
  allzs={};  
  for (var iz=0;iz<z1['$constlist'].length;iz++) {
        avar=z1['$constlist'][iz];
        znew[avar]=z1[avar];
        allzs[avar]=1;
  }
  for (var iz=0;iz<z2['$constlist'].length;iz++) {
        avar=z2['$constlist'][iz];
        znew[avar]=z2[avar];
        allzs[avar]=1;
  }
  znew['$constlist']=[];
  for (var iz in allzs) znew['$constlist']=znew['$constlist'].concat(iz);


  znew.get=getDOf ;
  znew.add=addDOf ;
  znew.removeVars=removeVarsDOf ;

  znew.math=mathDOf ;

  znew.addRecord=addRecordDOf ;
  znew.addConstant=addDOcf ;
  znew.update=updateDOf ;
  znew.extractID=extractDOidf ;
  znew.updateRecord=updateDOrecordf ;
  znew.display=displayDOf;
  znew.extract=extractDOf ;
  znew.extractRule=extractDOrulef ;
  znew.combine=combineDOf ;
  znew.save=saveDOf ;


return znew;
}

//===================
// return  contents of a data-object in a table
// all parameters are optional
// avarlist: If specified, a space delimited list of variables (in z) to display. If '*', or if not specified, all variables
//           If 0, do NOT display client variables
// constlist: if 0, do NOT display "constants". If '*', or if not specified, display all constants
// dec2: if 1, round values to 2 decimal points. 
// colwidth: if specified, maximum column width 

function displayDOf(avarlist,constlist,dec2,colwidth) {
var stuff,arow,cmatch,aid,ii,i,goo,iv;  
var avar;
var bgcolors=["lime","cyan"];
var ibg=0;
var z=this ;

if (arguments.length<1) avarlist=z['$varlist'] ;
if (arguments.length<3) dec2=0
if (arguments.length<4) colwidth=0
if (colwidth==1) colwidth=8                  ;          // common error

if (avarlist=='*')  avarlist=z['$varlist'] ;
if (arguments.length<2) constlist=z['$constlist'] ;
if (constlist=='*')  constlist=z['$constlist'] ;


if (strip(avarlist)==='0') {
   avarlist=[];
} else {
   avarlist=makeVnames(avarlist);
}
if (strip(constlist)==='0') {
   constlist=[];
} else {
   if (typeof(constlist)=='string') constlist=getWord(constlist);
}

//alert(typeof(z['BID'])+' displaydo varlist: '+avarlist);

//z['$varlookup']=updateVarLookup(z) ;


stuff='';

if (avarlist.length>0) {
  stuff=stuff+'<table border="1" >';
  arow='<tr bgcolor="#ababab"><th>#</th>';
  for (var i=0;i<avarlist.length;i++) {
    avar=avarlist[i];

    if (typeof(z['$varlookup'][avar])=='undefined') {
        arow=arow+'<th>badVar='+avar+'</th>' ;
     } else{
        avar=fixColWidth(avar,colwidth);
        arow=arow+'<th>'+avar+'</th>' ;
     }
  }        
  arow=arow+'</tr> \n ';
  stuff=stuff+arow ;

  for (var i=0;i<z.nentries;i++) {                         // extract from bigv, then reorder by client (make 1 or more rows per client)
    ibg=1-ibg;
    arow='<tr bgcolor="'+bgcolors[ibg]+'"><td><tt>'+i+'</tt></th>';
    for (var iv=0;iv<avarlist.length;iv++) {
       avar=avarlist[iv];
       if (typeof(z['$varlookup'][avar])=='undefined') {
          arow=arow+'<td></td>' ;
       } else {
//if (typeof(z[avar])=='undefined') alert('noo avar '+avar);
//if (typeof(z[avar][i])=='undefined') alert('noo avar i '+avar+','+i);
          vval=z[avar][i];
          if (dec2==1) vval=fix2(vval,vval);
           vval=fixColWidth(vval,colwidth);
          arow=arow+'<td>'+vval+'</td>' ;
       }
    }
    arow=arow+'</tr> \n ';
    stuff=stuff+arow ;
  }
  stuff=stuff+'</table> \n ';
}

if (arguments.length>1 && typeof(constlist)!=='undefined' && constlist.length>0) {
  stuff=stuff+'<p/><table border="1"> ';
  for (var i=0;i<constlist.length;i++) {
    avar=constlist[i];
    useval='<tt>undefined<tt>'
    if (typeof(z[avar])!='undefined') {
         useval=z[avar];
         if (typeof(useval)!=='object') {
             if (dec2==1) useval=fix2(useval,useval);
         } else {
             for (var ii=0;ii<useval.length;ii++) useval[ii]=fix2(useval[ii],useval[ii]);
             useval='['+useval.join(' , ')+']'
         }
    }
    arow='<tr><th>'+avar+'</th><td>'+useval+'</td></tr> \n ';
    stuff=stuff+arow;
  }
  stuff=stuff+'</table> \n ';
}

return stuff ;


}                       // showSubmitArrays


//======== fix column width (add <br> if necessary)
function fixColWidth(avar,colw) {
 var vv,im=0,m;
 avar=''+avar;
  if (colw==0) return avar ;
  vv='';
 for (m=0;m<avar.length;m++) {
    vv=vv+avar[m];
    im++ ;
    if (im>=colw)  {
        vv=vv+'<br/>';
        im=0;
    }
  }
  return vv;
}
    


//=========
// save dataObject to global storage
function saveDOf(aname) {
aname=strip(aname);
dataObjects[aname]=this;
return 1 ;
}


//==========================
// two decimal, if exists. else, ''
function fix2(aval,dval) {
if (arguments.length<2) dval='';
 if (typeof(aval)=='undefined') return dval ;
 if (isNaN(aval) || aval==='') return dval ;
 aval=aval*1;
 aval=aval.toFixed(2) ;
 return aval;
}


//=========
// retrieve dataObject from global storage. This is an independent function (not a dataObject method), so no f suffix
function retrieveDO(aname,exists) {
var z={} ;
aname=strip(aname);

if (arguments.length<2) exists=0;
if (exists==1) {
  if (typeof(dataObjects[aname])!='undefined') return 1;
  return 0
}
z=dataObjects[aname] ; 
return z ;
}

//==============================
// random-number-object (seedable random numbers) functions

//====================
// Create a seeded random-number-object, with a starting seed and bounds.
// Once created, dsvyRandom(..) can be used to generate a random value "from a sequence" 
// This means that different random-number-objects (say, ones created at completely
//  different times) will return the same sequence of values -- so long as the seed value (and bounds)
//  are the same!
//
// Syntax:
//         dsvyRandomCreate('varname',seedValue,min,max,idec,iprec,nretain)
//
//  where:
//
//      varname : the name of this random-number-object (it is used by dsvyRandom)
//                    varname will be capitalized, with leading/trailing spaces stripped
//       seed: the seed  (default value is 0= which means current time (so basically it's a random value))
//       amin: minimum value  (default value is 0.0)
//       amax: maximum value  (default value is 1.0)
//       idec: # of  decimalDigits
//              if not specified: -1
//       iprec: precision (round to be a multiple of this value, can be a decimal value less than 1)
//              if not specified: 0 (do NOT modify)
//          Example of multiple values (multiple of 0.05, 2 decimal digits):  idec=2,iprec=0.05
//       nretain: how many prior values to retain. If not specified, 0. If '*', infinite
//                  See dsvyRandom for hints on how to use nretain
//
//   Note: if a 'varname' random-number-object was previously defined, 
//          the above will just reset its seed,  min,max, idec , and nretain: 
//          Note that prior retained values are NOT removed, and the "counter" is NOT reset
//          Also, if a '' value is used for one of the arguments, NO reset occurs of that variable
//
// Technical notes:
//    * dsvyRandomCreate (and dsvyRandom) uses a _dsvyRandoms global object 
//      (it will be created if it doesn't exist)
//   * The makeURandom(seedValue) function is used to create objects,
//      with the following additional fields added (with default values as noted):
//           minVal      : default=0.0   
//           maxVal     : default=1.0 
//           decVal      :  default=-1                -1 means "use max precision"
//           precVal     : default=0                   0 means "no precision control"
//           nretain      : default=0                   0 means "do not retain prior values"
//      and a few used internally
//           nct=0 ;                               tracks how many values have been created 
//           retains=[];                           stores "retained" values
//
//    *  If an existing 'varname' is reset, future values generated from this object may 
//       have different characteristics that previously generate values.
//       However:  the "retained" values will not change (which means that a value outside of the
//       current bounds can be returned if nth < current_counter is requested)
//
// Example: create a random number with seed of 100, that returns full precision values
//             between 0.0 and 1.0
//  dsvyRandomCreate('myRand',100) 
// ...
//  v1=dsvyRandom('myRand')
// ..
//  v2=dsvyRandom('myRand')
//
// everytime this is run, the same values of v1 and v2 will be created
//
// Shortcut:
//     dsvyRandomCreate()  will create a random-number-object with the name $DEFAULT, using
//     the default values for min=0.0, max=1.0, maximum precision, 'random' starting seed , and no
//     retained variables. In other words, pretty much the same thing as using Math.random()


function dsvyRandomCreate(varname,seedValue,amin,amax,idec,iprec,nretain) {
var dd,stuff,vv,a1,a2 ,dw,idec,iprec;
var last10,ivv,im;

if (typeof(_dsvyRandoms)=='undefined') _dsvyRandoms={};         // define a global storage object

if (arguments.length<1) { 
   varname='$default';
} else {
  varname=strip(varname).toUpperCase() ;
}

if (varname=='*')  {          // display list of current varnames
        stuff='Random-Variable-Objects defined<p/> '+
              '<table><tr><th>Name</th><th>seed</th><th>min</th><th>max</th><th>resolution <br/> precision</th><th># retained</th><th>ct</th><th>last 10</th> ';
        last10='';
        for (var vv in _dsvyRandoms) {
        
           goo=_dsvyRandoms[vv];
           if (goo.retains.length>0) {
             for (var im=1;im<11;im++) {
                 ivv=goo.retains.length-im ;
                 if (ivv>=0) { 
                       last10=goo.retains[ivv]+' '+last10;  
                 }
             }
          }
          last10=' ['+goo.retains.length+'] ' +last10;  
           
           stuff=stuff+'<tr> '+
              ' <th>'+vv+'</th>'+
              ' <td>'+goo.seed+'</td><td>'+goo.minVal+'</td><td>'+goo.maxVal+'</td>' +
              ' <td>'+goo.decVal+','+goo.precVal+'</td><td>'+goo.nretain+'</td><th>'+goo.nct+'</th><td><tt>'+last10+'</tt></td> </tr>';
        }
        stuff=stuff+'</table>';
          var dw=window.open('','debugwindow');
          dw.document.open('text/html');
          var t1='<html><head><title>random-number-objects currently defined</title><head> '+
                  '<body>'+stuff+'</body></html>';
          dw.document.write(t1);
          dw.document.close();
              
        return 0;
   }


// create a new random-number-object
  if (typeof(_dsvyRandoms[varname])!=='object') {
    if (arguments.length<2 || seedValue<=0 || strip(seedValue)==='') {
         dd=new Date();
         seedValue=dd.valueOf() ;
     }

     _dsvyRandoms[varname]=new makeURandom(seedValue);   
     _dsvyRandoms[varname].nct=0 ;                 // internally used 
     _dsvyRandoms[varname].retains=[] ;            // internally used (not subject to reset)
     if (arguments.length<3 || strip(amin)==='' ) amin=0.0;
     if (arguments.length<4 || strip(amax)==='' ) amax=1.0;
     if (arguments.length<5 || strip(idec)==='')  idec=-1 ;
     if (arguments.length<6 || strip(iprec)==='')  iprec=0 ;
     if (arguments.length<7 || strip(nretain)==='') nretain=0;
     if (nretain=='*') nretain=Math.pow(2,32) ;
  } else {                              // exists: reset parameters?

     if (arguments.length>1  && strip(seedValue)!=='') {
        if (seedValue<=0 ) {
            dd=new Date();
            seedValue=dd.valueOf() ;
        }

        _dsvyRandoms[varname]=new makeURandom(seedValue);   
        _dsvyRandoms[varname].nct=0 ;                 // internally used      
        _dsvyRandoms[varname].retains=[] ;            // internally used (not subject to reset)
     }
     if  (arguments.length<3) amin='';
     if  (arguments.length<4) amax='';

     if (arguments.length<5 || strip(idec)==='')  idec=-1 ;
     if (arguments.length<6 || strip(iprec)==='')  iprec=0 ;
     if (arguments.length<7 || strip(nretain)==='') nretain=0;

  }
//alert('xxx '+amin+','+amax+','+idec+','+iprec);
// set (or change) additional attributes
 if (amin!=='')  _dsvyRandoms[varname].minVal=amin ;
 if (amax!=='')   _dsvyRandoms[varname].maxVal=amax ;
 if (idec!=='')   _dsvyRandoms[varname].decVal=idec ;
 if (idec!=='')   _dsvyRandoms[varname].precVal=iprec ;

 if (nretain!=='') {
    if (nretain=='*') nretain=Math.pow(2,32) ;
    _dsvyRandoms[varname].nretain=nretain ;
  }

  return 1 ;
  

}


// ==================================================== 
// Generate a random value from the "varname" random-number-object
// This uses objects created by dsvyRandomCreate
//
// Syntax:
//    aval=dsvyRandom('varname',nth,useSeed);
// where:
//    'varname' : name of the random-number-object (as created by dsvyRandomCreate)
//                  if no such object has been created, an error message is displayed
//    nth: optional (typically not used). 
//              If not used, or if 0, returns the next value in this random-number-object's  "sequence"
//              If specified, than return the 'nth' value; where nth=1 means the first value in the sequence
//  useSeed: optional. If 0, generate a pure random number  -- NOT one from the sequence
//              this value is otherwise treated the same (it is stored, subject to bounds, etc
//              Note that the sequence is paused -- the next call with useSeed !=0 (or useSeed not
//              specified) will generate the next number in the sequence (assuming nth=0)
//          Special values:
//                * nth='N' means "return nct (the number of values generated using this random-number-object)
//                * nth='*' : return a vector containing all the retained values (if any)
//                              
// More on nth
//    nth allows you to "look forward" or (if nretain was specified in dsvyRandomCreate) look backward.
//    This makes it easier to keep sets of random-number-objects synchronized, so that for any
//    given value of nth one can get the save vector of random values (assuming the set of random-
//    number-objects are always created with the same seeds)
//       Examples:
//               * if 6 values have been generated, and nth=8:
//                       a) a value is generated, and stored (as the 7th value)
//                       b) another value is generated, stored (as the 8th value), and returned
//               * if 6 values have been generated, and nth=3:
//                   a) If retain is greater then 3, then the 3rd value is returned
//                   b) if retain ls less than or equal to 3, an error message is shown and 0 is returned
//                         (since the oldest value is for nct=4)
//
//       Thus: retain should be large enough to accomodate the largest "look back" (the largest
//               difference between the current number of values generated and the nth value
//               requested.
//
//  Shortcut: dsvyRandom() will return a value from the '$default' random-number-object
//              (assuming you created it with dsvyRandomCreate()
//

function dsvyRandom(varname,nth,useseed) {

var amin,amax,idec,nretain,iprec,irandom ;

if (typeof(_dsvyRandoms)=='undefined') {
  alert('Error in dsvyRandom: no _dsvyRandoms global variable');
  return 0;
}

if (arguments.length<0) {
   varname='$default';
} else {
  varname=strip(varname).toUpperCase() ;
}

if (typeof(_dsvyRandoms[varname])=='undefined') {
  alert('Error in dsvyRandom: the '+varname+'  random-number-object was not created');
  return 0;
}

nct=_dsvyRandoms[varname].nct;
amin=_dsvyRandoms[varname].minVal ;
amax= _dsvyRandoms[varname].maxVal ;
idec= _dsvyRandoms[varname].decVal ;
iprec= _dsvyRandoms[varname].precVal ;

nretain= _dsvyRandoms[varname].nretain ;

if (arguments.length<2)  nth=0;
if (arguments.length<3)  useseed=1 ;

irandom=0;
if (useseed==0) irandom=1 ;

// special value: return # nct (number of values generated so far)
if (strip(nth).toUpperCase()=='N') return nct ;
if (strip(nth).toUpperCase()=='*') return _dsvyRandoms[varname].retains ;

if (isNaN(nth)) {
  alert('dsvyRandom error: nth is not a number: '+nth);
 return 0;
}

nth=parseInt(nth);

// use prior?
if (nth>0 &&  nth<=nct ) {
  if (nretain==0) {
        alert('Warning in dsvyRandom: prior value specified, but no values are retained');
        return 0;
  }
  ilen=_dsvyRandoms[varname].retains.length ;
  i1=1+nct-ilen ;               // count of oldest retained value
  if (nth>=i1) {
        i2=nth-i1 ;
        aval=_dsvyRandoms[varname].retains[i2];
        return aval ;
   } else {
        alert('Warning in dsvyRandom: prior value ('+nth+') specified, but too old (oldest is '+i1+')' +nct);
        return 0 ;
   }
}

//  scoot forward?
if (nth>0 &&  nth>(nct+1) ) {
  for (var m=nct+1;m<nth;m++) { 
     aval= _dsvyRandoms[varname].next(amin,amax,idec,iprec,irandom) ;   // generate a random number!
    _dsvyRandoms[varname].nct++ ;
    if (nretain>0) {
        _dsvyRandoms[varname].retains.push(aval);   // push to end
        if (_dsvyRandoms[varname].retains.length>nretain)  _dsvyRandoms[varname].retains.shift(); // pop from beginning
     }
  }
}  


// if here, get next value and return it
aval= _dsvyRandoms[varname].next(amin,amax,idec,iprec,irandom) ;   // generate a random number!
_dsvyRandoms[varname].nct++ ;
if (nretain>0) {
        _dsvyRandoms[varname].retains.push(aval);   // push to end
        if (_dsvyRandoms[varname].retains.length>nretain)  _dsvyRandoms[varname].retains.shift(); // pop from beginning
}

return aval;

}
  

//=================
// Create a seeded random-number-object.  Optional argument (typicall specified) is the starting seed
//  The sequence of number created after instantiating a makeURandom object, with a given seed,
//  is always the same. Thus,random-number-objects created with the same  seedValue.
//  will generate the same sequence of random values
//
// Usage:
//  myRand=new makeURandom(seedValue);
//  val1=myRand.next(min,max,idec,around)
// ...
//  val2=myRand.next(min,max,idec,around);
//
// where:
//   min: minimum value of random number  (default=0.0)
//   max: maximum value (default = 1.0)
//   idec: digits after decimal point (0 = return an integer) (-1=the default=maximum precision)
//   around: precision -- return value that is a multiple of this (around can be a fraction, i.e.; 0.02)
//                         (default=0, do NOT round)
//   irandom: optional overide. If 1, generate a pure random number (that is, suspend the sequence)
//
//  Thus: if no arguments, return a value between 0.0 and 1.0 (using maximum precision, no rounding)
//
// Notes:
//    * If seed not specified, use current date (basically, the same as Math.random()
//    * To reset the seed, use myRand.seed=avalue;
//    * dsvyRandomCreate starts with a makeURandom object, and adds fields


function makeURandom(aseed) {
var dd;
if (arguments.length<1) {
  dd=new Date();
  aseed=dd.valueOf();
}
this.seed=aseed;

this.next =function(lower,upper,idec,iprec,irandom) {
  var maxi,num,range,itmp;
  if (arguments.length<1) lower=0.0;
  if (arguments.length<2) upper=1.0;
  if (arguments.length<3) idec=-1 ;
  if (arguments.length<4) iprec=0 ;
  if (arguments.length<5) irandom=0 ;

  if (strip(irandom)!=='1') { 
     maxi = Math.pow(2,32);
     this.seed = (134775813 * (this.seed + 1)) % maxi;
     num = (this.seed) / maxi;
  } else {
        num=Math.random();
  }
  range = upper - lower;
  num *= range;
  num += lower;

   if (iprec>0) {
      itmp=num/iprec;  itmp=itmp.toFixed(0);
      num=itmp*iprec;
   }
   if (idec>0)  num=num.toFixed(idec);

   return num ;


}               // end of .next function

}               // end of makeURandom




