/////// Distributions -- analyze various types. ////////
// Fill an array with many=100 random values from 0 to range=100
//      each representing a height from 1.0 to 3.0 meters
// Display the  array horizontally.
// Generate a distribution table (histogram), y=height, x=0-100% 
// Flip the table, and display it with y=%, x=height
// (Event-driven:  pause until next event occurs.)
//
// VERSION 2:  Table extensible to the right.
// VERSION 3:  Say object (to avoid generics that trouble Open).
Say say=  new Say();


String title=  "DISTRIBUTIONS:  ", subtitle="";
String author="bam@professorBAM.com";
String version=  "9809k ~";
String hints="", news=".";
int innum=0, outnum=0;
String inroot=  "in", intype=".txt", infile= inroot+innum+intype;
String outroot="out", outtype=".txt", outfile= outroot+outnum+outtype;
//
int wide=800, high=500, ww=wide, hh=high;
int left=10, bottom=400, xHints=ww-222, xHist=left+405, yHist=bottom;
int columns=10, blocks=100;                          // (Number of columns in display of array.)
int framecount=0, r=0;
boolean debug=false, help=false, pause=false, sorted=false, done=false, display=false;

final int range=100;                 // Integer values in range 0-100,
int many=100;
int a[] = new int[many];             // Array of integer values (0-range)
int aa[] = new int[many];            // Sorted array.
int d[] = new int[range+1];          // Histograms:    d[i] contains count for a[i]; i is a[i];
int dd[] = new int[range+1];
//
int type=0;
String typename="";
float spread=5, sd=5;
float m1, m2;                        // mean & median values
int m3, m4;                          // mode index.
int h1, h2, h3, h4;                  // mean, median, mode -- scaled heights
//
//
// Heights (from 1m to 3m) are represented by a[] values (from 1 to range), in 200 steps of 1cm.
int step=200/range;                  // Scaled values (heights) from 100 to 300 cm
int lo=100, hi= lo + range*step;     // To get scaled height, multiply value by step then add lo. 
int mid= (hi + lo) /2; 
//
color RED=color(255, 0, 0), GREEN=color(0, 255, 0), BLUE=color(0, 0, 255), 
  YELLOW=color(255, 255, 0), CYAN=color(0, 255, 255), MAGENTA=color(255, 0, 255), 
  ORANGE=color(255, 127, 0), DARK=color(0, 150, 0), DIM=color(150);
char lastKey=' ';
char sigchar='\u03C3';
char klist[]= { 
  '\'', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')'
};

void setup() {
  size(800,500);
  //--  start(0);
  reset();
  hints=  gethints();
}

void draw() {
  if (pause) {  return;  }
  background(255, 225, 225);
  titles();
  if (help) { showHelp(20, 80); return;  }
  if (display) { disp( 20, 80 ); return;  }
  //
  ++framecount;
  showall();
}
void showall() {
  if (sorted) show( left, bottom, aa, many );    // Display sorted or unsorted values
  else        show( left, bottom, a, many );
  showhist( xHist, yHist, d, range+1 );          // Display the histogram
  showflip( yHist-25, xHist+30, d, range+1 );    // Flip the histogram
  pause=true;                                    // (Don't call draw() next event). 
}

// Display array values as vertical bars at (xx,yy)
void show( int xx, int yy, int a[], int many ) {
  int x=xx, y=yy;
  if (debug) {  
    ellipse(xx, yy, 10, 10);
  }

  for (int i=0; i<many; ++i) {
    int h=  lo + step*a[i];           // Height= 100cm + 20*value
    fill(200, 200, 255);
    rect( x+1, y, 2, -lo );            // DISPLAY THE BLUE BAR (with red head)
    fill(0, 0, 255);
    rect( x+1, y-lo, 2, lo-h );
    fill(255, 0, 0);
    ellipse( x+2, y-h, 5, 5 );
    //--  if (i%10<1) text( a[i], x+2, y-h-30 );    // show every 10th value
    if (i%10<1 || i==many-1) {                                   // show every 10th height & value
      if (sorted) {
        stroke(ORANGE);
        fill(RED);
        line( x+2, y, x+2, y+8 );
        text( a[i], x, y+15 );
      }
      fill(DARK);
      line( x+2, y-h, x+2, y-h-10 );
      if (i<many-1) text( h, x+2, y-h-10 );
      stroke(0);
    }
    x += 4;
    // Space the bars 4 pixels apart.
  }
  if (! sorted) {
    fill(YELLOW);
    rect(xx-5, yy+24, 248, 28 );
    say.cpxy( RED, 12, "Press the 's' key, to display sorted array.", xx, yy+40 );  
    say.cpxy( RED, 8, "(or click below)", xx, yy+50 );
  } else {
    say.cpxy( BLUE, 8, "Press the 's' key for UN-sorted array\n(or click below).", xx, yy+40 );
  }
}
// Display histogram as horizontal bars at (xx,yy)
void showhist( int xx, int yy, int d[], int range ) {
  int x=xx, y=yy, bar=10, group=10, gtotal=0;
  int count, sum=0, sumpct=0;
  float pct;
  int leftbar=x+70, leftpct=x+350;
  if (debug) {  
    ellipse(xx, yy, 10, 10);
  }
  //
  say.cpxy( DARK, 10, "  cm", x, y-lo+5*step );
  say.cpxy( 0, 8, "[Group\ncounts]", x+52, y-hi-28);
  //
  for (int i=0; i<range; ++i) {
    int h=  lo + step*i;            // Height= 100cm + 10*i
    count=  d[i];                   // How many instances of this value?
    sum += count;
    gtotal += count;
    fill(MAGENTA);
    rect( leftbar, y-h-3, bar*count, 2 );        // DRAW THE BAR //
    // Sum groups of ten counts
    //--  if (i%group < group-1) break;
    if (i%group == 0) {
      //--  text( h+"cm", x, y-h );
      say.cpxy( DARK, 10, ""+h, x, y-h );
      say.cpxy( RED, 12, ""+i, x+25, y-h );
      if (i>0) {
        say.cpxy( 0, 10, "["+gtotal+"]", x+50, y-h );
        say.xy( "["+gtotal+"]", leftbar+30+bar*(gtotal/5), y-h );
      }
      //
      pct= 100 * float(gtotal) / float(many);
      sumpct += pct;
      say.cpxy( BLUE, 12, ""+int(pct), leftpct, y-h );
      gtotal=0;
    }
  }// END OF LOOP //
  say.cpxy( 0, 8, ""+sum, x+50, y-85);
  say.cpxy( BLUE,0, sumpct+"%", leftpct, y-85);
}

// Display histogram as vertical bars at (xx,yy)
void showflip( int xx, int yy, int d[], int range ) {
  //--  int x=xx, y=yy+range*step, bottom=x;
  int x=xx+20, y=yy;
  int count;
  float pct;
  float next=y, spacing=3;
  if (debug) {  
    ellipse(xx, yy, 10, 10);
  }
  //
  for (int i=0; i<range; ++i) {
    count=  d[i];                    // How many instances of this value?
     next= y + i*spacing;
    fill(0);
    //--  rect( next, bottom, 1, -count*10 );        // Vertical bar!
    rect( next, x, 0.1, -count*20 );                  // VERTICAL BAR //
    if (i%5<1 || i>=range-1) {
      fill(RED); 
      textSize(10);
      text( i, next, x+10 );
      /*
       fill(DARK); 
       textSize(10);
       text( h, next-5, x+20 );
       */
      //--  text( count, next, x+20 );
      fill(0);
      pct= 100 * float(count) / float(many);
      say.cpxy( BLUE, 8, ""+int(pct), next+2, x+20 );
    }
  }
  say.cpxy( 0, 8, "%", next+15, x+20 );
  /*
  textSize(8);
   fill(0);
   say.cpxy( 0, 8, "count:", y, x+30 );
   fill(BLUE);
   text( "  %", y, x+40 );
   */
}
//// END OF SHOW METHODS ////


// Build the histogram //
void histogram( int a[], int many, int d[], int range ) {
  for (int i=0; i<range; ++i) { 
    d[i]=0;
  }
  // Bump correct bin for each height 
  for (int i=0; i<many; ++i) {
    int j= a[i];
    j=  j<0 ? 0 : j ;
    j=  j<range ? j : range-1 ;
    ++d[j];
  }
}

void bigsort( int a[], int many ) {
  // Sort int array (moving biggest to end, then shrinking array)
  for (int m=many; m>1; --m) {
    int w=  wherebig( a, m );     // find biggest
    swap( a, w, m-1 );            // move it to end
    // Shrink array until only one
  }
}
int wherebig( int a[], int m ) {
  // Return index of (first) biggest
  int w=0;                     // assume big is first
  for (int i=1; i<m; ++i) {    // Start with next element of array
    if (a[i] > a[w]) w=i;      // Found bigger
  }
  return w;                    // Return index of biggest
}
int wherebiglast( int a[], int m ) {
  // Return index of (last) biggest
  int w=0;                     // assume big is first
  for (int i=1; i<m; ++i) {    // Start with next element of array
    if (a[i] >= a[w]) w=i;      // Found bigger (or equal)
  }
  return w;                    // Return index of (last) biggest
}
void swap( int a[], int j, int k ) {
  // Swap a[j] with a[k]
  int tmp=a[j];
  a[j]=  a[k];
  a[k]=  tmp;
}
void copy( int a[], int aa[], int many) {
  for (int i=0; i<many; ++i) {
    aa[i] = a[i];
  }
}  




////////  DISTRIBUTIONS ////////
String[] dtypes= {  
  "Uniform", "Gaussian", "Bimodal", "Expo"
    , "Sinusoidal", "Cosine"
    , "Poisson", "Weird"
    , "Input from file ", "File name change:  ", "Output to file "
};
int n=0, U=n++, G=n++, B=n++, E=n++, S=n++, C=n++, P=n++, W=n++, I=n++, O=n++, F=n++, ntypes=n;
int types=dtypes.length;
void reset() {
  bug( "reset() was called " +r++ );
  type=  type%types;
  typename=  dtypes[type];
  subtitle=  typename;  
  if (type>0) subtitle += "  (\u03C3 = " + spread + ")"; 
  // 
  if (type==0) uniform( a, many, 0, range );
  else if (type==1) gaussian( spread, a, many, 0, range );
  else if (type==2) bimodal( spread, a, many, 0, range );
  else if (type==3) expo( a, many, 0, range );
  else if (type==4) sine( a, many, 0, range );
  else if (type==5) cosine( a, many, 0, range );
  else if (type==6) poisson( a, many, 0, range );
  else if (type==7) weird( a, many, 0, range );
  else if (type==8) {  
    subtitle=  typename + infile; 
    inarray( infile );
  }
  // Not a reset type; no analysis needed. ..
  else {
    if (type==9)  {  newin();  }                                    // CHANGE FILE NAME //
    else if (type==10) {  outarray( outfile, a, many, 0, range );   }    // OUTPUT //
    else {  
      typename="ERROR  "; subtitle=  typename + infile;  
    }
    return;
  }
  // Analyze & generate the histogram (unless I/O type);
  analyze();
}
void analyze() {
  // ANALYZE the distribution. //
  histogram( a, many, d, range );          // Generate histogram d from a
  copy( a, aa, many );
  bigsort( aa, many );
  histogram( aa, many, dd, range );         // Generate histogram d from sorted array b
  // (Should be identical.  Check this?)
  m1=  mean( a, many );
  m2=  median( aa, many );
  m3=  wherebig( d, range );              // Index of biggest count (first)
  m4=  wherebiglast( d, range );
  sd=  stdev( m1, a, many );
  h1=  int( step*(lo+m1) );                // Height= 100cm + 10*a[i]
  h2=  int( step*(lo+m2) );
  h3=  int( step*(lo+m3) );
  h4=  int( step*(lo+m4) );        //?????
}
// Change distribution type, then reset.
void reset1( int t ) { 
  type=t; 
  reset();
}
// Change distribution type & sigma, then reset.
void reset2( int t, float sp ) { 
  type=t; 
  spread=sp;
  reset();
}
// Change values, then analyze (w/o reset)
void down() {
  for (int i=0; i<many; ++i) {
    a[i] = range - a[i];
  }
  analyze();
  pause=false;
}  


// AVERAGES, ETC.
float mean( int a[], int many ) {
  float total=0;
  for (int i=0; i<many; ++i) {
    total += a[i];
  }
  return total / many;
}
float median( int aa[], int many ) {
  if (many%2 > 1) return aa[ many / 2 ];
  else return ( aa[many/2] + aa[1+many/2] ) / 2;
}
float stdev( float m1, int a[], int many ) {
  double dev, sum, sig;
  float result;
  sum=0;
  for (int i=0; i<many; ++i) {
    dev=  a[i] - m1;
    sum += dev*dev;              // Sum the squares of the deviation
  }
  sig=  Math.sqrt(sum) / many;  // (should be many-1 for sampling.
  result=  (float) sig;
  return result;
}

//// FILL ARRAY WITH DIFFERENT DISTRIBUTIONS ////
// Fill int array with random values, from lo to hi.
void uniform( int a[], int many, int lo, int hi ) {
  bug( typename );
  for (int i=0; i<many; ++i) {
    a[i]=  int( random(lo, hi) );
  }
}
void gaussian( float spread, int a[], int many, int lo, int hi ) {
  float g;
  bug( typename + spread );
  for (int i=0; i<many; ++i) {
    g=  randomGaussian();
    a[i]=  (hi-lo)/2 + int( g * spread);
  }
}
void bimodal( float spread, int a[], int many, int lo, int hi ) {
  int middle=many/2;
  float gg;
  bug( typename + spread );
  for (int i=0; i<middle; ++i) {
    gg=  randomGaussian();
    a[i]=  (hi-lo)/4 + int( gg * spread);
  }
  for (int i=middle; i<many; ++i) {
    gg=  randomGaussian();
    a[i]=  (hi-lo)*3/4 + int( gg * spread);
  }
}
void poisson( int a[], int many, int lo, int hi ) {
  // STUB +++ Poisson //
  bug( typename );
  for (int i=0; i<many; ++i) {
    //--  a[i]=  lo + int( float(poi(1) *(hi-lo)) / spread )/2;
    float plus=  float(poi(1) *(hi-lo)) ;
    if (i%10 < 1) bug( ""+plus );
    //--  a[i]=  lo + int( float(poi(1) *(hi-lo)) / spread )/2;
    a[i]=  lo +  (int) (plus / spread) /2;
  }
}
static final int poi(final double lambda) {
  final double L = Math.exp(-lambda);
  double p = 1;
  int k = -1;

  do ++k; 
  while ( (p *= Math.random ()) > L);
  return k;
}
void expo( int a[], int many, int lo, int hi ) {
  // STUB +++ Square //
  bug( typename + spread );
  for (int i=0; i<many; ++i) {
    double power=  spread;
    double q=  int( random(lo, hi) );
    double qq=  Math.pow(q, power), rr=  Math.pow(range, power), fraction= qq / rr;            
    double value=  range * fraction;
    int v=  (int) value;
    // Fraction of the maximum rr;
    a[i]=  v;      // (Should be 1..20);
  }
}
void weird( int a[], int many, int lo, int hi ) {
  float g;
  bug( typename + spread );
  for (int i=0; i<many; ++i) {
    g=  randomGaussian();
    if (g>0) a[i]=  lo + int( g * spread);
    else     a[i]=  hi + int( g * spread);
  }
}
void sine( int a[], int many, int lo, int hi ) {
  float angle, value, mid=  (hi+lo)/2;
  angle=  2 * PI / many;
  bug( typename+ "(i*" +angle+ "*" +int(spread)+ " )" );
  for (int i=0; i<many; ++i) {
    value=  sin( i * angle * spread );        // frequency of sine wave.
    a[i]=  int(  mid + value*range/2 );
  }
}
void cosine( int a[], int many, int lo, int hi ) {
  float angle, value, mid=  (hi+lo)/2;
  angle=  2 * PI / many;
  bug( typename+ "( i * " +angle+ " * " +spread+ " )" );
  for (int i=0; i<many; ++i) {
    value=  cos( i * angle * spread );        // frequency of sine wave.
    a[i]=  int(  mid + value*range/2 );
  }
}


// I/O //
void inarray( String infile ) {
  // INPUT values into the array a[] //
  //// NOTE:  a[] must be GLOBAL -- not a pass-by value argument! ////
  bug( "**** in(" +infile + "):  Input from file "+infile );
  news=  "Input from file "+infile;
  String[] s = loadStrings( infile );    // Input new values.
  if (s == null ) {
    news += "\nERROR:  The file " +infile+ "\n    is missing or inaccessible!";
    return;
  }
  int n = s.length;
  news +=  "\nDONE:  " +n+ " values input.";
  if (n<1) {  
    news+=  "\n    EMPTY INPUT FILE:  " +infile; 
    return;
  }
  // Use length of input file as new array size [many] 
  many=  n;
  newsize( many );
  // Copy new values into a[]
  for (int i=0; i<many-5; ++i) {
    a[i]=  int( s[i] );
  }
}
void newin() {
  ++innum;
  infile= inroot+innum+intype;
  news=  "Next input file is:  "+infile;
  subtitle=  typename + infile; 
}
// Output the array //
int outarray( String f, int a[], int many, float spread, int range ) {
  news +=  "Output to file " +f;
  subtitle=  typename + f;
  int lines=0;
  PrintWriter output;
  // Create a new file in the sketch directory
  output = createWriter( f ); 
  output.println( "// Writing " +many+ " values from a[]" );
  bug( "\n" +typename +f+ "  " +sigchar+ ":  " +spread+ ", range: 0-" +range);

  int i=0;
  for (i=0; i<many; ++i) {
    output.println( a[i] );    // Write the next value.
  }
  lines=i-1;

  output.flush();  // Writes the remaining data to the file
  output.close();  // Finishes the file
  ++outnum;        // Change file name for next output   
  outfile= outroot+outnum+outtype;
  return lines;
}



//// EVENT HANDLERS ////
void mousePressed() {
  pause=false;
  help=false;
  display=false;
  news=":";
  if (debug) news=  "CLICK:  (" +mouseX+ "," +mouseY+ ")";
  if (help) return;
  if (mouseY<30) reset1( ++type ); 
  if (mouseY>bottom+20 && mouseX<250) sorted = ! sorted;
  if (mouseX<xHist && mouseY<bottom-20 && mouseY>bottom-250 ) { 
    reset(); 
    news="reset";
  }
  // Same as +/- keys
  if (mouseX>xHist && mouseY<height-30 && mouseY>height-80 ) { 
    reset2(type, ++spread);
  }
  if (mouseX>xHist && mouseY>height-30 ) {
    spread=  reduce( spread );
    reset2(type, spread);
  }
}
float reduce( float spread) {
    if (spread>1) --spread;
    else if (spread>0.1) {  int n; n=  int( spread*10 ) - 1; spread = float( n ) / 10;  }
    else spread *= 0.9;
    return spread;
}
  
void keyPressed() {
  pause=false;
  help=false;
  display=false;
  lastKey=key;
  news=""+key;
  if (debug) news=  "KEY:  " +key+ "\n";
  //
  if (key == 'q') exit();
  else if (key == 'r') { reset(); news="reset";  }
  else if (key == '~') { down();  news="v = 1 - v";  } 
  else if (key == 's') { sorted = ! sorted; } 
  else if (key == '?') { help = true; } 
  else if (key == 'd') { debug = ! debug; } 
  else if (key == 'p') { pause = ! pause; }
  //
  else if (key == 'U') { reset1(U); }        // Uniform distribution.
  else if (key == 'G') { reset2(G, 5); }     // Gaussian (Normal)
  else if (key == 'N') { reset2(G, 5); } 
  else if (key == 'B') { reset2(B, 5); }     // Bimodal
  else if (key == 'E') { reset2(E, 5); }     // Exponential
  else if (key == 'S') { reset1(S);  }       // Sine
  else if (key == 'C') { reset1(C);  }       // Cosine
  else if (key == 'P') { reset2(P, 5); }     // Poisson
  else if (key == 'W') { reset2(W, 5); }     // Weird
  else if (key == 'D') { reset1( ++type ); } // Next type of distribution.

  // SIGMA //
  else if (key=='0') { reset2(type, 10); }
  else if (key>'0' && key<='9') {  reset2(type, key-'0');  }
  // +/- to increase/decrease spread (sigma)
  else if (key == '+') { reset2(type, ++spread);  } 
  else if (key == '=') { reset2(type, ++spread);  } 
  else if (key == '-') { reset2(type, spread>1 ? spread-1 : spread*0.9 );  } 
  else if (key == '_') { reset2(type, spread>1 ? spread-1 : spread*0.9 );  }
  //
  else if (key == '<') { reset2(type, spread>0 ? spread-1 : 1);  } 
  else if (key == '>') { reset2(type, spread+1);  } 
  else if (key == ',') { reset2(type, spread*10);  } 
  else if (key == '.') { reset2(type, spread/10);  }
  //+++ ? change definition of '_' (or '=' ???

  else if (key == '~') { down();  }

  // RESIZE ARRAYS //  
  else if (key == '[') {  many--;      resize();  }
  else if (key == '{') {  many -= 10;  resize();  } 
  else if (key == ']') {  many++;      resize();  } 
  else if (key == '}') {  many += 10;  resize();  }
  else if (key == '|') {  many += 100;  resize(); display=true; }
  else if (key == ':') {  many += 500;  resize(); display=true; }
  else if (key == '\\') { 
    many = 100;  
    resize();    
    news += "\n    surface.setSize(  800, 500 );";
    surface.setSize( 800, 500 );
    xHist=400;
  } 


  // I/O //
  else if (key == 'I' || key == 'i') {  reset1(8);  }    // INPUT
  else if (key == '#' || key == '^') {  newin();  }     // New input file
  else if (key == 'O' || key == 'o') {  reset1(10);  }   // OUTPUT

  else if (key == 'f') {
    String s[]=  loadStrings( "namefile" );
    bug( s[0] );
    if (s.length == 1 && s[0].length() >0) inroot=  s[0];
  }

  else if (key == 'a' || key == 'A') {  display=true; }    // Display the arrays.
      // Display a or aa on the screen (in pause mode)
  
  // Change window size???
  else if (key == '$') {  size( 1000, 700); }
  else if (key == '%') {  size( 1200, 800); }
  else if (key == '&') {  frame.setSize( 400, 300 ); }
  else if (key == '*') {  frame.setSize( 900, 500 ); }
 
  else if (key == ' ') { return; }                          // Ignore blank.
  else { news=  "Unrecognized key:  " + key;  }
  // +++ ?use select-case ?
}
void disp( int xx, int yy ) {
  int xxaa=xx+25*columns, xxdd=50*columns;
  dispArray( a, many, xx, yy, "____Contents of a[" +many+ "]:----" );
  dispArray( aa, many, xxaa, yy, "____Sorted array aa[" +many+ "]:----" );
  dispArray( d, range, xxdd, yy+130, "____Histogram array d[" +range+ "]:----" );
}
void dispArray( int a[], int many, int x, int y, String header ) {
  say.cpxy( BLUE, 12, header, x, y );
  int cols=  many>200 ? columns : 10;                 // (Histogram remains 10 per row)
  String s=  "";
  String padding= many<5000 ? "  " : " " ;
  for (int i=0; i<many; ++i) { 
    if (a[i]>9)    s +=  a[i] +padding;
    else           s +=  "  "+a[i] +padding;          // Pad before single-digit value.
    if (i%cols == cols-1) { 
       say.next(s);                                   // Display the row
       s="";
    }
    if (i%blocks == blocks-1) { say.half(); }         // Extra half line, every 100th item
  }
}


// Change array size. //
void newsize( int m ) {
  many=  m;
  news += "\nNew size for a[ " +many +" ]";
  a = new int[many];               // Array of integer values (0-range)
  aa = new int[many];              // Sorted array.
}
void resize() {
  many=  many>3 ? many : 3;
  news += "\nResizing a[ " +many +" ]";
  a = new int[many];               // Array of integer values (0-range)
  aa = new int[many];              // Sorted array.
  d=  new int[range+1];            // Histograms:    d[i] contains count for a[i]; i is a[i];
  dd = new int[range+1];
  reset();
  int hi=high;
  if (many>100) {
    xHist = left + many*4;
    wide=  xHist+400;
    // Increase screen size, if necessary.  (More columns for display.) 
    if (many>200) { high=600; columns=20; blocks=columns*10;  }
    if (many>400) { high=700; columns=30; blocks=columns*10;  }
    if (many>800) { high=800;columns=40; blocks=columns*10;  }
    if (many>1500) { high=900; columns=50; blocks=columns*20;  }
    if (many>2500) { high=1000; columns=60; blocks=columns*20;  }
    if (many>4000) { high=1000; columns=100; blocks=columns*20;  }
    if (many>6000) { high=1000; columns=200; blocks=columns*20;  }
    if (many>14500) { high=1000; columns=200; blocks=columns*20;  }
    news += "\n    surface.setSize( " +wide+ ", " +high+ " )";
    if (high != hi) println( "[" +wide+ ", " +high+ "]  many=" +many );     // print changes
    surface.setSize(  wide, high );
  }
  else { columns=10; blocks=100; wide=800; high=500; }                      // Normal size.
}


void showHelp( int x, int y ) {
  say.cpxy( BLUE, 12, "Random integers from " +0+ " to " +range+ " are generated in array a[]", x, y );     
  say.next( "    (representing heights ranging from 100 to 300 centimeters)." );     
  say.next("");
  say.cpnext( BLUE, 0, "Array is displayed horizontally." );
  say.next( "Histogram (distribution table) is displayed vertically:  y=height, x=counters & %" );
  say.next( "Also, histogram is flipped, and displayed nbelow, with y=%, x=height"  );
  //--  say( "Display the array horizontally." );
  //--  say( "Generate histogram (distribution table):  y=height, x=0-100%" );
  //--  say( "Flip the table, and display it with y=%, x=height"  );

  say.half();
  say.cpnext( BLUE, 0, "To reset the array to new random values, click on the display (or press 'r' key)." );
  say.next( "To display sorted array instead, click the yellow box  (or Press 's' key)." );
  fill(color(0, 100, 0));
  say.next( "" );
  //
  float ySave=  say.yy;
  say.cpnext( DARK, 14, "Distribution types:" );
  say.half();
  say.pnext( 14, "    Uniform" );
  say.next( "    Gaussian (Normal) -- depends upon " +sigchar+ "." );
  for (int i=2; i<dtypes.length-2; ++i) {        // Omit F & O types
    say.next( "    "+dtypes[i] );
  }
/*
  say.next( "    Bimodal" );
  say.next( "    Exponential" );
  say.next( "    Sinusoidal" );
  say.next( "    Cosine" );
  say.next( "    Poisson" );
  say.next( "    Weird" );
  say.next( "    Input" );
*/
  say.next( "" );
  say.pnext( 12, "First letter of name is the key to press for each distribution," );    
  say.next( "To change DISTRIBUTION type, click on the title or press 'D' key." );
  say.next( "Press 'I' key to input values from a file named " +infile );

  say.half();
  say.cpnext( MAGENTA, 12, "Press '#' key to change input file name." );
  say.next( "Press 'o' key to output the values to a file named " +outfile );
  say.next( "Press 'a' key to display the contents of the arrays." );

  say.cpxy( RED, 18, "\n\nPress any other key, to resume.", 20, height-30  );


  say.xx=400;
  say.yy=ySave;
  say.cpnext( 0, 12, "NOTE:  Array size is nominally 100, but may be changed:" ); 
  say.next( "    [ or ]  decrease/increase array size by one" ); 
  say.next( "    { or }  increase/increase array size by ten" ); 
  say.next( "    \\       restore array size [100]" ); 

  say.next( "" );
  say.next( "" );

  say.cpnext( RED, 0, sigchar+" is selected by digit keys:  1, 2, 3, 4, 5, ... \n" );
  //--  say( "    0 for 10" ); 
  say.next( "    +  increase "+sigchar+ " by one" ); 
  say.next( "    -  decrease " +sigchar+ " by 1 (if >1, else by 0.1)" ); 
  say.half();
  say.cpnext( 0, 0, "Press '~' key replace each a[i] with 1-a[i]" ); 

  showColors( 666, 250 );
}
void showColors( int x, int y ) {
  say.xx=x;
  say.yy=y;
  textSize(12);
  fill(RED); 
  sayColor(RED, "RED");
  sayColor(GREEN, "GREEN");
  sayColor(BLUE, "BLUE");
  sayColor(YELLOW, "YELLOW");
  sayColor(CYAN, "CYAN");
  sayColor(MAGENTA, "MAGENTA");  
  sayColor(ORANGE, "ORANGE");
  sayColor(DARK, "DARK");
  sayColor(DIM, "DIM");
}
String gethints() {
  String s="";
  s += "?  for help, p to pause, q to quit\n";
  s += "r  to reset array (random values)\n";
  s += "s  to display sorted array (or click below)\n";
  s += "D  for next DISTRIBUTION type (or click title)\n";
  s += "+/-  to increase/decrease  \u03C3\n";
  return s;
}
void titles() {
  String s="";
  say.cpxy( 0, 24, title + subtitle, ww/3, 20 );
  if ( many==100) say.cpxy( RED, 14, " Values range from 0 to " +range, ww/3, 40 );
  else say.cpxy( RED, 14, many+ " Values range from 0 to " +range, ww/3, 40 );
  say.cpxy( DARK, 10, "(representing heights from " +lo+ " to " +hi+ " cm)", ww/3, 56 );
  say.cpxy( 0, 0, hints, xHints, 35 );
  //
  say.cpxy( 100, 10, author, 10, height-20 );
  say.cpxy( color(150), 10, "©2019  "+version, 10, height-10 );
  s=  "[" +wide+ "x" +high+ "]  (" +columns+ ")" ;
  say.cpxy( 150, 9, s, 150, height-8 );
  say.cpxy( DIM, 10, ""+framecount, 10, 10 );
  say.cpxy( DIM, 10, ""+lastKey, wide-10, 10 );
  say.cpxy( MAGENTA, 14, news, 10, 30 );
  if (debug) say.cpxy( MAGENTA, 14, "[DEBUG MODE]", 80, 15 );

  //--  say.cpxy( 0, 10, "type=" +type+ ":  " +dtypes[type], xHist+50, height-15 );
  say.cpxy( 0, 10, "type=" +type+ ":  " +typename, xHist+50, height-15 );
  say.cpxy( 0, 12, sigchar+" (spread) = "+spread, xHist+50, height-30 );
  say.cpxy( 0, 20, "+", xHist+240, height-40 );
  say.cpxy( 0, 20, "-", xHist+240, height-20 );
  line( xHist+240, height-35, xHist+360, height-35 ); 
  say.cpxy( DARK, 12, "(increase)", xHist+265, height-40 );
  say.cpxy( RED, 12, "(decrease)", xHist+265, height-20 );
  if (help) return;

  // Show averages, etc.
  int x=260, y=height-66;
  say.cpxy( 0, 12, " mean:\n median:\n mode:\n +/-  \u03C3:", x, y );    
  say.cpxy( DARK, 12, h1+ "\n" +h2+ "\n" +h3+ "\n" +h4, x+55, y );  
  say.cpxy( RED, 10, m1+ "\n" +m2+ "\n" +m3+ "\n" +sd, x+88, y+4 );
  if (m4 != m3) {
    say.cpxy( RED, 10, ""+m4, x+105, y+37 );
    say.cpxy( DARK, 12, ""+h4, x+125, y+37 );  
    say.cpxy( 0, 6, ""+ m3+ ", " +m4, x+160, y+37 );
  }
}

void sayColor( int c, String s ) {
  say.cpnext( c, 0, s+ ":  "+ c );
}

void bug( String s) { 
  if (!debug) return;
  news += s;
  println( s );
}

class Say {
  String ss;
  color cc=color(0,0,0);
  int pp=12;                // textSize (pixels)
  float xx=10, yy=10;       // Save (x,y)
  // METHODS //
  void xy( String s, float x, float y ) {  
    text( ss=s, xx=x, yy=y ); 
  }
  void next( String s ) {
    text( ss=s, xx, yy += pp );  
  }
  void cpxy( color c, int p, String s, float x, float y ) { 
    fill(cc=c); 
    if (p>0) textSize(pp=p); 
    xy( s, x, y );  
  }
  void cpnext( color c, int p, String s ) {  
    fill(cc=c); 
    if (p>0) textSize(pp=p); 
    next( s );  
  }
  void pxy( int p, String s, float x, float y ) { cpxy( cc, p, s, x, y );  }
  void pnext( int p, String s ) {  cpnext( cc, p, s );  }
  //
  void half() { yy += pp/2; }      // Skip half-line
}
