/*
 * File:    HighlightSquare.cpp
 * Author:  S Harry White
 * Created: 2020-10-03
 * Updated: 2020-10-06
 *   Add comment.
 *   Adjust rectangles per file for all_kinds selection.
 * Updated: 2020-10-10
 *   Change color scheme choices to: mono, multi, random_ mono, random_ multi
 * Updated: 2020-10-24
 *   Fix "natural" determination for non-square rectangles.
 * Updated: 2020-10-31
 *   Add optional output of numbers and cell borders.
 * Updated: 2020-11-01
 *   Adjust size limit.
 * Updated: 2020-11-21
 *   Increase groupsize allocation.
 * Updated: 2020-12-22
 *   Output width and height to 1 decimal place.
 * Updated: 2021-09-12
 *   Add "number range" feature.
 *  Updated: 2023-01-31
 *    Change to compile with g++ for macOS.
 * Updated: 2023-03-28
 *   Fix store allocation. 
 *   Remove allocatedR=0, allocatedC=0, allocatedQueueSize=0 from initGlobals().
 */

/*
 *  Displays number rectangles with featured cells highlighted in color.
 *  Optionally, groups of connected cells are colored differently by group size. 
 */

//#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 Uint unsigned int

int R, C,           // The order of the rectangle.
    N,              // R==C? R : sqrt(RC)
    Rm1,            // R-1
    Cm1,            // C-1
    RC,             // R*C
    lo, hi,         // number range
    RClimitNums=1000000,
    RClimitNoNums=10000000;

const int all_kinds=0, non_prime=1, prime=2, odd=3, even=4, natural=5, singly_even=6,
      natural_prime=7, doubly_even=8, num_range=9,
      firstKind=all_kinds, lastKind=num_range, numKinds=lastKind-firstKind+1,
      mono=1, multi=2, rmono=3, rmulti=4;

int maxNumber,            // Biggest number in the rectangle.
    inputRectNumber,      // Counts rectangles input from a .txt file.
    fileRectNumber,       // Counts rectangles for the current output file.
    outputFileNumber,
    rectanglesPerFile;

int **xRectangle=NULL, **group=NULL, *groupSize, allocatedR, allocatedC;
const int startSize=25; const bool F=false, T=true;
bool **highLight=NULL, multicolor, isAssoc;

struct t_array { char kind, scheme; bool nums, bords; float h, w; };
struct t_Cell { int group, row, col; }; t_Cell *Queue=NULL;
int allocatedQueueSize, qIndex;

const int numColors=36;
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"
};

int colorNum[numColors], monoColor;
const char *hiName[]={
  "all-choices", "non-prime", "prime", "odd", "even", "natural", "singly-even",
  "natural-prime", "doubly-even", "number range" },
  *schemeName[]={ NULL, "mono", "multi", "random_ mono", "random_ multi" },
  *nameSq="square", *nameRect="rectangle", *nameSR;

void initGlobals() {
//   -----------
  Rm1=R-1; Cm1=C-1; RC=R*C; N=R==C? R : (int)sqrt((double)RC); qIndex=0;
  inputRectNumber=0; fileRectNumber=0; outputFileNumber=-1;
  rectanglesPerFile=50000 / RC;  // N==5: 2000, N==10: 500, N==100: 5
  if (rectanglesPerFile==0) rectanglesPerFile=1; nameSR=(R==C)?nameSq:nameRect;
  multicolor=F; monoColor=1; isAssoc=F;
} // initGlobals
//========================================== allocate store ==========================================

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

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

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

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

void freeStore() {
//   ---------
  freeIntRectangle(&group, allocatedR); freeBoolRectangle(&highLight, allocatedR);
  freeIntRectangle(&xRectangle, allocatedR);
  if (groupSize!=NULL) { free(groupSize); groupSize=NULL; }
  allocatedR=0; allocatedC=0; freeQueue();
}

bool allocateIntRectangle(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) freeIntRectangle(rectangle, numAllocated);
  }
  return ok;
} // allocateIntRectangle

bool allocateBoolRectangle(bool*** rectangle, const int nR, const int nC) {
//   ---------------------
  bool ok;

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

bool allocateQueue(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() {
//   ------------- 
  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; int nR=R, nC=C;
  if (nR<startSize) nR=startSize; if (nC<startSize) nC=startSize;
  if ((nR>allocatedR)|(nC>allocatedC)) {
    int nRnC=nR*nC; freeStore();
    if ((ok=allocateIntRectangle(&xRectangle, nR, nC)))
	     if ((ok=allocateBoolRectangle(&highLight, nR, nC)))
         if ((ok=allocateIntRectangle(&group, nR, nC)))
          if ((ok=allocateQueue(nRnC))) {
            groupSize=(int*) malloc((nRnC/2+2)*sizeof(int)); ok=(groupSize!=NULL);
            if (ok) {allocatedR=nR; allocatedC=nC; }
          }
    if (!ok) { reportError(storeAllocFail); freeStore(); }
  }
  return ok;
} // allocateStore
//=================================== common procedures ======================================

void seed_rand() { srand((unsigned int)time(NULL)); }

int random_(const int x) { return rand()%x; }

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) {
//   -----------------
  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
//========================================== input ===========================================

bool inputRectangle(FILE *rfp) {
//   --------------
  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) ) {
        char msg[100]; snprintf(msg, 100, "Reading %ss from file", nameSR);
        return reportError(msg);
      }
      return F;
    }
    if (tmp>maxNumber) maxNumber=tmp; xRectangle[r][c]=tmp;
  }
  ++inputRectNumber; ++fileRectNumber; return T; 
} // inputRectangle

void clearLine(int c) { while (c!='\n') c=getchar(); }
//   ---------

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

int getInt() { int n=0; scanf("%d", &n); clearLine(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;
  } 
  clearLine(c);
  if (!ok) return reportError("Invalid input"); return T;
} // getInts

bool getRange(int *p, int *q) {
//   --------
  bool ok=F; int c; *p=*q=0;

  do { c=getchar(); } while ((c==' ')|(c=='\t'));
  if ( ('0'<=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 ( ('0'<=c)&(c<='9') ) {
        int i=c-'0'; while ( ('0'<=(c=getchar()))&&(c<='9') ) i=i*10 + c-'0'; *q=i;
      }
    }
    if (*q<*p) {int tmp=*q; *q=*p; *p=tmp; } ok=T;
  } 
  clearLine(c);
  if (!ok) return reportError("Invalid input"); return T;
} // getRange

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);  
  clearLine(c); return ok;
} // getYorInts

bool getFileName(char *buf, 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"); clearLine(*s); return F; }
  *s='\0'; return T;
} // getFileName

const int bufSize=1024, outSize=bufSize + 50;
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]) {
//    ---------
  FILE *rfp=NULL; char *rFname=NULL;
  do {
    printf("\nEnter the name of the %ss file: ", nameSR);
    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
//========================================== 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(const char *name) {
//   -------
  int sub=0; char buf[bufSize]; strcpy(buf, name);
  do {
    if (mkdir(buf, 0775)==0) break;
      if (errno!=EEXIST) {
        char msg[bufSize+50];
        snprintf(msg, bufSize+50, "Can't make folder %s", buf); perror(msg); return F;
      }
      snprintf(buf, bufSize, "%s_%d", name, ++sub);
  } while (T);
  strcpy(dirName, buf); printf("Output files are in folder %s\n", buf); return T;
} // openDir

FILE *openOutput(char *inFname, const char *hiName) {
//    ----------
  FILE *wfp=NULL; int sub=0; const int baseSize=bufSize+25; char baseName[baseSize], buf[outSize];
  snprintf(baseName, baseSize, "./%s-%s", inFname, hiName);
  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

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) {
    int ri=i+random_(numColors-i);
    if (i!=ri) { int v=colorNum[i]; colorNum[i]=colorNum[ri]; colorNum[ri]=v; }
  }
} // setupColors

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

bool writeHeadStart(t_array options, FILE *wfp) {
//   --------------   
  double h, w;
  if (options.nums) { h=(double) (tableWidth()*(R<100 ? 8 : 9)/(10*C)-2); w=h; }
                     // Numbers>9999 are split over 2 lines, forcing a higher profile.
  else { h=options.h; w=options.w; }
    
  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>Highlight</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; width : %.1fpx; height : %.1fpx; }\n", w, h)>0;
} // writeHeadStart

bool writeHeadA(const bool bords, FILE *wfp) {
//   ---------   
  if (bords) if (fprintf(wfp,
    "    .cl1,.cl { border : inset white thin }\n")<0) return F;

  return fprintf(wfp,
  "    .cl1 { background-color : #00ffff }\n"
  "    .cl  { background-color : #ececec }\n"
  "</style>\n</head>\n\n<body>\n\n")>0;
} // writeHeadA

bool writeHeadB(const bool bords, FILE *wfp) {
//   ---------  
  if (bords) if (fprintf(wfp,
  "    .cl,.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")<0) return F;

  return fprintf(wfp,
  "    .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"
  "    .cl  { background-color : #ececec }\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, t_array options) {
//   ---------    
  if (writeHeadStart(options, wfp))
    return options.scheme==mono?writeHeadA(options.bords, wfp):writeHeadB(options.bords, wfp);
  return F;
} // writeHead

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 writeBlankCell(FILE *wfp, const char *_class) {
//   --------------
    return fprintf(wfp, "<td class=\"%s\">\n", _class)>0;
} // writeBlankCell

bool writeRectangle(FILE *wfp, t_array options) {
//   --------------
  //int tw=tableWidth(), cw=tw/C-2;
  //if (fprintf(wfp,
  //      "<table width =\"%d\" border=\"1\" "
  //      "summary=\"Rectangle with highlighted cells.\">\n"
  //      "<colgroup span=\"%d\" width=\"%d\"></colgroup>\n"
  //      "<caption>%s</caption>\n",
  //      tw, C, cw, hiName[kind])<0) return F;
  if (fprintf(wfp,
        "<table summary=\"Rectangle with highlighted cells.\">\n"
        "<caption>%s</caption>\n", hiName[options.kind])<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++) {
	       bool hl=highLight[r][c]; const char *col;
	       if (hl) col=multicolor?color[colorNum[group[r][c]]]:color[monoColor]; else col="cl";
         if (options.nums) { if (!writeCell(wfp, col, xRectangle[r][c])) return F; }
         else { if (!writeBlankCell(wfp, col)) return F; }
	     }
    }
    return fprintf(wfp, "</table>\n<p>&nbsp;</p>\n\n")>0;
} // writeRectangle

void formatNum(char *s, Uint b, Uint u) {
//   ---------
  const int ct=1000, cm=ct*ct; 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

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

bool startOutputFile(char *inFn, t_array options, 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(inFn, hiName[options.kind]))==NULL) { *writeError=T; return F; }
  if (!writeHead(*wfp, options)) { *writeError=T; return F; }
  return T;
} // startOutputFile
//======================================== highlight ========================================

bool isAssociative(int **x, const int nr, const int nc) {
//   -------------
  bool odd=((nr&1)==1);
  const int nrd2=nr/2, nrm1=nr-1, ncd2=nc/2, ncm1=nc-1, S2=x[0][0]+x[nrm1][ncm1];

  for (int r=0; r<nrd2; ++r) for (int c=0; c<nc; ++c) {
    if (x[r][c]+x[nrm1-r][ncm1-c]!=S2) return F;
  }
  if (odd) for (int c=0; c<ncd2; ++c)
    if (x[nrd2][c]+x[nrd2][ncm1-c]!=S2) return F;
  return T;
} // isAssociative

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

bool insertQueue(int group, int row, int col) {
//   -----------
  if ((qIndex >= allocatedQueueSize)&&!increaseQueue()) return F;
  t_Cell cell; cell.group=group; cell.row=row; cell.col=col;

  int i=qIndex-1; while (Queue[i].group<group) { Queue[i+1]=Queue[i]; if (--i<0) break; }
  Queue[++i]=cell; ++qIndex;
  //printQueue();
  return T;
} // insertQueue

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

bool checkGroups(const int p, const int r, const int c) {
//   -----------
  if ((0<=r)&(r<R)&(0<=c)&(c<C)) {
    bool hl=highLight[r][c], notVisited=group[r][c]==0;
    if (hl&notVisited) {
      if (qIndex==0) pushQueue(p, r, c); else if (!insertQueue(p, r, c)) return F;
      group[r][c]=p; ++groupSize[p];
    }
  }
  return T;
} // checkGroups

void putGroupColors(const int kind) {
//   --------------
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) {
    if (highLight[r][c]) group[r][c]=groupSize[group[r][c]]%numColors;
  }
  if (isAssoc&(kind==num_range)) {
    const int mr=R-1, mc=C-1; 
    for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) if (highLight[r][c]) {
      group[mr-r][mc-c]=group[r][c]; highLight[mr-r][mc-c]=T;
    }
  }
} // putGroupColors

bool getGroups(const int kind) {
//   ---------
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) group[r][c]=0;
  int pnum=0; qIndex=0;
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) {
    bool hl=highLight[r][c], notVisited=group[r][c]==0;
    if (hl&notVisited) {
      pushQueue(++pnum, r, c); group[r][c]=pnum; groupSize[pnum]=1;
      while (qIndex!=0) {
   	    t_Cell cell; popQueue(&cell); int gp=cell.group, cr=cell.row, cc=cell.col;
	      if (!checkGroups(gp, cr-1, cc)) return F; if (!checkGroups(gp, cr+1, cc)) return F;
   	    if (!checkGroups(gp, cr, cc-1)) return F; if (!checkGroups(gp, cr, cc+1)) return F;
      }
    }
  }
  putGroupColors(kind); return T;
} // getGroups

bool isPrime(const int v, const int r, const int c) {
//   -------
  int i=2; if (v<=1) return F; while ((i*i)<=v) if ((v%i++)==0) return F; return T;
} // isPrime

bool isNotPrime(const int v, const int r, const int c) { return !isPrime(v, r, c); }
//   ----------

bool isOdd(const int v, const int r, const int c) { return (v&1)==1; }
//   -----

bool isEven(const int v, const int r, const int c) { return (v&1)==0; }
//   ------

bool isSinglyEven(const int v, const int r, const int c) { return (v&3)==2; }
//   ------------

bool isDoublyEven(const int v, const int r, const int c) { return (v&3)==0; }
//   ------------

bool isNatural(const int v, const int r, const int c) { return v==(r*C+c+1); }
//   ---------

bool isNaturalPrime(const int v, const int r, const int c) { return isNatural(v,r,c)&&isPrime(v,r,c); }
//   --------------

bool isInRange(const int v, const int r, const int c) { return (v>=lo)&(v<=hi); }
//   ---------

typedef bool(*t_bcIcIcI)(const int, const int, const int);
// non_prime=1, prime=2, odd=3, even=4, natural=5, singly_even=6, natural_prime=7, doubly_even=8, num_range=9
t_bcIcIcI highLightType[numKinds+1]={
  NULL, isNotPrime, isPrime, isOdd, isEven, isNatural, isSinglyEven, isNaturalPrime,
  isDoublyEven, isInRange
};

void hiLiteCells(int **X, t_bcIcIcI highLightType, const int kind) {
//   -----------
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) highLight[r][c]=highLightType(X[r][c], r, c);
  if (isAssoc&&(kind==num_range)&&!multicolor) 
    for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) if (highLight[r][c]) highLight[R-1-r][C-1-c]=T;
} //hiLiteCells

bool getHiLite(FILE *wfp, t_array options, bool *writeError) {
//   ---------
  *writeError=F; hiLiteCells(xRectangle, highLightType[options.kind], options.kind);
  if (multicolor&&!getGroups(options.kind)) return F;
  if (!writeRectangle(wfp,options)) { reportError("problem writing file"); *writeError=T; return F; }
  return T;
} // getHiLite

bool getHiLites(FILE *wfp, t_array options, bool *writeError) {
//   ----------
  if ((options.kind==num_range)|(options.kind==all_kinds)) isAssoc=isAssociative(xRectangle,R,C);
  if (fprintf(wfp, "<p>%s %d", nameSR, inputRectNumber)<0) return F;
  *writeError=F; setupColors(options.scheme);
  if (options.kind==all_kinds) {
    for (int k=firstKind+1; k<=lastKind; ++k) {
      options.kind=k; if (!getHiLite(wfp, options, writeError)) return F;
    }
  } else return getHiLite(wfp, options, writeError);
  return T;
} // getHiLites

//bool writeCheckRectangle(FILE *wfp) {
////   -------------------
//  int R=6, C=6, tw=C*25, cw=tw/C-2;
//  if (fprintf(wfp,
//        "<table width =\"%d\" border=\"1\" "
//        "summary=\"Rectangle with highlighted cells.\">\n"
//        "<colgroup span=\"%d\" width=\"%d\"></colgroup>\n"
//        "<caption>colors</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++) {
//	       int v=r*C+c, hl=T; char *col;
//	       if (hl) col=color[r*C+c]; if (!writeCell(wfp, col, v)) return F;
//	     }
//    }
//    return fprintf(wfp, "</table>\n<p>&nbsp;</p>\n\n")>0;
//} // writeCheckRectangle
//
//bool checkColors(FILE *wfp) {
////   -----------
//   return writeCheckRectangle(wfp);
//} // checkColors
//============================================== main ===============================================

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

void adjustRectanglesPerFile() {
//   -----------------------
  rectanglesPerFile/=lastKind; if (rectanglesPerFile==0) rectanglesPerFile=1;
} // adjustRectanglesPerFile

int getKind() {
//  -------
  printf("Highlight: %d %s, %d %s, %d %s, %d %s, %d %s, %d %s,\n"
         "           %d %s, %d %s, %d %s, %d %s? ",
         all_kinds, hiName[all_kinds], non_prime, hiName[non_prime], prime, hiName[prime],
         odd, hiName[odd], even, hiName[even],
         natural, hiName[natural], singly_even, hiName[singly_even], natural_prime, hiName[natural_prime],
         doubly_even, hiName[doubly_even], num_range, hiName[num_range]);
  int kind=getInt(); if (kind<all_kinds) kind=all_kinds; if (kind>num_range) kind=num_range;
  if ((kind==num_range)|(kind==all_kinds)) {
    printf("Number range, (low high)? "); if (!getRange(&lo, &hi)) return -1;
  }
  if (kind==all_kinds) adjustRectanglesPerFile(); return kind;
} // getKind

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

bool getNumsBordsHW(t_array *x) {
//   --------------
  printf("Output numbers: y (yes) or n (no)? "); x->nums=getY();
  if (x->nums) { x->bords=T; x->h=0.0; x->w=0.0; }
  else {
    printf("Use cell borders: y (yes) or n (no)? "); x->bords=getY();
    printf("Cell pixels, (width height)? ");
    if (scanf("%f %f", &x->w, &x->h)!=2) { printf("\aInput error.\n"); return F; }
  }
  int RClimit=x->nums?RClimitNums:RClimitNoNums;
  if (RC>RClimit) { printf("\aSize of %s limit is %d\n", nameSR, RClimit); return F; }
  return T;
} // getNumsBordsHW

bool getOptions(t_array *x) {
//   ----------
  x->kind=getKind(); if (x->kind<0) return F; x->scheme=getScheme(); return getNumsBordsHW(x);
} // getOptions

int main() {
//  ----
  bool another=T, inputOrder=T, ok=F, dirEmpty=T; outputLocalTime();
  allocatedR=0; allocatedC=0; allocatedQueueSize=0; seed_rand();
  if (openDir("./highLight")) do {
    bool writeError=F;
    if (inputOrder) { printf("\nOrder? "); if (!getInts(&R, &C, -1)) break;}
    initGlobals();
    if (allocateStore()) {
      char ibuf[bufSize]; FILE *rfp=openInput(ibuf);
      if (rfp!=NULL) {
        t_array options;
        if (getOptions(&options)) {
          if (chdir(dirName)==0) {
            FILE *wfp=NULL; char obuf[outSize]; stripName(ibuf, obuf);
            if (startOutputFile(obuf, options, &wfp, &writeError)) {
              time_t startTime=time(NULL); dirEmpty=F;
              while (inputRectangle(rfp)) { //checkColors(wfp);
                if ((fileRectNumber>rectanglesPerFile)
                  &&!startOutputFile(obuf, options, &wfp, &writeError)) break;
                if (!getHiLites(wfp, options, &writeError)) break;
              }
              if (wfp!=NULL) { if (!writeError) writeFoot(wfp, &writeError); fclose(wfp); }
              if (!writeError) ok=T; reportElapsedTime(startTime);
            }
            if (chdir("..")!=0) { writeError=T; perror("Can't change folder "); }
          } else {
            char msg[bufSize+50];
            snprintf(msg, bufSize+50, "Can't open folder %s", dirName); perror(msg);

          }
        }
        fclose(rfp);
      } // (rfp!=NULL
    } // allocateStore
    another=doAnother(&inputOrder, writeError, dirEmpty);
  } while (another);
  printf("\nHit return to close the console.");
  while (T) if (getchar()=='\n') break; return ok ? EXIT_SUCCESS : EXIT_FAILURE;
} // main