/*
 * File:    AssocPan.cpp
 * Author:  S Harry White
 * Created: 2012-02-13
 * Updated: 2021-12-05
 *   Tidy code.
 * Updated: 2023-01-24
 *   Tidy code with procedure check_txt.
 * Updated: 2023-02-12
 *   Improve openInput and openOutput.
 */

/*
 * For doubly even orders, transforms associative magic squares to
 * pandiagonal complete magic squares and vice versa.
 */

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

const int startSize=32;
int N,               // The order of the square.
    Nm1,             // N-1
    Nm2,             // N-2
    Nd2,             // N/2
    Nd2m1,           // N/2-1 
    N3d4,            // 3*N/4
    N3m2d2,          // (3*N-2)/2
    NN,              // N*N
    NNm1,            // NN-1
    NNp1,            // NN+1

    magicConstant,   // 1..NN
    magicSum,        // 0..NN-1 or 1..NN
    Sum2,            //         "
    Sum4,            //         "

    **xSquare=NULL, **tSquare=NULL, allocatedSize, squareNum;
bool zeroBase, *numberUsed=NULL; const bool F=false, T=true;

void initGlobals() {
//  -----------
    Nm1=N-1; Nm2=N-2; Nd2=N/2; Nd2m1=Nd2-1; N3d4=3*N/4; N3m2d2=(3*N-2)/2;
    NN=N*N; NNm1=NN-1; NNp1=NN+1; magicConstant=Nd2*(NN+1); squareNum=0;
} // initGlobals
//==================================== allocate store ========================================

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

void freeUsed() { if (numberUsed!=NULL) { free(numberUsed); numberUsed=NULL; } }
//   --------

void freeStore() {
//   --------- 
  freeSquare(&xSquare, allocatedSize);  freeSquare(&tSquare, allocatedSize);
  freeUsed(); allocatedSize=0;
} // freeStore

bool allocateSquare(int*** square, const int size) {
//   --------------
  bool ok; *square=(int**) malloc(size*sizeof(int*)); ok=(*square!=NULL);
  if (ok) {
    int numAllocated=size;
    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) freeSquare(square, numAllocated);
  }
  return ok;
} // allocateSquare 

bool allocateUsed(const int size) { return ((numberUsed=(bool*) malloc(size*sizeof(bool)))!=NULL); }
//   ------------

bool allocateStore(int size) {
//   -------------
  bool ok=T; if (size<startSize) size=startSize;
  if (size>allocatedSize) {
    freeStore();
    if (ok=allocateSquare(&tSquare, size))
      if (ok=allocateSquare(&xSquare, size))
        if (ok=allocateUsed(size*size+1)) allocatedSize=size;
     if (!ok) freeStore();
  }
  return ok;
} //allocateStore
//=========================================== output =========================================

int fieldWidth;
int getWidth(int number) { int width=1, i=number; while ((i=i/10)!=0) ++width; return width; }
//  --------

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

bool fprintFW1(FILE *fp, const int i) { return fprintf(fp, "%1d",  i)>0; }
bool fprintFW2(FILE *fp, const int i) { return fprintf(fp, "%2d",  i)>0; }
bool fprintFW3(FILE *fp, const int i) { return fprintf(fp, "%3d",  i)>0; }
bool fprintFW4(FILE *fp, const int i) { return fprintf(fp, "%4d",  i)>0; }
bool fprintFW5(FILE *fp, const int i) { return fprintf(fp, "%5d",  i)>0; }
bool fprintFW6(FILE *fp, const int i) { return fprintf(fp, "%6d",  i)>0; }
bool fprintFW7(FILE *fp, const int i) { return fprintf(fp, "%7d",  i)>0; }
bool fprintFW8(FILE *fp, const int i) { return fprintf(fp, "%8d",  i)>0; }
bool fprintFW9(FILE *fp, const int i) { return fprintf(fp, "%9d",  i)>0; }
bool fprintFWa(FILE *fp, const int i) { return fprintf(fp, "%10d", i)>0; }

static t_fprintFW fprintFW[]={ NULL,
  fprintFW1, fprintFW2, fprintFW3, fprintFW4, fprintFW5,
  fprintFW6, fprintFW7, fprintFW8, fprintFW9, fprintFWa
};
const int maxFieldWidth=10;

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

const int bufSize=1024, outSize=bufSize+50;
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

char fnA[outSize], fnP[outSize];
FILE *openOutput(const char *inFname, char *add) {
//    ----------
  FILE *wfp=NULL; int sub=0; const int baseSize=bufSize+25; char baseName[baseSize], buf[outSize];
  sprintf_s(baseName, baseSize, "%s%s", inFname, add); 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 (fopen_s(&wfp, buf, "w")==0) strcpy_s(add[0]=='A' ? fnA : fnP, outSize, buf);
  else {
    char msg[outSize+50];
    sprintf_s(msg, outSize+50, "\a\nCan't open for write %s", buf); perror(msg);
  }
  return wfp;
} // openOutput
//============================================= check =============================================

bool allNumbersUsed(int **x) {
//   --------------
  for (int i=0; i<=NN; ++i) numberUsed[i]=F;
  for (int r=0; r<N; ++r) for (int c=0; c<N; ++c) {
    const int t=x[r][c]; if ((t>=0)&(t<=NN)) numberUsed[t]=T;
  }
  for (int i=1; i<NN; ++i) if (!numberUsed[i]) return F;
  if (numberUsed[0]==numberUsed[NN]) return F;
  return T;
} // allNumbersUsed

enum magicType { notMagic, normalSemiMagic, otherSemiMagic, normalMagic, otherMagic };
const magicType basicType[2][2]={ {otherSemiMagic, normalSemiMagic}, {otherMagic, normalMagic} };

magicType isNormal(int **x) {
//        --------
  int sumX, sumY, sumXY=0, sumYX=0, chkSum;

  for (int r=0; r<N; ++r) {
    sumX=0; sumY=0;
    for (int c=0; c<N; ++c) { sumX+=x[r][c]; sumY+=x[c][r]; } if (r==0) chkSum=sumX;
    if ((sumX!=chkSum)|(sumY!=chkSum)) return notMagic;
    sumXY+=x[r][Nm1-r]; sumYX+=x[r][r];
  }
  return basicType[(sumXY==chkSum)&&(sumYX==chkSum)][allNumbersUsed(x)&&(chkSum==magicSum)];
} // isNormal

bool bell=T; char fmt[bufSize];
bool programError(char *s) {
//   ------------
  sprintf_s(fmt, bufSize,
    "%sProgram error: %s.\nPlease report by e-mail.\n", bell ? "\a" : "", s); 
  printf(fmt, squareNum); return bell=F;
} // programError

bool isComplete(int **x) {
//   ----------
  for (int r=0; r<Nd2m1; ++r) { // no need to check last pair in each diag
    const int i=r+Nd2;
    for (int c=0; c<N; ++c) {
      int j=c+Nd2; j=j<N ? j : j-N; if ((x[r][c]+x[i][j])!=Sum2) return F;
    }
  }
  return T;
} // isComplete

bool isPandiagonal(int **x) {
//   -------------
  // Main diagonals already checked in isNormal.
  for (int i=1; i<N; ++i) {
    int sumYX=0, sumXY=0;
    for (int r=0, c=i; r<N; ++r, ++c) {
      const int cModn=c<N ? c : c-N; sumYX+=x[r][cModn]; sumXY+=x[r][Nm1-cModn];
    }
    if ((sumYX!=magicSum)|(sumXY!=magicSum)) return F;
  }
  return T;
} // isPandiagonal

bool isPanComplete(int **x) {
//   -------------
  return (isNormal(x)==normalMagic)&&isPandiagonal(x)&&isComplete(x);
} // isPanComplete

bool checkSquareP(int **x) {
//   ------------
  if (!isPanComplete(x))
    return programError("Output square %d is not pandiagonal complete");
  return T;
} // checkSquareP

bool isAssociative(int **x) {
//   -------------
  if (isNormal(x)==normalMagic) {
    for (int r=0; r<Nd2; ++r) for (int c=0; c<N; ++c)
      if ((x[r][c]+x[Nm1-r][Nm1-c])!=Sum2) return F;
    return T;
  }
  return F;
} // isAssociative

bool checkSquareA(int **x) {
//   -------------
  if (!isAssociative(x))
    return programError("Output square %d is not associative");
  return T;
} // checkSquareA

bool inAssociative, readError;
void assignGlobals() {
//   -------------
  if (zeroBase) { fieldWidth=getWidth(NNm1); magicSum=magicConstant-N; Sum2=NNm1; }
  else {          fieldWidth=getWidth(NN);   magicSum=magicConstant;   Sum2=NNp1; }
  Sum4=Sum2+Sum2;
} // assignGlobals

bool checkSquare(const int smallest, const int biggest) {
//   -----------
  if ((smallest<0)|(biggest>NN)) {
    printf("\a\nError: Invalid number %d in square %d.\n\n",
            (smallest<0) ? smallest : biggest, squareNum);
    return F;
  } else {
    //return inAssociative=T; // no check
    if (!((inAssociative=isAssociative(xSquare))||isPanComplete(xSquare))) {
      printf("\a\nError: Square %d is neither associative "
             "nor pandiagonal complete.\n\n", squareNum);
      return F;
    }
  }
  return T;
} // checkSquare
//================================================ input ==========================================

bool readSquare(FILE *rfp) {
//   ----------
  const int n=N; int smallest=LONG_MAX, biggest=LONG_MIN; inAssociative=F;
  for (int r=0; r<n; r++) {
    for (int c=0; c<n; 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 square %d from file.\n", squareNum+1);
        }
        return F;
      }
    }
  }
  ++squareNum; zeroBase=(smallest==0); assignGlobals();
  return checkSquare(smallest, biggest);
} // readSquare

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 getYorInt(int *n) { // Yes or No or integer
//   ---------
  bool result=F; int c; *n=-1;

  do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n') );
  if ( (c=='Y')|(c=='y') ) result=T;
  else if ( (c!='N')&(c!='n') )
    if ( ('0'<=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;
} // getYorInt

int getInt() { int n=0; scanf_s("%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

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

void reverseRowsRightHalf(int **x) {
//    -------------------
  for (int r=0; r<N; ++r) for (int c=Nd2; c<N3d4; ++c) {
    const int t=x[r][c]; x[r][c]=x[r][N3m2d2-c]; x[r][N3m2d2-c]=t;
  }
} // reverseRowsRightHalf

void  reverseColumnsBottomHalf(int **x) {
//    ------------------------
  for (int c=0; c<N; ++c) for (int r=Nd2; r<N3d4; ++r) {
    const int t=x[r][c]; x[r][c]=x[N3m2d2-r][c]; x[N3m2d2-r][c]=t;
  }
} // reverseColumnsBottomHalf

#define xForm(iX, jX) for (int i=0; i<N; ++i) for (int j=0; j<N; ++j) t[iX][jX]=x[i][j]
#define xCopy() for (int i=0; i<N; ++i) for (int j=0; j<N; ++j) x[i][j]=t[i][j]

void putInFrenicleForm(int **x) {
//   -----------------
  bool inFrenicleForm=F; int **t=tSquare,
       minC=min(min(x[0][0], x[0][Nm1]), min(x[Nm1][0], x[Nm1][Nm1]));

  if (x[0][0]==minC) {
    if (x[0][1]<x[1][0]) inFrenicleForm=T;
    else xForm(j,i);                                 // YX
  } else if (x[0][Nm1]==minC) {
    if (x[1][Nm1]<x[0][Nm2]) xForm(Nm1-j,i);         // -90
    else xForm(i,Nm1-j);                             // Y   
  } else if (x[Nm1][Nm1]==minC) {
    if (x[Nm1][Nm2]<x[Nm2][Nm1]) xForm(Nm1-i,Nm1-j); // 180
    else xForm(Nm1-j,Nm1-i);                         // XY
  } else { // x[Nm1][0]==minC
    if (x[Nm2][0]<x[Nm1][1]) xForm(j,Nm1-i);         // 90
    else xForm(Nm1-i,j);                             // X
  }
  if (!inFrenicleForm) xCopy();
} // putInFrenicleForm

// Rotates the square if necessary to put the quarter with the smallest
// corner number at top left.
void orientSquare(int **x) {
//   ------------
  int **t=tSquare, sm=x[0][0], smNW, smNE, smSE, smSW;

  if (x[0][Nd2m1]<sm) sm=x[0][Nd2m1]; if (x[Nd2m1][0]<sm) sm=x[Nd2m1][0];
  if (x[Nd2m1][Nd2m1]<sm) sm=x[Nd2m1][Nd2m1]; smNW=sm;

  sm=x[0][Nd2];
  if (x[0][Nm1] <sm) sm=x[0][Nm1]; if (x[Nd2m1][Nd2]<sm) sm=x[Nd2m1][Nd2];
  if (x[Nd2m1][Nm1]<sm) sm=x[Nd2m1][Nm1]; smNE=sm;

  sm=x[Nd2][Nd2];
  if (x[Nd2][Nm1]<sm) sm=x[Nd2][Nm1]; if (x[Nm1][Nd2]<sm) sm=x[Nm1][Nd2];
  if (x[Nm1][Nm1]<sm) sm=x[Nm1][Nm1]; smSE=sm;

  sm=x[Nd2][0];
  if (x[Nd2][Nd2m1]<sm) sm=x[Nd2][Nd2m1]; if (x[Nm1][0]    <sm) sm=x[Nm1][0];
  if (x[Nm1][Nd2m1]<sm) sm=x[Nm1][Nd2m1]; smSW=sm;

  sm=min( min(smNW,smNE), min(smSE,smSW) );
  if (sm!=smNW) {
         if (sm==smNE)    xForm(Nm1-j,i);     // -90
    else if (sm==smSE)    xForm(Nm1-i,Nm1-j); // 180
    else /* (sm==smSW) */ xForm(j,Nm1-i);     //  90
    xCopy();
  }
}  // orientSquare

int numA, numP;
bool getSquare(FILE *wfpA, FILE *wfpP) {
//   ---------
  int **x=xSquare; bool good=F, write=F; orientSquare(x);
  reverseColumnsBottomHalf(x); reverseRowsRightHalf(x);
  //write=writeSquare(stdout, x); return T; // no check
  putInFrenicleForm(x);
  if (inAssociative)
       { good=checkSquareP(x); write=writeSquare(wfpP, x); ++numP; }
  else { good=checkSquareA(x); write=writeSquare(wfpA, x); ++numA; }
  return good&write;
} // getSquare
//================================================ main ===============================================

bool checkN() {
//   ------
  if ((N<=0)|((N&3)!=0)) {
    printf("\aOrder %d is not a positive doubly even order.\n\n", N); return F;
  }
  return T;
} // checkN

void checkFilesWritten() {
//   -----------------
  if (numA!=0) printf("Output file is %s\nNumber of associative squares: %d\n", fnA, numA);
  else remove(fnA);
  if (numP!=0) printf("Output file is %s\nNumber of pandiagonal complete squares: %d\n", fnP, numP);
  else remove(fnP);
} // checkFilesWritten

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

bool doAnother(bool getError, bool *inputSize) {
//   ---------
  if (!getError) {
    printf("\nContinue? input y (yes) or n (no) or the order of the squares: ");
    if (getYorInt(&N)) { *inputSize=(N<0); return T; }
  }
  freeStore(); return F;
} // doAnother

int main() {
//  ----
  bool another=T, inputSize=T, ok=T; outputLocalTime();
  do { // for each input file
    if (inputSize) { printf("\nInput the order of the squares: "); N=getInt(); }
    if (checkN()) {
      initGlobals();
      if (allocateStore(N)) {
        char ibuf[bufSize], obuf[outSize]; FILE *rfp=openInput(ibuf);
        if (rfp!=NULL) {
          time_t startTime=time(NULL);
          stripName(ibuf, obuf); FILE *wfpA=openOutput(obuf, "A");
          if (wfpA!=NULL) {
            FILE *wfpP=openOutput(obuf, "P");
            if (wfpP!=NULL) {
              squareNum=0; numA=0; numP=0; bell=T;
              while (readSquare(rfp)) if (!(ok=getSquare(wfpA, wfpP))) break;
              fclose(wfpP); 
            } // if (wfpP!=NULL)       
            fclose(wfpA); checkFilesWritten();
          } // if (wfpA!=NULL)
          reportElapsedTime(startTime); fclose(rfp);
        } // (rfp!=NULL)
      } // (allocateStore())
    } // (checkN
    another=doAnother(!ok, &inputSize);
  } while (another);
  printf("\nPress a key to close the console"); while (!_kbhit()) Sleep(250);
  return ok?EXIT_SUCCESS:EXIT_FAILURE;
} // main