/*
 * File:    Bones.cpp
 * Author:  S Harry White
 * Created: 2011-04-03
 * Updated: 2021-12-13
 *   Tidy code.
 * Updated: 2022-07-15
 *   Add non-square rectangles.
 * Updated: 2023-01-26
 *   Increase size of buffer for perror message.
 * Updated: 2023-02-13
 *   Improve openInput and openOutput.
 *  Updated: 2023-03-28
 *   Fix store allocation. Remove allocatedSize=0 from initGlobals().
 */

#include "stdafx.h"
#include <assert.h>
#include <conio.h>
#include <direct.h>
#include <errno.h>
#include <io.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <Windows.h>

const bool F=false, T=true; const int startSize=25;
int R, C,
    N,
    minNumber, maxNumber, // Smallest, biggest number in the rectangle
    inputRectangleNumber, // Counts rectangles input from a .txt file.
    outputFileNumber, rectanglesPerFile, allocatedSize;

double **xRectangle=NULL, StoB; // Rectangle to bones adjustment

void initGlobals() { rectanglesPerFile=90000/(R*C); N=(int)sqrt((double)R*C); }
//============================================ storage ==================================================

char *storeAllocFail="Storage allocation failed";
bool reportError(char *msg) { printf("\a\nError: %s.\n", msg); return F; }

bool reportNumError(char *s, double num) {
//   --------------
  const int msgSize=256; char msg[msgSize];
  sprintf_s(msg, msgSize, s, num); return reportError(msg);
} // reportNumError

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

bool allocateRectangle() {
//   -----------------
  bool ok=T; int size=(R>C) ? R : C; if (size<startSize) size=startSize;
  if (size>allocatedSize) {
    freeRectangle(allocatedSize);
    xRectangle=(double**) malloc(size*sizeof(double*)); ok=xRectangle!=NULL;
    if (ok) {
      int numAllocated=0;
      for(int i=0; i<size; i++) {
        double* p=(double*) malloc(size*sizeof(double)); xRectangle[i]=p;
        if (p==NULL) { numAllocated=i; ok=F; break; }
      }
      if (ok) allocatedSize=size; else freeRectangle(numAllocated);
    }
  }
  return ok;
} // allocateRectangle
//============================================== output ==================================================

//void consolePrint() {
////   ------------
//  double **x=xRectangle;
//  for (int r=0; r<R; ++r) {
//    printf("%d", (int)x[r][0]); for (int c=1; c<C; ++c) printf(" %d", (int)x[r][c]); putchar('\n');
//  }
//} // consolePrint

const int bufSize=1024, titleSize=bufSize+25, outSize=titleSize+50; char title[titleSize];
bool writeHead(bool toBones, FILE *wfp) {
//   ---------
  char *s=toBones ?
    "    .cellbb { background-color : #ccffff; color : #0000FF }\n"
    "    .cellgg { background-color : #ccffcc; color : #008000 }\n"
    "    .cellrr { background-color : #ffcccc; color : #800000 }\n" :
    "    .cellwb { background-color : #eeeeee; color : #000080 }\n";

  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>%s</title>\n"
    "<style type=\"text/css\" id=\"internalStyle\">\n"
    "  body { font-family : Verdana, Arial, Helvetica,"
    " sans-serif; color : #000080 }\n"
    "  td { font-size : small; font-weight : bold; text-align : center; }\n"
    "%s</style>\n</head>\n\n<body>\n\n", title, s)>0;
} // writeHead

bool writeCell(FILE *wfp, char *_class, double value, bool useDouble) {
//   ---------
  if (useDouble) return fprintf(wfp, "<td class=\"%s\">%.1f\n", _class, value)>0; 
  else return fprintf(wfp, "<td class=\"%s\">%d\n", _class, (int)value)>0;
} // writeCell

int tableWidth(bool toBones) {
//  ----------
  int cellWidth=N<10 ? 24 : N<32 ? 30 : N<100 ? 40 : 50;
  if (toBones) cellWidth+=N%2 ? 8 : 20; return cellWidth*C;
} // tableWidth

bool writeRectangle(FILE *wfp, bool toBones, bool *writeError) {
//   --------------
  const int width=tableWidth(toBones); double pm=toBones ? -StoB : StoB;
  if (fprintf(wfp,
        "<table width =\"%d\" summary=\"%ss\">"
        "<colgroup span=\"%d\" width=\"%d\"></colgroup>\n",
        width, toBones ? "bone" : (R==C) ? "square" : "rectangle", C, width/C-2)<0) return F;
  for (int row=0; row<R; row++) {
    if (fprintf(wfp, "<tr>\n")<0) return F;
	   for (int col=0; col<C; col++) {
	     double tmp=xRectangle[row][col]+pm; char *s;
      if (toBones) s=tmp<0. ? "cellbb" : tmp==0. ? "cellgg" : "cellrr"; else s="cellwb";
      if (!writeCell(wfp, s, tmp, toBones&(C%2==0))) return F;
	   }
  }
  return fprintf(wfp, "</table><div><br/></div>\n\n")>0;
} // writeRectangle

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

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_s(obuf, outSize, s); 
} // stripName

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

FILE *openOutput(char *dir, char *inFname, const bool toBones) {
//    ----------
  int sub=0; FILE *wfp=NULL; const int baseSize=titleSize+25;
  char baseName[baseSize], buf[outSize];
  sprintf_s(title, titleSize, "%s%s%d",
            inFname, toBones ? "Bones" : (R==C) ? "Squares" : "Rectangles", ++outputFileNumber);
  sprintf_s(baseName, baseSize, "%s\\%s", dir, title); sprintf_s(buf, outSize, "%s.html", baseName);
  do {
    if (_access_s(buf, 00)==ENOENT) break; sprintf_s(buf, outSize, "%s%d.html", baseName, ++sub);
  } while (T);
  if (fopen_s(&wfp, buf, "w")!=0) {
    char msg[outSize+50]; sprintf_s(msg, outSize+50, "\a\nCan't open for write %s", buf); perror(msg);
  }
  return wfp;
} // openOutput

bool startOutputFile(char *dir, char *inFn, FILE **wfp, bool toBones, bool *writeError) {
//   ---------------
  if (*wfp!=NULL) {
    writeFoot(*wfp, writeError); fclose(*wfp); *wfp=NULL;
    if (*writeError) return F; inputRectangleNumber=0; 
  }
  if ((*wfp=openOutput(dir, inFn, toBones))==NULL) { *writeError=T; return F; }
  if (!writeHead(toBones, *wfp)) { *writeError=T; return F; }
  return T;
} // startOutputFile
//================================================ input ================================================

bool getToBones(FILE *rfp, bool *toBones) {
//   ----------
  double tmp=0.; *toBones=T;
  for (int j=0; j<R; ++j) {
    int rv;
    if ( (rv=fscanf_s(rfp, "%lf", &tmp))!=1) return reportError("problem reading rectangle");
    if (tmp<0.) { *toBones=F; break; }
  }
  rewind(rfp); if ((R==1)&(C==1)&(tmp==0.)) *toBones=F; return T;
} // getToBones

bool checkRectangle(bool toBones, bool zeroBased) {
//   --------------
  double goodMin, goodMax, nn=(double)R*C;
  if (toBones) { goodMin=zeroBased ? 0 : 1; goodMax=zeroBased ? nn-1. : nn; }
  else { goodMax=(nn-1.)/2.; goodMin=-goodMax; }
  for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
    double x=xRectangle[r][c];
    if ((x<goodMin)|(x>goodMax)) { reportNumError("%f is out of range", x); return F; }
    double y=(double)((int)x);
    if (toBones) {
      if (x!=y) return reportNumError("fractional number %f in input file", x);
    } else {
      if (R%2){
        if (x!=y) return reportNumError("fractional number %f in odd order bones", x);
      } else {
        double z=x<0. ? y-.5 : y+.5;
        if (x!=z) return reportNumError("%f requires fraction .5 in even order bones", x);
      }
    }
  }
  return T;
} // checkRectangle

bool inputRectangle(FILE *rfp, bool toBones) {
//   --------------
  bool zeroBased=F; if (!allocateRectangle()) return F;
  for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
    int rv; double tmp;
    if ( (rv=fscanf_s(rfp, "%lf", &tmp))!=1) {
      if ( (rv!=EOF)|(r!=0)|(c!=0) ) reportError("Reading file"); return F;
    }
    if ((tmp==0.)&toBones) zeroBased=T; xRectangle[r][c]=tmp;
  }
  ++inputRectangleNumber; StoB=(((double)R*C)+1.)/2.; if (zeroBased) --StoB;
  return checkRectangle(toBones, zeroBased);
} // 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

bool getInts(int *p, int *q, int c) {
//   -------
  bool ok=F; *p=0; *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 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, 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"); clearLine(*s); return F; }
  *s='\0'; return T;
} // getFileName

void check_txt(char *buf) {
//   ---------
  char *s=buf; bool txt=F; while (*s++!='\0');
  while (--s!=buf) if (*s=='.') { txt=(*++s=='t')&&(*++s=='x')&&(*++s=='t')&&(*++s=='\0'); break; }
  if (!txt) strcat_s(buf, bufSize, ".txt");
} // check_txt

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-4)) { 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) {
    check_txt(buf);
    if (fopen_s(&rfp, buf, "r")!=0) {
      const int msgSize=bufSize+50; char msg[msgSize];
      sprintf_s(msg, msgSize, "\a\nCan't open for read %s", buf); perror(msg);
    }
  }
  return rfp;
} // openInput
//================================================ main ==================================================

bool checkSize() {
//   ---------
  if ((R<0)|(C<0)) { printf("\a\nError: Bad order %d %d\n", R, C); return F; }
  if ((R&1)!=(C&1)) {
    printf("\a\nError: %s not supported.\n", R&1 ? "Odd x even" : "Even x odd"); return F;
  }
  return T;
} // checkSize


//void outputLocalTime() {
////   --------------
//  time_t startTime=time(NULL); struct tm local;
//  if (localtime_s(&local, &startTime)==0) {
//    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

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

int main() {
//  ----
  bool another=T, inputOrder=T, ok=F; allocatedSize=0; //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], obuf[bufSize];
        FILE *rfp=openInput(ibuf); stripName(ibuf, obuf);
        if (rfp!=NULL) {
          bool toBones=F;
          if (getToBones(rfp, &toBones)) {
            time_t startTime=time(NULL);
            inputRectangleNumber=0; outputFileNumber=0; FILE *wfp=NULL;
            if (startOutputFile(dir, obuf, &wfp, toBones, &writeError)) {
              while (inputRectangle(rfp, toBones)) {
                if ((inputRectangleNumber>rectanglesPerFile)
                  &&!startOutputFile(dir, obuf, &wfp, toBones, &writeError)) break;
                if (!writeRectangle(wfp, toBones, &writeError)) break;
              }
              if (wfp!=NULL) { if (!writeError) writeFoot(wfp, &writeError); fclose(wfp); }
            }
            reportElapsedTime(startTime);
          }
          fclose(rfp);
        } // (rfp!=NULL
      } // if (openDir
    } // (checkSize
    if (!writeError) ok=T; another=doAnother(&inputOrder, writeError);
  } while (another);

  printf("\n\nPress a key to close the console");
  while (!_kbhit()) Sleep(250); return ok?EXIT_SUCCESS:EXIT_FAILURE;
} // main