/*
 * File:    WaterRetention.cpp
 * Author:  S Harry White
 * Created: 2009-11-25
 * Updated: 2020-01-12: Add option to color ponds by size in the Units square.
 *              -01-21: Remove unwanted border around the Cells and Units tables row.
 *                      Reduce the html style when option multicolor is not chosen.
 * Updated: 2020-10-04: Randomly sort colors.
 *                      Fix crash on "not magic" message in inputRectangle.
 * Updated: 2020-10-11
 *   Change color scheme choices to: mono, multi, random_ mono, random_ multi
 * Updated: 2020-11-02
 *    Adjust size limit.
 * Updated 2022-02-04
 *   Tidy code.
 *  Updated: 2023-02-07
 *    Change to compile with g++ for macOS.
 */

//#include <ctype.h>
#include <curses.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
//#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#define max(a,b) (((a)>(b))?(a):(b))
const bool F=false, T=true;
bool multicolor, *numUsed=NULL; // To check that numbers 1 to RC are in a magic rectangle.
const int mono=1, multi=2, rmono=3, rmulti=4, startSize=25,
          maxRC=1000000; // Some browsers can't handle big rectangle files.
int R, C, // The order of the rectangle.
    N,    // R==C? R : sqrt(RC)
    Rm1,  // R-1
    Cm1,  // C-1
    RC,   // R*C
    maxNumber,       // Biggest number in the rectangle.
    inputRectNumber, // Counts rectangles input from a .txt file.
    fileRectNumber,  // Counts rectangles for the current output file.
    outputFileNumber,
    rectanglesPerFile, allocatedQueueSize=0, qIndex=0,
    **xRectangle=NULL, **water=NULL, **pond=NULL, *pondSize, allocatedR=0, allocatedC=0;
typedef unsigned int Uint;
Uint rowConstant, // C*(RC+1)/2
     colConstant; // R*(RC+1)/2 for 1 base

struct t_Cell { int priority, row, col; }; t_Cell *Queue=NULL;

const int numColors=36; int colorNum[numColors], monoColor;
const char *color[numColors]={
  "cl0","cl1","cl2","cl3","cl4","cl5","cl6","cl7",
  "cl8","cl9","clA","clB","clC","clD","clE","clF",
  "clG","clH","clI","clJ","clK","clL","clM","clN",
  "clO","clP","clQ","clR","clS","clT","clU","clV",
  "clW","clX","clY","clZ"
},
     *schemeName[]={ NULL, "mono", "multi", "random_ mono", "random_ multi" };
//================================================= store =================================================

const char *storeAllocFail="Storage allocation failed"; bool bell=T;
bool reportError(const char *msg) {
//   -----------
  printf("%sError: %s.\n", bell ? "\a\n" : "",  msg); return bell=F;
} // reportError

void freeRectangle(int*** rectangle, const int size) {
//   -------------
  if (*rectangle!=NULL) {
    for(int i=0; i<size; i++) free((*rectangle)[i]); free(*rectangle); *rectangle=NULL;
  }
} // freeRectangle

void freeUsed() { if (numUsed!=NULL) { free(numUsed); numUsed=NULL; } }
//   --------

void freeQueue() { if (Queue!=NULL) { free(Queue); Queue=NULL; allocatedQueueSize=0; } }
//   ---------

void freeStore() {
//   ---------
  freeRectangle(&pond, allocatedR); freeRectangle(&water, allocatedR);
  freeRectangle(&xRectangle, allocatedR); if (pondSize!=NULL) { free(pondSize); pondSize=NULL; }
  allocatedR=0; allocatedC=0; freeUsed(); freeQueue();
} // freeStore

bool allocateRectangle(int*** rectangle, const int nR, const int nC) {
//   -----------------
  bool ok; *rectangle=(int**) malloc(nR*sizeof(int*)); ok=(*rectangle!=NULL);
  if (ok) {
    int numAllocated=0;
    for(int i=0; i<nR; i++) {
      int* p=(int*) malloc(nC*sizeof(int)); (*rectangle)[i]=p; if (p==NULL) { numAllocated=i; ok=F; break; }
    }
    if (!ok) freeRectangle(rectangle, numAllocated);
  }
  return ok;
} // allocateRectangle 

bool allocateUsed(const int size) { return ((numUsed=(bool*) malloc(size*sizeof(bool)))!=NULL); }
//   ------------

bool allocateQueue(const int size) {
//   -------------
  bool ok=T;
  if (allocatedQueueSize<size) {
   	freeQueue(); Queue=(t_Cell *) malloc(size*sizeof(t_Cell)); if ((ok=(Queue!=NULL))) allocatedQueueSize=size;
  }
  return ok;
} // allocateQueue

bool increaseQueue() {
//   -------------
  const int size=allocatedQueueSize+allocatedQueueSize; t_Cell *tmp=(t_Cell *) malloc(size*sizeof(t_Cell));
  if (tmp==NULL) { reportError(storeAllocFail); return F; }
  for (int i=0; i<qIndex; i++) tmp[i]=Queue[i]; freeQueue(); Queue=tmp; allocatedQueueSize=size; return T;
} // increaseQueue;

bool allocateStore() {
//   -------------
  bool ok=T; const int nR=(R<startSize) ? startSize : R, nC=(C<startSize) ? startSize : C;
  if ((nR>allocatedR)|(nC>allocatedC)) {
    const int nRnC=nR*nC; freeStore();
    if ((ok=allocateRectangle(&xRectangle, nR, nC))) if ((ok=allocateRectangle(&water, nR, nC)))
      if ((ok=allocateRectangle(&pond, nR, nC))) if ((ok=allocateQueue(nRnC))) if ((ok=allocateUsed(nRnC+1))) {
        pondSize=(int*) malloc(nRnC/2*sizeof(int)); ok=(pondSize!=NULL); if (ok) {allocatedR=nR; allocatedC=nC; }}
    if (!ok) { reportError(storeAllocFail); freeStore(); }
  }
  return ok;
} // allocateStore
//===================================================== type ======================================================

enum magicType {notMagic, notNormal, Normal};
typedef magicType (*t_mticb)(const bool); t_mticb isMagic;
magicType isMagicSq(const bool zeroBase) { // R==C
//        ---------
  const int min=zeroBase ? 0 : 1, max=RC+min-1; const Uint magicSum=zeroBase ? rowConstant-R : rowConstant;
  Uint chkSum=0, sumX, sumY, sumXY=0, sumYX=0; for (int i=min; i<=max; i++) numUsed[i]=F;
  for (int r=0; r<R; r++) {
    sumX=0; sumY=0;
    for (int c=0; c<R; c++) {
	    const int tmp=xRectangle[r][c];
	    if ((min<=tmp)&(tmp<=max)) numUsed[tmp]=T; sumX+=tmp; sumY+=xRectangle[c][r];
    }
	  if (r==0) chkSum=sumX; if ((sumX!=chkSum)|(sumY!=chkSum)) return notMagic;
    sumXY+=xRectangle[r][Rm1-r]; sumYX+=xRectangle[r][r];
  }
  if ((sumXY!=chkSum)|(sumYX!=chkSum)) return notMagic; if (chkSum!=magicSum) return notNormal;
  for (int i=min; i<=max; i++) if (!numUsed[i]) return notNormal; return Normal;
} // isMagicSq

magicType isMagicRect(const bool zeroBase) {
//        -----------
  const int min=zeroBase ? 0 : 1, max=RC+min-1; Uint chkSumR, chkSumC;
  const Uint rowSum=zeroBase ? rowConstant-C : rowConstant,
             colSum=zeroBase ? colConstant-R : colConstant; for (int i=min; i<=max; i++) numUsed[i]=F;
  for (int r=0; r<R; r++) {
    Uint sum=0; for (int c=0; c<C; c++) {
	    const int tmp=xRectangle[r][c]; if ((min<=tmp)&(tmp<=max)) numUsed[tmp]=T; sum+=tmp;
    }
	  if (r==0) chkSumR=sum; else if (sum!=chkSumR) return notMagic;
  }
  for (int c=0; c<C; c++) {
    Uint sum=0; for (int r=0; r<R; r++) sum+=xRectangle[r][c];
	  if (c==0) chkSumC=sum; else if (sum!=chkSumC) return notMagic;
  }
  if ((chkSumR!=rowSum)|(chkSumC!=colSum)) return notNormal;
  for (int i=min; i<=max; i++) if (!numUsed[i]) return notNormal; return Normal;
} // isMagicRect

void initGlobals() {
//   -----------
  Rm1=R-1; Cm1=C-1; RC=R*C; N=R==C ? R : (int)sqrt((double)RC);
  if (R&1) { rowConstant=(RC+1)/2*C; colConstant=(RC+1)/2*R; }
      else { rowConstant=C/2*(RC+1); colConstant=R/2*(RC+1); }
  isMagic=(R==C) ? isMagicSq : isMagicRect;
  rectanglesPerFile=50000/RC; // N==5: 2000, N==10: 500, N==100: 5
  if (rectanglesPerFile==0) rectanglesPerFile=1; multicolor=F; monoColor=1;
} // initGlobals
//==================================================== input ====================================================

const int bufSize=1024, outSize=bufSize+50;
void clear_line(int c) { while (c!='\n') c=getchar(); }
//   ----------

bool getY() {
//   ----
  int c; do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n')); clear_line(c); return (c=='Y')|(c=='y');
} // getY

int getInt() { int n=0; scanf("%d", &n); clear_line(getchar()); return n; }
//  -------

bool getInts(int *p, int *q, int c) {
//   -------
  bool ok=F; *p=*q=0; if (c<0) do { c=getchar(); } while ((c==' ')|(c=='\t'));
  if ( ('1'<=c)&(c<='9') ) {
    int i=c-'0'; while ( ('0'<=(c=getchar()))&&(c<='9') ) i=i*10+c-'0'; *p=i;
    if ((c==' ')|(c=='\t')) {
      do { c=getchar(); } while ((c==' ')|(c=='\t'));
      if ( ('1'<=c)&(c<='9') ) {  int i=c-'0'; while ( ('0'<=(c=getchar()))&&(c<='9') ) i=i*10+c-'0'; *q=i; }
    }
    if (*q==0) *q=*p; ok=T;
  } 
  clear_line(c); if (!ok) return reportError("Invalid input"); return T;
} // getInts

bool getYorInts(int *p, int *q) {
//   ----------
  bool ok=F; int c; *p=-1; do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n') );
  if ( (c=='Y')|(c=='y') ) ok=T; else if ( (c!='N')&(c!='n') ) return getInts(p, q, c);  
  clear_line(c); return ok;
} // getYorInts

bool getFileName(char *buf, const int size) {
//   -----------
  int c, i=0; char *s=buf; do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n') ); *s=c;
  while (i++<size) if ( (*++s=getchar())=='\n') break;
  if (*s!='\n') { printf("\nFile name too long.\n"); clear_line(*s); return F; } *s='\0'; return T;
} // getFileName

void adjustName(char *buf) {
//   ----------
  char *s=buf; bool txt=F;
  if ((*s!='.')&(*s!='/')) { // Assume in current folder.
    char tmp[bufSize]; snprintf(tmp, bufSize, "./%s", buf); strcpy(buf, tmp);
  }
  if (access(buf, R_OK)!=0) {
    while (*s++!='\0')
      ; --s;  if ((strlen(buf)>6)&&(*s--=='t')&&(*s--=='x')&&(*s--=='t')&&(*s=='.')) txt=T;
    if (!txt) strcat(buf, ".txt"); // no .txt entered, add it
  }
} // adjustName

FILE *openInput(char buf[bufSize]) {
//    ---------
  char *rFname=NULL; FILE *rfp=NULL;
  do {
    printf("\nEnter the name of the %ss file: ", (R==C)?"square":"rectangle");
    if (getFileName(buf, bufSize-6)) { rFname=buf; break; }
     printf("\a\nCan't read the file name. Try again? y (yes) or n (no) "); if (!getY()) break;
  } while (T);
  if (rFname!=NULL) {
    adjustName(buf);
    if ((rfp=fopen(buf, "r"))==NULL) {
      const int msgSize=bufSize+50; char msg[msgSize];
      snprintf(msg, msgSize, "\a\nCan't open for read %s", buf); perror(msg);
    }
  }
  return rfp;
} // openInput

bool inputRectangle(FILE *rfp) {
//   --------------
  if (!allocateStore()) return F; bool zeroBase=F; maxNumber=INT_MIN;
  for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
   	int rv, tmp;
	   if ( (rv=fscanf(rfp, "%d", &tmp))!=1) {
      if ( (rv!=EOF)|(r!=0)|(c!=0) ) return reportError("Reading rectangle from file"); return F;
    }
    if (tmp==0) zeroBase=T; else if (tmp>maxNumber) maxNumber=tmp; xRectangle[r][c]=tmp;
  }
  ++inputRectNumber; ++fileRectNumber; magicType t=isMagic(zeroBase);
  char ts[10]; snprintf(ts, 10, "%s", (R==C)?"Square":"Rectangle");
  if (t==notMagic) printf("\n%s number %d is not magic.\n", ts, inputRectNumber);
  else if (t==notNormal) printf("\n%s number %d is not normal magic.\n", ts, inputRectNumber);
  return T; 
} // inputRectangle
//=============================================== output =============================================

void stripName(char *inFname, char *obuf) {
//   ---------
  char *s=inFname; while (*s++!='\0');
  while (--s!=inFname) // Remove .txt and any directory path
    if (*s=='.') *s='\0'; else if ((*s=='\\')|(*s=='/')) { ++s; break; } strcpy(obuf, s); 
} // stripName

char dirName[bufSize];
bool openDir(char *dir) {
//   -------
  int sub=0; char baseName[bufSize];
  snprintf(baseName, bufSize, "./Water%dx%d", R, C); strcpy(dir, baseName);
  do {
    if (mkdir(dir, 0775)==0) break;
    if (errno!=EEXIST) {
      snprintf(baseName, bufSize, "\a\nCan't make folder %s", dir); perror(baseName); return F;
    }
    snprintf(dir, bufSize, "%s_%d", baseName, ++sub);
  } while (T);
  strcpy(dirName, dir); printf("Output file(s) are in folder %s\n", dir); return T;
} // openDir

FILE *openOutput(char *dir, char* inFname) {
//    ----------
  FILE *wfp=NULL; int sub=0; const int baseSize=bufSize+25; char baseName[baseSize], buf[outSize];
  snprintf(baseName, baseSize, "%s/%s", dir, inFname);
  if (++outputFileNumber>0) {
    snprintf(buf, baseSize, "_%d", outputFileNumber); strcat(baseName, buf);
  }
  snprintf(buf, outSize, "%s.html", baseName);
  do {
    if (access(buf, F_OK)!=0) break; snprintf(buf, outSize, "%s_%d.html", baseName, ++sub);
  } while (T);
  if ((wfp=fopen(buf, "w"))==NULL) {
    char msg[outSize+50];
    snprintf(msg, outSize+50, "\a\nCan't open for write %s", buf); perror(msg);
  }
  return wfp;
} // openOutput

int tableWidth() { return C*(N<10 ? 25 : N<32 ? 32 : 40); }

bool writeHeadStart(FILE *wfp) {
//   --------------   
  const int cellHeight=tableWidth()*(R<100 ? 8 : 9)/(10*C)-2;
    // Numbers>9999 are split over 2 lines, forcing a higher profile.
  return fprintf(wfp,
  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
  " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
  "<html lang=\"en\" xml:lang=\"en\" "
  "xmlns=\"http://www.w3.org/1999/xhtml\">\n\n"
  "<head>\n<title>Water Retention</title>\n"
  "<style type=\"text/css\" id=\"internalStyle\">\n"
  "  body { font-family : Verdana, Arial, Helvetica,"
  " sans-serif; color : #000080 }\n"
  "  caption { font-size : medium; font-weight : bold }\n"
  "  td { font-size : small; text-align : center; height : %dpx }\n", cellHeight)>0;
} // writeHeadStart

bool writeHeadA(FILE *wfp) {
//   ---------   
  return fprintf(wfp,
  "    .eb,.cl1,.fb { border : inset white thin }\n"
  "    .cl1  { background-color : #00ffff }\n"
  "    .eb  { background-color : #ececec }\n"
  "    .fb  { background-color : #f8f8f8 }\n"
  "</style>\n</head>\n\n<body>\n\n")>0;
} // writeHeadA

bool writeHeadB(FILE *wfp) {
//   ---------  
  return fprintf(wfp,
  "    .eb,.fb,.cl0,.cl1,.cl2,.cl3,.cl4,.cl5,.cl6,.cl7,.cl8,.cl9,\n"
  "    .clA,.clB,.clC,.clD,.clE,.clF,.clG,.clH,.clI,.clJ,.clK,.clL,.clM,\n"
  "    .clN,.clO,.clP,.clQ,.clR,.clS,.clT,.clU,.clV,.clW,.clX,.clY,.clZ\n"
  "      { border : inset white thin }\n"
  "    .cl0,.clE,.clH,.clI,.clJ,.clK,.clL,.clM,.clO,.clP,\n"
  "    .clQ,.clR,.clS,.clT,.clU,.clV,.clW,.clX,.clY,.clZ\n"
  "         { font-weight : bold; color : #ffffff }\n"
  "    .eb  { background-color : #ececec }\n"
  "    .fb  { background-color : #f8f8f8 }\n"
  "    .cl0 { background-color : #663366 }\n"
  "    .cl1 { background-color : #00ffff }\n"
  "    .cl2 { background-color : #33ff99 }\n"
  "    .cl3 { background-color : #ffff99 }\n"
  "    .cl4 { background-color : #ffcccc }\n"
  "    .cl5 { background-color : #00ccff }\n"
  "    .cl6 { background-color : #00ff00 }\n"
  "    .cl7 { background-color : #ffff00 }\n"
  "    .cl8 { background-color : #ff99ff }\n"
  "    .cl9 { background-color : #ff00ff }\n"
  "    .clA { background-color : #00cc66 }\n"
  "    .clB { background-color : #ffcc00 }\n"
  "    .clC { background-color : #ff9999 }\n"
  "    .clD { background-color : #cccc00 }\n"
  "    .clE { background-color : #669966 }\n"
  "    .clF { background-color : #ff9900 }\n"
  "    .clG { background-color : #ff00cc }\n"
  "    .clH { background-color : #0000ff }\n"
  "    .clI { background-color : #009900 }\n"
  "    .clJ { background-color : #0000ff }\n"
  "    .clK { background-color : #0099ff }\n"
  "    .clL { background-color : #0000cc }\n"
  "    .clM { background-color : #006600 }\n"
  "    .clN { background-color : #cc9933 }\n"
  "    .clO { background-color : #ff0000 }\n"
  "    .clP { background-color : #6600cc }\n"
  "    .clQ { background-color : #666600 }\n"
  "    .clR { background-color : #ff6600 }\n"
  "    .clS { background-color : #990000 }\n"
  "    .clT { background-color : #333399 }\n"
  "    .clU { background-color : #003300 }\n"
  "    .clV { background-color : #999900 }\n"
  "    .clW { background-color : #3300cc }\n"
  "    .clX { background-color : #9933ff }\n"
  "    .clY { background-color : #660000 }\n"
  "    .clZ { background-color : #000000 }\n"
  "</style>\n</head>\n\n<body>\n\n")>0;
} // writeHeadB

bool writeHead(FILE *wfp, const bool mono) {
//   ---------    
  if (writeHeadStart(wfp)) return mono?writeHeadA(wfp):writeHeadB(wfp); return F;
} // writeHead

bool writeEmptyCell(FILE *wfp, const char *_class, const int r) {
//   --------------
  char v[bufSize];
  if (r==0) {
    strcpy(v, "&nbsp;&nbsp;");
    if (N>9) strcat(v, "&nbsp;&nbsp;");  // 3 digit numbers
    if (N>31) strcat(v, "&nbsp;&nbsp;"); // 4 digit numbers
  } else strcpy(v, "");
  return fprintf(wfp, "<td class=\"%s\">%s\n", _class, v)>0;
} // writeEmptyCell

bool writeCell(FILE *wfp, const char *_class, const int v) {
//   ---------
  if (v>9999) return fprintf(wfp, "<td class=\"%s\">%d,<br/>&nbsp;%03d\n", _class, v/1000, v%1000)>0;
  else return fprintf(wfp, "<td class=\"%s\">%d\n", _class, v)>0;
} // writeCell

bool writeWaterRectangle(FILE *wfp, const bool writeUnitsRectangle) {
//   -------------------
  const int tw=tableWidth(), cw=tw/C-2;
  if (writeUnitsRectangle) 
    if (fprintf(wfp, "<table cellpadding=\"10\" summary="
                     "\"Row of two rectangles.\"><tr><td>\n")<0) return F;
  if (fprintf(wfp,
        "<table width =\"%d\" border=\"1\" "
        "summary=\"Rectangle with cells retaining water colored aqua.\">\n"
        "<colgroup span=\"%d\" width=\"%d\"></colgroup>\n", tw, C, cw)<0) return F;

  if (writeUnitsRectangle) if (fprintf(wfp, "<caption>Cells</caption>\n")<0) return F;
  for (int r=0; r<R; r++) {
    if (fprintf(wfp, "<tr>\n")<0) return F;
   	for (int c=0; c<C; c++) {
	    const int v=xRectangle[r][c]; const char *s=v==water[r][c] ? "fb" : "cl1"; if (!writeCell(wfp, s, v)) return F;
	   }
  }
  if (writeUnitsRectangle) {
    if (fprintf(wfp, "</table></td><td>\n"
          "<table width =\"%d\" border=\"1\" "
          "summary=\"Rectangle shows the units of water retained.\">\n"
          "<colgroup span=\"%d\" width=\"%d\"></colgroup>\n"
          "<caption>Units</caption>\n", tw, C, cw)<0) return F;
    for (int r=0; r<R; r++) {
      if (fprintf(wfp, "<tr>\n")<0) return F;
	     for (int c=0; c<C; c++) {
	       const int v=xRectangle[r][c], w=water[r][c];
	       if (v==w) {
          if (!writeEmptyCell(wfp, "eb", r)) return F;
        } else {
          const char *col=multicolor?color[colorNum[pond[r][c]]]:color[monoColor];
          if (!writeCell(wfp, col, w-v)) return F;
        }
	     }
    }
    if (fprintf(wfp, "</table></td></tr>\n")<0) return F;
  }
  return fprintf(wfp, "</table>\n<p>&nbsp;</p>\n\n")>0;
} // writeWaterRectangle

void formatNum(char *s, const Uint b, const Uint u) {
//   ---------
  const int ct=1000, cm=ct*ct; const Uint m=u/cm, t=(u%cm)/ct, r=u%ct;
  if (b>0) snprintf(s, 20, "%u,%03u,%03u,%03u", b, m, t, r);
  else if (m>0) snprintf(s, 20, "%u,%03u,%03u", m, t, r);
  else if (t>0) snprintf(s, 20, "%u,%03u", t, r);
  else snprintf(s, 20, "%u", r);
} // formatNum

bool writeCapacity(const Uint unitsB, const Uint units, const Uint cells, FILE *wfp) {
//   -------------
  char u[20], c[20], t[20]; formatNum(u, unitsB, units); formatNum(c, 0, cells);
  if (R==C) snprintf(t, 20, "%d", R); else snprintf(t, 20, "%dx%d", R, C);
  return fprintf(wfp, "<h4>Order %s, cells retaining water %s "
	                "(%2.1f%%), units retained %s</h4>\n", t, c, (double)(100*cells)/RC, u)>0;
} // writeCapacity

void writeFoot(FILE *wfp, bool *writeError) { *writeError=fprintf(wfp, "</body>\n</html>")<0; }
//   ---------

bool writeHTML(FILE *wfp, const Uint unitsB, const Uint units, const Uint cells) {
//   ---------
  return writeCapacity(unitsB, units, cells, wfp)&&writeWaterRectangle(wfp, (unitsB>0)|(units>0));
} // writeHTML

bool startOutputFile(char *dir, char *inFn, const bool mono, FILE **wfp, bool *writeError) {
//   ---------------
  if (*wfp!=NULL) {
    writeFoot(*wfp, writeError); fclose(*wfp); *wfp=NULL;
    if (*writeError) return F; if (fileRectNumber!=0) fileRectNumber=1; 
  }
  if ((*wfp=openOutput(dir, inFn))==NULL) { *writeError=T; return F; }
  if (!writeHead(*wfp, mono)) { *writeError=T; return F; } return T;
} // startOutputFile
//============================================ get water ===========================================

void seed_rand() { srand((unsigned int)time(NULL)); } int random_(const int x) { return rand()%x; }

void setupColors(const int scheme) {
//   -----------
  for (int i=0; i<numColors; ++i) colorNum[i]=i;
  if (scheme==mono) monoColor=1;
  else if (scheme==rmono) monoColor=random_(numColors);
  else if (scheme==rmulti) for (int i=0; i<numColors; ++i) {
    const int ri=i+random_(numColors-i);
    if (i!=ri) { int v=colorNum[i]; colorNum[i]=colorNum[ri]; colorNum[ri]=v; }
  }
} // setupColors

void initBounds() {
//   ----------
  const int mR=Rm1, mC=Cm1; int **x=xRectangle;
  for (int r=0; r<R; r++) { water[r][0]=x[r][0]; water[r][mC]=x[r][mC]; }
  for (int c=1; c<mC; c++) { water[0][c]=x[0][c]; water[mR][c]=x[mR][c]; }
  for (int r=1; r<mR; r++) for (int c=1; c<mC; c++) water[r][c]=maxNumber; qIndex=0;
} // initBounds

void pushQueue(const int priority, const int row, const int col) {
//   ---------
  t_Cell cell; cell.priority=priority; cell.row=row; cell.col=col; Queue[qIndex++]=cell;
} // pushQueue

bool insertQueue(const int priority, const int row, const int col) {
//   -----------
  if ((qIndex>=allocatedQueueSize)&&!increaseQueue()) return F;
  t_Cell cell; cell.priority=priority; cell.row=row; cell.col=col; int i=qIndex-1;
  while (Queue[i].priority<priority) { Queue[i+1]=Queue[i]; if (--i<0) break; }
  Queue[++i]=cell; ++qIndex; return T;
} // insertQueue

void popQueue(t_Cell *cell) { *cell=Queue[--qIndex]; }
//   --------

bool queueBorder() {
//   -----------
  if ((R>2)&(C>2)) {
    const int mR=Rm1, mC=Cm1; pushQueue(water[1][0], 1, 0); if (!insertQueue(water[1][mC], 1, mC)) return F;
    for (int r=2; r<mR; r++) {
	    if (!insertQueue(water[r][0], r, 0)) return F; if (!insertQueue(water[r][mC], r, mC)) return F;
    }
    for (int c=1; c<mC; c++) {
	    if (!insertQueue(water[0][c], 0, c)) return F; if (!insertQueue(water[mR][c], mR, c)) return F;
    }
  } return T;
} // queueBorder

bool computeBounds(const int p, const int r, const int c) {
//   -------------
  const int mR=Rm1, mC=Cm1;
  if ( (0<r)&(r<mR)&(0<c)&(c<mC)) {
    int *bp=&water[r][c], tmp=max(xRectangle[r][c],p);
	  if (tmp<*bp) { *bp=tmp; if (!insertQueue(tmp, r, c)) return F; }
  }
  return T;
} // computeBounds

void computeCapacity(Uint *unitsB, Uint *units, Uint *cells) {
//   ---------------
  const Uint b=1000000000; Uint count=0, uB=0, u=0;
  for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
	  const Uint delta=water[r][c]-xRectangle[r][c]; if (delta!=0) { ++count; u+=delta; if (u>=b) { ++uB; u-=b; } }
 	}
  *unitsB=uB; *units=u; *cells=count;
} // computeCapacity

bool checkPonds(const int p, const int r, const int c) {
//   ----------
  const int mR=Rm1, mC=Cm1;
  if ( (0<r)&(r<mR)&(0<c)&(c<mC)) {
    const bool wet=water[r][c]>xRectangle[r][c], visited=pond[r][c]!=0;
    if (wet&!visited) {
      if (qIndex==0) pushQueue(p, r, c); else if (!insertQueue(p, r, c)) return F; pond[r][c]=p; ++pondSize[p];
    }
  }
  return T;
} // checkPonds

void putPondColors() {
//   -------------
  for (int r=1; r<Rm1; ++r) for (int c=1; c<Cm1; ++c) {
    const bool wet=water[r][c]>xRectangle[r][c]; if (wet) pond[r][c]=pondSize[pond[r][c]]%numColors;
  }
} // putPondColors

bool getPonds() {
//   --------
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) pond[r][c]=0; int pnum=0;
  for (int r=1; r<Rm1; ++r) for (int c=1; c<Cm1; ++c) {
    const bool wet=water[r][c]>xRectangle[r][c], visited=pond[r][c]!=0;
    if (wet&!visited) {
      pushQueue(++pnum, r, c); pond[r][c]=pnum; pondSize[pnum]=1;
      while (qIndex!=0) {
   	    t_Cell cell; popQueue(&cell); const int p=cell.priority, r=cell.row, c=cell.col;
        if (wet) {
	        if (!checkPonds(p, r-1, c)) return F; if (!checkPonds(p, r+1, c)) return F;
   	      if (!checkPonds(p, r, c-1)) return F; if (!checkPonds(p, r, c+1)) return F;
        }
      }
    }
  }
  putPondColors(); return T;
} // getPonds

/*
 * Based on an algorithm of Gareth McCaughan supplied by Craig Knecht.
 */
bool getWater(FILE *wfp, const int scheme, bool *writeError) {
//   --------
  *writeError=F; initBounds(); if (!queueBorder()) return F;
  while (qIndex!=0) {
   	t_Cell cell; popQueue(&cell);	const int p=cell.priority, r=cell.row, c=cell.col;
	  if (!computeBounds(p, r-1, c)) return F; if (!computeBounds(p, r+1, c)) return F;
   	if (!computeBounds(p, r, c-1)) return F; if (!computeBounds(p, r, c+1)) return F;
  }
  if (multicolor&&!getPonds()) return F;
  Uint unitsB, units, cells; computeCapacity(&unitsB, &units, &cells); setupColors(scheme);
  if (writeHTML(wfp, unitsB, units, cells)) return T;
  else { reportError("problem writing file"); *writeError=T; return F; }
} // getWater
//============================================= main ===================================================

bool checkSize() {
//   ---------
  if (R*C>maxRC) {
    char msg[100]; snprintf(msg, 100, "Order limit RxC is set at %d", maxRC); return reportError(msg);
  }
  return T;
} // checkSize

bool doAnother(bool *inputOrder, const bool writeError) {
//   ---------
  if (writeError) return F; printf("\nContinue? input y (yes) or n (no) or the order: ");
  if (getYorInts(&R, &C)) { *inputOrder=(R<0); return T; } else { freeStore(); return F; }
} // doAnother

int getScheme() {
//  -------
  printf("Color scheme: %d %s, %d %s, %d %s, %d %s? ",
    mono, schemeName[mono], multi, schemeName[multi], rmono, schemeName[rmono], rmulti, schemeName[rmulti] );
  int scheme=getInt(); if (scheme<mono) scheme=mono; if (scheme>rmulti) scheme=rmulti;
  multicolor=(scheme==multi)|(scheme==rmulti); return scheme;
} // getScheme

void outputLocalTime() {
//   ---------------
  time_t startTime=time(NULL); struct tm *local=localtime(&startTime);
  if (local!=NULL) {
    char dateTime[100];
    size_t slen=strftime(dateTime, 100, "%A %Y-%m-%d %X %Z\n\0", local); printf("\n%s", dateTime);
  }
} // outputLocalTime

void reportElapsedTime(time_t startTime) {
//   -----------------
  const int et=(int)difftime(time(NULL), startTime);
  if (et>0) printf("\nelapsed time %d:%02d:%02d\n", et/3600, et%3600/60, et%60);
} // reportElapsedTime

int main() {
//  ----
  bool another=T, inputOrder=T, dirEmpty=T, ok=F; seed_rand(); outputLocalTime();
  do { // for each input file
    bool writeError=F;
    if (inputOrder) { printf("\nOrder? "); if (!getInts(&R, &C, -1)) break;}
    if (checkSize()) {
      initGlobals(); char dir[bufSize];
      if (openDir(dir)) {
        char ibuf[bufSize]; FILE *rfp=openInput(ibuf);
        if (rfp!=NULL) {
          const int scheme=getScheme(); inputRectNumber=0; fileRectNumber=0; outputFileNumber=-1;
          FILE *wfp=NULL; char obuf[outSize]; stripName(ibuf, obuf);
          if (startOutputFile(dir, obuf, scheme==mono, &wfp, &writeError)) {
            time_t startTime=time(NULL); dirEmpty=F; 
            while (inputRectangle(rfp)) {
              if ((fileRectNumber>rectanglesPerFile)
                &&!startOutputFile(dir, obuf, scheme==mono, &wfp, &writeError)) break;
              if (!getWater(wfp,scheme, &writeError)) break;
            }
            if (wfp!=NULL) {
              if (!writeError) writeFoot(wfp, &writeError); if (!writeError) ok=T; fclose(wfp);
            }
            reportElapsedTime(startTime);
          }
          fclose(rfp);
        } // (rfp!=NULL
      } // if (openDir
    } // (checkSize
    another=doAnother(&inputOrder, writeError);
  } while (another); if (dirEmpty) rmdir(dirName);
  printf("\nHit return to close the console.");
  while (T) if (getchar()=='\n') break; return ok ? EXIT_SUCCESS : EXIT_FAILURE;
} // main