/*
 * File:    ZeroOne.cpp
 * Author:  S Harry White
 * Created: 2014-02-15
 * Updated: 2020-10-16
 *   Add support of non-square rectangles.
 * Updated: 2021-06-03
 *   Reset smallestRead and biggestRead after conversion to fix bad output.
 * Updated: 2022-02-07
 *   Prompt for file name before 0 or 1.
 * Updated: 2023-01-24
 *   Open output in the current folder.
 * Updated: 2023-01-26
 *   Change outputFile from bufSize to outSize.
 * Updated: 2023-02-22
 *   Improve openInput and openOutput.
 * Updated: 2023-03-28
 *   Fix square allocation. Call freeSquare() for each allocation.
 */

/*
 * Convert rectangles from origin 0 to 1 or vice versa.
 */

#include "stdafx.h"
#include <conio.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <share.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys\stat.h>
#include <time.h>
#include <Windows.h>

const bool F=false, T=true; bool readError, writeError, bell;
const int startSize=25, bufSize=1024, writeSize=100000000;
int R, C,     // order of the rectangle
    Rm1, Cm1, // R-1, C-1
    RC,       // R*C
    **xSquare=NULL, allocatedSize, smallestRead, biggestRead,
    squareBytes, outBytes, numSquares, printNum, fieldWidth;
char outBuffer[writeSize], *outPointer=NULL, *nameSq="square", *nameRect="rectangle", *nameSR;

void initGlobals() {
//   -----------
  Rm1=R-1; Cm1=C-1; RC=R*C; numSquares=0; printNum=100000; allocatedSize=0; fieldWidth=0;
  squareBytes=0; outBytes=0; outPointer=outBuffer; nameSR=R==C?nameSq:nameRect;
  smallestRead=LONG_MAX; biggestRead=LONG_MIN; readError=F; writeError=F; bell=T;
} // initGlobals
//================================================= store ==================================================

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

void freeSquare(const int allocated) {
//   ----------
  if (xSquare!=NULL) {
    for(int i=0; i<allocated; i++) free(xSquare[i]); free(xSquare); xSquare=NULL;
  } allocatedSize=0;
} // freeSquare

bool allocateSquare() {
//   --------------
  bool ok; xSquare=(int**) malloc(R*sizeof(int*)); ok=(xSquare!=NULL);
  if (ok) {
    int numAllocated=R;
    for (int i=0; i<R; i++) {
      int* p=(int*) malloc(C*sizeof(int)); xSquare[i]=p; if (p==NULL) { numAllocated=i; ok=F; break; }
    }
    if (ok) allocatedSize=R; else { freeSquare(numAllocated); reportError(storeAllocFail); }
  }
  return ok;
} // allocateSquare
//=============================================== input ===============================================

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_s("%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 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: ", nameSR);
    if (getFileName(buf, bufSize-4)) { rFname=buf; break; }
    printf("\a\nCan't read 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

bool readSquare(FILE *rfp) {
//   ----------
  int smallest=LONG_MAX, biggest=LONG_MIN;

  for (int r=0; r<R; r++) {
    for (int c=0; c<C; c++) {
	   int tmp, rv;
     if ( (rv=fscanf_s(rfp, "%d", &tmp))==1) {
        if (tmp<smallest) smallest=tmp; if (tmp>biggest) biggest=tmp; xSquare[r][c]=tmp;
      } else {
        if ( (rv!=EOF)|(r!=0)|(c!=0) ) {
          readError=T; printf("\a\nError reading %s from file.\n", nameSR);
        }
        return F;
      }
    }
  }
  smallestRead=smallest; biggestRead=biggest; return T;
} // readSquare
//======================================= output =========================================

int getWidth(int i) {
//  --------
  bool neg=i<0; if (neg) i=-i; int width=1; while ((i=i/10)!=0) ++width;
  if (width>10) width=10; /* limited by sprintFW11 */ return neg ? width+1 : width;
} // getWidth

void writeNumberOfSquares(char *s, const int num) {
//   --------------------
  const int msq=num/1000000, tsq=num%1000000/1000, rsq=num%1000;
  if (msq==0) if (tsq==0) printf("%s %d\n", s, rsq); else printf("%s %d,%03d\n", s, tsq, rsq);
  else printf("%s %d,%03d,%03d\n", s, msq, tsq, rsq);
} // writeNumberOfSquares

typedef void (*t_sprintFW)(char *s, const int i);
void sprintFW1 (char *s, const int i) { sprintf_s(s, 2,  "%1d",  i); }
void sprintFW2 (char *s, const int i) { sprintf_s(s, 3,  "%2d",  i); }
void sprintFW3 (char *s, const int i) { sprintf_s(s, 4,  "%3d",  i); }
void sprintFW4 (char *s, const int i) { sprintf_s(s, 5,  "%4d",  i); }
void sprintFW5 (char *s, const int i) { sprintf_s(s, 6,  "%5d",  i); }
void sprintFW6 (char *s, const int i) { sprintf_s(s, 7,  "%6d",  i); }
void sprintFW7 (char *s, const int i) { sprintf_s(s, 8,  "%7d",  i); }
void sprintFW8 (char *s, const int i) { sprintf_s(s, 9,  "%8d",  i); }
void sprintFW9 (char *s, const int i) { sprintf_s(s, 10, "%9d",  i); }
void sprintFW10(char *s, const int i) { sprintf_s(s, 11, "%10d", i); }
void sprintFW11(char *s, const int i) { sprintf_s(s, 12, "%11d", i); }
static t_sprintFW sprintFW[] =
  {
    NULL,      sprintFW1, sprintFW2, sprintFW3, sprintFW4, sprintFW5,
    sprintFW6, sprintFW7, sprintFW8, sprintFW9, sprintFW10, sprintFW11
  };

void writeSquareAny(int **Q, const int wfd) {
//   --------------
  char *s=outPointer; const int fw=fieldWidth, fw1=fieldWidth+1;
  for (int r=0; r<R; ++r) { sprintFW[fw](s, Q[r][0]); s+=fw;
    for (int c=1; c<C; ++c) { sprintFW[fw1](s, Q[r][c]); s+=fw1;} *s++='\n';
  }
  *s++='\n'; outPointer=s; outBytes+=squareBytes;  
} // writeSquareAny

void writeSquareLT40(int **Q, const int wfd) {
//   ---------------
  char *s=outPointer;
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) { const int v=Q[r][c];
    if (v<10) { *s++=' '; *s++='0'+v; } else if (v<20) { *s++='1'; *s++='0'-10+v; }
    else if (v<30) { *s++='2'; *s++='0'-20+v; } else { *s++='3'; *s++='0'-30+v; }
    if (c==Cm1) *s++='\n'; else *s++=' ';
  } *s++='\n'; outPointer=s; outBytes+=squareBytes;
} // writeSquareLT40

void writeSquareLT30(int **Q, const int wfd) {
//   ---------------
  char *s=outPointer;
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) { const int v=Q[r][c];
    if (v<10) { *s++=' '; *s++='0'+v; } else if (v<20) { *s++='1'; *s++='0'-10+v; }
    else { *s++='2'; *s++='0'-20+v; }
    if (c==Cm1) *s++='\n'; else *s++=' ';
  } *s++='\n'; outPointer=s; outBytes+=squareBytes;
} // writeSquareLT30

void writeSquareLT20(int **Q, const int wfd) {
//   ---------------
  char *s=outPointer;
  for (int r=0; r<R; ++r) for (int c=0; c<C; ++c) { const int v=Q[r][c];
    if (v<10) { *s++=' '; *s++='0'+v; } else { *s++='1'; *s++='0'-10+v; } if (c==Cm1) *s++='\n'; else *s++=' ';
  } *s++='\n'; outPointer=s; outBytes+=squareBytes;
} // writeSquareLT20

void writeSquareLT10(int **Q, const int wfd) {
//   ---------------
  char *s=outPointer;
  for (int r=0; r<R; ++r) {  *s++='0'+Q[r][0]; for (int c=1; c<C; ++c) { *s++=' '; *s++='0'+Q[r][c]; } *s++='\n'; }
  *s++='\n'; outPointer=s; outBytes+=squareBytes;  
} // writeSquareLT10

bool writeSquare(int **Q, const int wfd) {
//   -----------
  if (++numSquares==printNum) { writeNumberOfSquares("..", numSquares); printNum+=printNum; }
  if ((outBytes+squareBytes)>=writeSize) {
    if (_write(wfd, outBuffer, outBytes)!=outBytes) { writeError=T; return F; } outBytes=0; outPointer=outBuffer;
  }
  if (smallestRead<0) writeSquareAny(Q,wfd); else switch (fieldWidth) {
    case 1: writeSquareLT10(Q,wfd); break;
    case 2: { if (biggestRead<20) writeSquareLT20(Q,wfd); else if (biggestRead<30) writeSquareLT30(Q,wfd);
      else if (biggestRead<40) writeSquareLT40(Q,wfd); else writeSquareAny(Q,wfd); } break;
    default: writeSquareAny(Q,wfd); break;
  } return T;
} // writeSquare

void outputLast(const int wfd) { if ((outBytes!=0)&&(_write(wfd, outBuffer, outBytes)!=outBytes)) writeError=T; }
//   ----------

const int outSize=bufSize+50; char outputFile[outSize];
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

int openOutput(char *inFname, const int to) {
//  ----------
  int sub=0; int wfd=-1; const int baseSize=bufSize+25; 
  char baseName[baseSize], buf[outSize]; stripName(inFname, buf);
  sprintf_s(baseName, baseSize, "%sTo%d", buf, to); sprintf_s(buf, outSize, "%s.txt", baseName);
  do {
    if (_access_s(buf, 00)==ENOENT) break; sprintf_s(buf, outSize, "%s_%d.txt", baseName, ++sub);
  } while (T);
  if (_sopen_s(&wfd, buf, _O_CREAT|_O_WRONLY, _SH_DENYNO, _S_IWRITE)==0) {
    printf(".. output file is %s\n", buf); sprintf_s(outputFile, outSize, "%s", buf);
  } else {
    char msg[outSize+50]; sprintf_s(msg, outSize+50, "\a\nCan't open for write %s", buf); perror(msg);
  } return wfd;
} // openOutput
//================================================== convert ===================================================

bool convertOrigin(const int wfd, int **x, const int to) {
//   -------------
  int smallest=LONG_MAX, biggest=LONG_MIN;
  if ((smallestRead==to)|((smallestRead!=0)&(smallestRead!=1))) { smallest=smallestRead; biggest=biggestRead; }
  else {
    if (to==0) {
	     for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
         const int v=--x[r][c]; if (v<smallest) smallest=v; if (v>biggest) biggest=v;
       }
    } else {
      for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
        const int v=++x[r][c]; if (v<smallest) smallest=v; if (v>biggest) biggest=v;
      }
    }
  }
  biggestRead=biggest; smallestRead=smallest;
  const int sW=getWidth(smallest), bW=getWidth(biggest); fieldWidth=(sW>bW)?sW:bW;
  squareBytes=RC*(fieldWidth+1)+1; return writeSquare(xSquare,wfd);
} // convertOrigin
//================================================= main ========================================================

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

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

bool doAnother(bool *inputOrder, 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; } return F;
} // doAnother

int main() {
//  ----
  bool another=T, inputOrder=T, writeError=F, ok=F; char buf[bufSize]; outputLocalTime();
  do {
    if (inputOrder) { printf("\nOrder? "); if (!getInts(&R, &C, -1)) break;}
      initGlobals();
      if (allocateSquare()) { FILE *rfp=openInput(buf);
        if (rfp!=NULL) {
          int toOrigin=0; printf("\nConvert to 0 or 1: "); toOrigin=getInt();
          if ((toOrigin!=0)&(toOrigin!=1)) printf("\aMust convert to 0 or 1.\n\n");
          else { const int wfd=openOutput(buf, toOrigin);
          if (wfd!=-1) { time_t startTime=time(NULL);
            while (readSquare(rfp)) {if (!convertOrigin(wfd, xSquare, toOrigin)) break; }
            if (!writeError) { outputLast(wfd);
              if (!writeError) { ok=T;
                char s[50]; sprintf_s(s, 50, "\nNumber of %ss:", nameSR); writeNumberOfSquares(s, numSquares);
              }
            } _close(wfd); if (!ok) remove(outputFile); printElapsedTime(startTime, '\n');
          } fclose(rfp);
        } // if ((toOrigin!=0) ...
      } // rfp!=NULL
      freeSquare(allocatedSize);
    } // allocateSquares
    another=doAnother(&inputOrder, writeError);
  } while (another);
  printf("\nPress a key to close the console");
  while (!_kbhit()) Sleep(250); return ok ? EXIT_SUCCESS : EXIT_FAILURE;
} // main