/*
 *  File:    Transforms1_2.cpp
 *  Author:  S Harry White
 *  Created: 2011-02-20
 *  Updated: 2022-02-01
 *    Tidy code.
 *  Updated: 2023-02-03
 *    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>

const bool F=false, T=true; bool zeroBased, isMagic, noError=T, *numberUsed=NULL;
const int bufSize=1024, outSize=bufSize+50, startSize=25, maxNMagicConstant=2047;
char tmpFname[outSize], wfpFname[outSize];
int N,    // The order of the square.
    M,    // N-1
    NN,   // N*N
    Half, // N/2
    Mid,  // (N+1)/2
    Odd,  // N%2
    **squareA=NULL, allocatedSize, fieldWidth, numSquares,
    smallestRead, biggestRead, printNum; // print a message at this number of squares
typedef unsigned long Uint; Uint MagicConstant; // N*(NN+1)/2

void initGlobals() {
//  -----------
  M=N-1; NN=N*N; Half=N/2; Mid=(N+1)/2; Odd=N%2; MagicConstant=N*(NN+1)/2;
  printNum=100000; numSquares=0; strcpy(tmpFname, ""); strcpy(wfpFname, "");
} // initGlobals
//====================================================== store =====================================================

void reportError(const char *msg) { printf("\a\nError: %s.\n", msg); noError=F; }
//   -----------

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

void freeStore() {
//   --------- 
  freeSquare(&squareA, allocatedSize); if (numberUsed!=NULL) { free(numberUsed); numberUsed=NULL; }
} // freeStore

bool allocateSquare(int*** square, const int size) {
//   --------------
  bool ok=((*square=(int**) malloc(size*sizeof(int*)))!=NULL);
  if (ok) {
    int numAllocated=0;
    for(int i=0; i<size; i++) {
      int *p=(int*) malloc(size*sizeof(int)); (*square)[i]=p; if (p==NULL) { numAllocated=i; ok=F; break; }
    }
    if (ok) allocatedSize=size; else freeSquare(square, numAllocated);
  }
  return ok;
} // allocateSquare 

bool allocateStore(int size) {
//   -------------
  bool ok=T; if (size<startSize) size=startSize;
  if (size>allocatedSize) { freeStore();
    if ((ok=allocateSquare(&squareA, size))) {
      numberUsed=(bool *) malloc((size*size+1)*sizeof(bool)); ok=(numberUsed!=NULL); if (!ok) freeStore();
    }
  }
  if (!ok) reportError("Storage allocation failed"); 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');
} // getY

bool getYorSize(int *n) { // 'y' or 'n' or size
//   ----------
  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;
} // getYorSize

bool getYorRows(int *p, int *q) { // 'y' or 'n' or row(s)
//   ----------
  bool result=F; int c; *p=*q=0; do { c=getchar(); } while ((c==' ')|(c=='\t'));
  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'; *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; }
    }
    result=T;
  }   
  clearLine(c); return result;
} // getYorRows

int getInt() { int n=0; scanf("%d", &n); clearLine(getchar()); return n; }
//  ------
 
void getInts(int *p, int *q) {
//   -------
  bool result=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; }
    }
    result=T;
  }   
  clearLine(c);
} // getInts

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 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); noError=F;
    }
  }
  return rfp;
} // openInput

FILE *openFile(char *inFname) {
//    --------
  FILE *rfp=NULL;
  if ((rfp=fopen(inFname, "r"))==NULL) { perror("\aCan't open transformed output file "); noError=F; }
  return rfp;
} // openFile

bool isCorrect() {
//   ---------
  if (N>maxNMagicConstant) return T; /* Overflow, can't check */
  const int size=N, min=zeroBased ? 0 : 1, max=NN+min-1;
  Uint chkSum=zeroBased ? MagicConstant-N : MagicConstant, sumX, sumY, sumXY=0, sumYX=0;
  for (int i=min; i<=max; i++) numberUsed[i]=F;
  for (int i=0; i<size; i++) {
    sumX=0; sumY=0;
    for (int j=0; j<size; j++) { int tmp=squareA[i][j]; numberUsed[tmp]=T; sumX+=tmp; sumY+=squareA[j][i]; }
    if ((sumX!=chkSum)|(sumY!=chkSum)) return F; sumXY+=squareA[i][size-i-1]; sumYX+=squareA[i][i];
  }
  if ((sumXY!=chkSum)|(sumYX!=chkSum)) return F; for (int i=min; i<=max; i++) if (!numberUsed[i]) return F;
  return T;
} // isCorrect

int getWidth(int i) {
//  --------
  const bool isSigned=i<0; int width=1; while ((i=i/10)!=0) ++width; return isSigned ? width+1 : width; }

bool readSquare(FILE *rfp, bool *error) {
//   ----------
  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; squareA[r][c]=tmp;
    } else {
      if ( (rv!=EOF)|(r!=0)|(c!=0) ) { reportError("Reading square from file"); *error=T; } return F;
    }
  }
  zeroBased=(smallest==0); isMagic=((smallest>=0)&(biggest<=NN)) ? isCorrect() : F;
  const int sWidth=getWidth(smallest), bWidth=getWidth(biggest); fieldWidth=sWidth>bWidth ? sWidth : bWidth;
  smallestRead=smallest; biggestRead=biggest; return T;
} // readSquare
//======================================================= 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

FILE *openOutput(char *inFname, const bool tmp) {
//    ----------
  FILE *wfp=NULL; int sub=0; const int baseSize=bufSize+25; char baseName[baseSize], buf[outSize]; 
  stripName(inFname, buf); snprintf(baseName, baseSize, "./%sXForm", 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); noError=F;
  } else {
    strcpy(tmp ? tmpFname : wfpFname, buf);
    if (!tmp) printf(".. writing squares to file %s\n", wfpFname);
  }
  return wfp;
} // openOutput

void fcloseNULL(FILE **pfp) { if (*pfp!=NULL) { fclose(*pfp); *pfp=NULL; }}
//   ----------

bool renameOutput(FILE **pfp) {
//   ------------
  fcloseNULL(pfp); bool ok=remove(wfpFname)==0;
  if (ok) { ok=rename(tmpFname, wfpFname)==0; if (!ok) perror("\aRename file failed.\n");
  } else perror("\aRemove file failed.\n"); return ok;
} // renameOutput

typedef bool (*t_Write)(FILE *);

bool writeSquare1(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%1d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %1d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare1

bool writeSquare2(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%2d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %2d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare2

bool writeSquare3(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%3d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %3d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare3

bool writeSquare4(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%4d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %4d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare4

bool writeSquare5(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%5d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %5d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare5

bool writeSquare6(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%6d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %6d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare6

bool writeSquare7(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%7d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %7d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare7

bool writeSquare8(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%8d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %8d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare8

bool writeSquare9(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%9d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %9d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare9

bool writeSquare10(FILE *wfp) {
//   -------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%10d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %10d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare10

bool writeSquare11(FILE *wfp) {
//   -------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%11d", squareA[r][0])<0) return F;
    for (int c=1; c<N; c++) if (fprintf(wfp, " %11d", squareA[r][c])<0) return F;
    if (fputc('\n', wfp)==EOF) return F;
  }
  return T;
} // writeSquare11

static t_Write writeSquareFW[]=
  { NULL,        writeSquare1, writeSquare2,  writeSquare3,
   writeSquare4, writeSquare5, writeSquare6,  writeSquare7,
   writeSquare8, writeSquare9, writeSquare10, writeSquare11 };

bool writeSquareOrder5(FILE *wfp) {
//   ------------------
  char squareString[100], *s=squareString; int colCount=0;
  for (int r=0; r<N; r++) for (int c=0; c<N; c++) {
    const int v=squareA[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 (++colCount==N) { *s++='\n'; colCount=0; } else *s++=' ';
  }
  *s++='\n'; *s++='\0'; return fputs(squareString, wfp)!=EOF;   
} // writeSquareOrder5

bool writeSquare(FILE *wfp) {
//   -----------
  if (++numSquares==printNum) {
    const int msq=printNum/1000000, tsq=printNum%1000000/1000, rsq=printNum%1000;
    if (msq==0) if (tsq==0) printf(".. %10d\n", rsq); else printf(".. %6d,%03d\n", tsq, rsq);
    else printf(".. %2d,%03d,%03d\n", msq, tsq, rsq); printNum+=printNum;
  }
  if ((N==5)&&(fieldWidth==2)&&(smallestRead>=0)&&(biggestRead<=NN)) { return writeSquareOrder5(wfp);
  } else { return writeSquareFW[fieldWidth](wfp) ? fputc('\n', wfp)!=EOF : F; }
} // writeSquare
//================================================== transform ==================================================

int cmpInts(const void *vp, const void *vq) { // ascending
//  -------
  int *p=(int *)vp, *q=(int*)vq; if (*p<*q) return -1; else if (*p++>*q++) return 1; return 0;
} // cmpInts

void sortInts(const int num, int *array) { qsort(array, num, sizeof(int), cmpInts); }
//   --------

void swapRowsCols1(int w, const bool msg) {
//   -------------
  const int n=N, y=M-(--w);
  if (msg) { int v[]={w+1, y+1}; sortInts(2, v); printf("swap rows/cols %d and %d\n", v[0], v[1]); }
  int *tmp=squareA[w]; squareA[w]=squareA[y]; squareA[y]=tmp;
  for (int r=0; r<n; ++r) { const int tmp=squareA[r][w]; squareA[r][w]=squareA[r][y]; squareA[r][y]=tmp; }
} // swapRowsCols1

void swapRowsCols2(int w, int x, const bool msg) {
//   -------------
  int n=N, y=M-(--w), z=M-(--x);
  if (msg) { int v[]={w+1, x+1, y+1, z+1}; sortInts(4, v);
    printf("swap rows/cols %d and %d, %d and %d\n", v[0], v[1], v[2], v[3]);
  }
  int *tmp=squareA[w]; squareA[w]=squareA[x]; squareA[x]=tmp;
       tmp=squareA[y]; squareA[y]=squareA[z]; squareA[z]=tmp;
  for (int r=0; r<n; ++r) {
    int tmp=squareA[r][w]; squareA[r][w]=squareA[r][x]; squareA[r][x]=tmp;
        tmp=squareA[r][y]; squareA[r][y]=squareA[r][z]; squareA[r][z]=tmp;
  }
} // swapRowsCols2

bool transformSquares( FILE *rfp, FILE **wfp, const int row1, const int row2, const bool tmp, bool *writeError) {
//   ----------------
  bool first=T, readError=F; numSquares=0; *writeError=F;
  while (readSquare(rfp, &readError)) {
    if (row2==0) { swapRowsCols1(row1, first); first=F; } else { swapRowsCols2(row1, row2, first); first=F; }
    if (isMagic&&!isCorrect()) reportError("Incorrect result. Please report by email");
    if (!writeSquare(*wfp)) { perror("\a\nError writing file"); *writeError=T; break; }
  }
  if (readError|*writeError) { fcloseNULL(wfp); remove(tmp ? tmpFname : wfpFname); } return !readError;
} // transformSquares
//==================================================== main ===================================================

bool checkSize() {
//   ---------
  if (N<=0) { reportError("N must be a positive integer"); return F; }
  else if (N==1) { reportError("Can't swap in an order 1 square"); return F; } return T;
} // checkSize

bool checkRows(const int row1, int *prow2) {
//   ---------
  bool rv=T; const int row2=*prow2;
  if ((row1<=0)|(row1>N)|(row2<0)|(row2>N)) { // row2==0 ok, indicates transform 1
    printf("\aError: Row must be from 1 to %d.\n\n", N); rv=F; noError=F;
  } else if (Odd&((row1==Mid)|(row2==Mid))) { reportError("Can't swap the middle row"); rv=F;
  } else if (row2!=0) {
    if (row1==row2) { reportError("Asked to swap a row with itself"); rv=F;
    } else if ((row1<=Half)!=(row2<=Half)) { // not on same side
      if (row2==(N+1-row1)) { /* row2 is row1 opposite */ *prow2=0; // do a transform 1
      } else { reportError("Rows must be on the same side of the square"); rv=F; }
    }
  }
  return rv;
} // checkRows

FILE *rfpOrig=NULL;
bool doAnotherXXform(FILE **pfp, int *row1, int *row2, bool *isRepeatXXform, bool *inputRows) {
//   ---------------
  printf("\nDo another transform on the transformed squares?\ninput y (yes) or n (no) or the row number(s): ");
  if (getYorRows(row1, row2)) {
    *pfp=openFile(wfpFname); if (*pfp!=NULL) { *isRepeatXXform=T; *inputRows=(*row1==0); return T; }
  }
  return F;
} // doAnotherXXform

bool doAnotherXform(FILE **pfp, int *row1, int *row2, bool *isRepeatXXform, bool *inputRows) {
//   --------------
  printf("\nDo another transform on the original squares?\ninput y (yes) or n (no) or the row number(s): ");
  if (getYorRows(row1, row2)) { *inputRows=(*row1==0); rewind(rfpOrig); *pfp=rfpOrig; *isRepeatXXform=F; return T; }
  return F;
} // doAnotherXform

bool doAnotherSize(bool *inputSize) {
//   -------------
  printf("\nTransform more squares?\ninput y (yes) or n (no) or the order of the squares: ");
  if (getYorSize(&N)) { *inputSize=(N==0); return T; } else { freeStore(); return F; }
} // doAnotherSize

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

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

int main() {
//  ----
  char buf[bufSize];
  bool anotherSize=T, inputSize=T, readError=F, writeError=F, ok=F; outputLocalTime();
  do {
    if (inputSize) { printf("\nInput the order of the squares: "); N=getInt(); }
    if (checkSize()) { initGlobals();
      if (allocateStore(N)) {
        FILE *rfp=openInput(buf);
        if (rfp!=NULL) {
          rfpOrig=rfp; bool anotherXform=T, inputRows=T;
          do { // transform on original file
            int row1, row2; bool transformDone=F, anotherXXform=T, isRepeatXXform=F;
            do { // transform on transformed file             
              anotherXXform=F; // until reset after below
              if (inputRows) { printf("\nInput the row number(s): "); getInts(&row1, &row2); inputRows=F; }
              time_t startTime=time(NULL);
              if ((noError=checkRows(row1, &row2))) {
                FILE *wfp=openOutput(buf, isRepeatXXform);
                if ((noError=(wfp!=NULL))) {
                  readError=!transformSquares(rfp, &wfp, row1, row2, isRepeatXXform, &writeError);
                  fcloseNULL(&wfp);
                  if ((transformDone=!(readError|writeError))) transformDone=!isRepeatXXform||renameOutput(&rfp);
                } // if (wfp!=NULL)
              }//if (checkRows 
              if ((!noError)&isRepeatXXform) fcloseNULL(&rfp); // Must close for each open to permit remove.
              reportElapsedTime(startTime);
              if (transformDone) {
                ok=T; anotherXXform=doAnotherXXform(&rfp, &row1, &row2, &isRepeatXXform, &inputRows);
              }
            } while (anotherXXform);
            anotherXform=!(readError||writeError)&&doAnotherXform(&rfp, &row1, &row2, &isRepeatXXform, &inputRows);
          } while (anotherXform);
          fcloseNULL(&rfpOrig);
        } // (rfp!=NULL)
      } // (allocateSquares())
    } // (checkSize())
    anotherSize=doAnotherSize(&inputSize);
  } while (anotherSize);
  printf("\nHit return to close the console.");
  while (T) if (getchar()=='\n') break; return ok ? EXIT_SUCCESS : EXIT_FAILURE;
} // main