/*
 *  File:    GetQR.cpp
 *  Author:  S Harry White
 *  Created: 2020-12-29
 * Updated: 2023-01-26
 *   Open output files in the current folder.
 *  Updated: 2023-01-28
 *    Change to compile with g++ for macOS.
 */

/*
 *  Gets the quotient and remainder squares of magic squares.
 */

//#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>

int **X=NULL, N, M, O, NN, squareNumber, fieldWidth; const int maxN=20000;
const bool F=false, T=true;

int getFieldWidth(int i) { int width=1; while ((i/=10)!=0) ++width; return width; }
//  -------------

void initGlobals() { M=N-1; NN=N*N; squareNumber=0; fieldWidth=getFieldWidth(N-1); }
//  ------------
//========================================= 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; }
//   -----------

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

void freeStore() { freeintArray(&X, N); }
//   ---------

bool allocateintArray(int ***a) {
//   ----------------
  bool ok=((*a=(int**) malloc(N*sizeof(int*)))!=NULL);
  if (ok) {
    int numAllocated=N;
    for (int i=0; i<N; i++) {
      int *p=(int*) malloc(N*sizeof(int));    
      if (p==NULL) { numAllocated=i; ok=F; break; } (*a)[i]=p;
    }
    if (!ok) freeintArray(a, numAllocated);
  }
  return ok;
} // allocateintArray

bool allocateStore() {
//   -------------
  bool ok=allocateintArray(&X); if (!ok) {reportError(storeAllocFail); freeStore(); } return ok;
} // allocateStore
//========================================= 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');
}

bool getYorOrder(int *n) {
//   -----------
  bool result=F; int c; *n=0;

  do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n') );
  if ( (c=='Y')|(c=='y') ) result=T;
  else if ( (c!='N')&(c!='n') )
    if ( ('1'<=c)&(c<='9') ) {
      int i=c-'0'; while ( ('0'<=(c=getchar())) && (c<='9') ) i=i*10+c-'0'; *n=i; result=T;
    }   
  clearLine(c); return result;
} // getYorOrder

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

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

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]) {
//    ---------
  char *rFname=NULL; FILE *rfp=NULL;
  do {
    printf("\nEnter the name of the squares file: ");
    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

void baseZero() { for (int r=0; r<N; r++) for (int c=0; c<N; c++) --X[r][c]; }
//   --------

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

  for (int r=0; r<N; r++) {
    for (int c=0; c<N; c++) {
	   int tmp, rv;
     if ( (rv=fscanf(rfp, "%d", &tmp))==1) {
        if (tmp<smallest) smallest=tmp; if (tmp>biggest) biggest=tmp; X[r][c]=tmp;
      } else {
        if ( (rv!=EOF)|(r!=0)|(c!=0) ) printf("\a\nError reading file.\n"); return F;
      }
    }
  }
  bool zeroBase=smallest==0; int maxv=zeroBase?NN-1:NN;
  if ((smallest<0)|(biggest>maxv)) {
    printf("Number out of range: %d\n", smallest<0?smallest:biggest); return F; }
  if (!zeroBase) baseZero(); ++squareNumber; return T;
} // readSquare
//=========================================== output ===========================================

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

char outFile[outSize];
FILE *openOutput(char inFname[bufSize]) {
//    ----------
  FILE *wfp=NULL; int sub=0; const int baseSize=bufSize+25;
  char baseName[baseSize], buf[outSize]; stripName(inFname, buf);
  snprintf(baseName, baseSize, "./%sQR", buf); snprintf(buf, outSize, "%s.txt", baseName);
  do {
    if (access(buf, F_OK)!=0) break;
    snprintf(buf, outSize, "%s_%d.txt", 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); }
  else { printf("..output file %s\n", buf); snprintf(outFile, outSize, "%s", buf); }
  return wfp;
} // openOutput

typedef bool (*t_fprintFW)(FILE *fp, int i);

bool fprintFW1(FILE *fp, int i) { return fprintf(fp, "%1d",  i) > 0; }
bool fprintFW2(FILE *fp, int i) { return fprintf(fp, "%2d",  i) > 0; }
bool fprintFW3(FILE *fp, int i) { return fprintf(fp, "%3d",  i) > 0; }
bool fprintFW4(FILE *fp, int i) { return fprintf(fp, "%4d",  i) > 0; }
bool fprintFW5(FILE *fp, int i) { return fprintf(fp, "%5d",  i) > 0; }
bool fprintFW6(FILE *fp, int i) { return fprintf(fp, "%6d",  i) > 0; }

static t_fprintFW fprintFW[] = { NULL, fprintFW1, fprintFW2, fprintFW3, fprintFW4, fprintFW5, fprintFW6 };
const int maxFieldWidth=6;

bool printQ(FILE *wfp) {
//   ------
  int fw0=fieldWidth, fw=fw0+1; if (fw>maxFieldWidth) return F;
  for (int i=0; i<N; i++) {
    if (!fprintFW[fw0](wfp, X[i][0]/N)) return F;
    for (int j=1; j<N; j++) if (!fprintFW[fw](wfp, X[i][j]/N)) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return fputc('\n', wfp)!=EOF;
} // printQ

bool printR(FILE *wfp) {
//   ------
  int fw0=fieldWidth, fw=fw0+1; if (fw>maxFieldWidth) return F;
  for (int i=0; i<N; i++) {
    if (!fprintFW[fw0](wfp, X[i][0]%N)) return F;
    for (int j=1; j<N; j++) if (!fprintFW[fw](wfp, X[i][j]%N)) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return fputc('\n', wfp)!=EOF;
} // printR

bool getQR(FILE *wfp) { return printQ(wfp)&&printR(wfp); }
//   -----
//================================================= main ================================================

//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 printElapsedTime(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);
} // printElapsedTime

bool validOrder() {
//   ----------
  if ((N<1)|(N>maxN)) { printf("\aERROR: Supported orders are 1 to %d.\n", maxN); return F; } return T;
} // validOrder

int main() {
//  ----
  bool inputOrder=T, ok=T; char buf[bufSize]; //outputLocalTime();
  do {
    if (inputOrder) { printf("\nOrder? "); N=getInt(); } initGlobals();
    if (validOrder()&&allocateStore()) {
      FILE *rfp=openInput(buf); time_t startTime=time(NULL);
      if (rfp!=NULL) {
        FILE *wfp=openOutput(buf);
        if (wfp!=NULL) {
          bool writeError=F; squareNumber=0;
          while (readSquare(rfp)) { writeError=!getQR(wfp); if (writeError) break; }
          fclose(wfp); if (squareNumber==0) remove(outFile);
          if (writeError) { perror("\n\aError writing file"); ok=F; break; }
          else printf("Number of squares: %d\n", squareNumber);
        }
        fclose(rfp);
      } // rfp!=NULL
      printElapsedTime(startTime); freeStore();
    } // allocateStore
    printf("\nAnother order? input y (yes), n (no) or the order: ");
    if (getYorOrder(&N)) inputOrder=(N<0); else break;
  } while (T);
  printf("\nHit return to close the console.");
  while (T) if (getchar()=='\n') break; return ok ? EXIT_SUCCESS : EXIT_FAILURE;
} // main