/*
 *  File:    GetMagic.cpp
 *  Author:  S Harry White
 *  Created: 2009-12-28
 *  Updated: 2022-01-02
 *    Tidy code.
 * Updated: 2023-02-20
 *   Improve openInput and openOutput.
 */

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

const int maxNMagicConstant=2047; // max N before 32-bit unsigned overflow of MagicConstant
int N,             // The order of the square.
    N_1,           // N-1
    NN,            // N*N
    numNotMagic,
    numMagic,      // magic, other
    numNormal,     // normal magic
    numTotal,
    printNum,      // print a message at this number of squares
   **square=NULL;
typedef unsigned int Uint; Uint MagicConstant; // N*(NN+1)/2
const bool F=false, T=true; bool *numberUsed=NULL, readError;

void initGlobals() {
//  -----------
  N_1=N-1; NN=N*N; MagicConstant=(N&1) ? (NN+1)/2*N : N/2*(NN+1); readError=F;
  numNotMagic=0; numMagic=0; numNormal=0; numTotal=0; printNum=100000;
} // initGlobals
//=============================================== store ======================================================

void freeStore(int allocated) {
//   ---------
  if (square!=NULL) { for (int i=0; i<allocated; i++) free(square[i]); free(square); square=NULL; }
  if (numberUsed!=NULL) { free(numberUsed); numberUsed=NULL; }
} // freeStore

bool allocateStore() {
//   -------------
  bool ok; square=(int**) malloc(N*sizeof(int *)); ok=(square!=NULL);
  if (ok) {
    int allocated=N;
    for (int i=0; i<N; i++) {
      int *p=(int *) malloc(N*sizeof(int)); if (p==NULL)  {ok=F; allocated=i; break; } square[i]=p;
    }
    if (ok) { numberUsed=(bool*) malloc((NN+1)*sizeof(bool)); ok=(numberUsed!=NULL); }
    if (!ok) freeStore(allocated);
  }
  if (!ok)
    printf("\a\n\nERROR: Storage allocation failed.\n\n");
  return ok;
} // allocateStore
//================================================ type ============================================

enum magicType {notMagic, Magic, Normal}; magicType squareType;
magicType isMagic() {
//        -------
  int sumX, sumY, sumXY=0, sumYX=0, chkSum=0;
  for (int i=0; i<N; i++) {
    sumX=0; sumY=0;
    for (int j=0; j<N; j++) { sumX+=square[i][j]; sumY+=square[j][i]; } if (i==0) chkSum=sumX;
    if ((sumX!=chkSum)|(sumY!=chkSum)) return notMagic; sumXY+=square[i][N_1-i]; sumYX+=square[i][i];
  }
  if ((sumXY!=chkSum)|(sumYX!=chkSum)) return notMagic; return Magic;
} // isMagic

magicType isNormal(bool checkZeroBased) {
//        --------
  unsigned long magicSum=checkZeroBased ? MagicConstant-N : MagicConstant;
  int min=checkZeroBased ? 0 : 1, max=NN+min-1; Uint sumX, sumY, sumXY=0, sumYX=0, chkSum;

  for (int i=min; i<=max; i++) numberUsed[i]=F;
  for (int i=0; i<N; i++) {
    sumX=0; sumY=0;
    for (int j=0; j<N; j++) { const int tmp=square[i][j]; numberUsed[tmp]=T; sumX+=tmp; sumY+=square[j][i]; }
    if (i==0) chkSum=sumX; if ((sumX!=chkSum)|(sumY!=chkSum)) return notMagic;
    sumXY+=square[i][N_1-i]; sumYX+=square[i][i];
  }
  if ((sumXY!=chkSum)|(sumYX!=chkSum)) return notMagic; if (chkSum!=magicSum) return Magic;
  for (int i=min; i<=max; i++) if (!numberUsed[i]) return Magic; return Normal;
} // isNormal
//================================================ 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 getYorOrder(int *n) { // 'y' or 'n' or the order
//   -----------
  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 getSize() { int n=0; scanf_s("%d", &n); clearLine(getchar()); return n; }
//  -------

bool getFileName(char *buf, 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 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], *rFname=NULL; FILE *rfp=NULL;
  do {
    printf("\nEnter the name of the squares file: ");
    if (getFileName(buf, bufSize-4)) { rFname=buf; break; } 
    printf("\nTry 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

int fieldWidth;
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; return neg ? width+1 : width;
} // getWidth

int smallestRead, biggestRead;
bool readSquare(FILE *rfp) {
//   ----------
  int smallest=LONG_MAX, biggest=LONG_MIN;
  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; square[r][c]=tmp;
      } else {
        if ( (rv!=EOF)|(r!=0)|(c!=0) ) { readError=T; printf("\a\nError reading square from file.\n"); }
        return F;
      }
    }
  }
  if ( (smallest<0)|(biggest>NN) ) squareType=isMagic(); else squareType=isNormal(smallest==0);
  int sWidth=getWidth(smallest), bWidth=getWidth(biggest);
  fieldWidth=sWidth>bWidth ? sWidth : bWidth; smallestRead=smallest; biggestRead=biggest; return T;
} // readSquare
//================================================ output ==============================================

bool openDir(char *dir) {
//   -------
  char baseName[bufSize]; int sub=0; sprintf_s(baseName, bufSize, "Order%dSquares", N);
  strcpy_s(dir, bufSize, baseName);
  do {
    if (_mkdir(dir)!=-1) break;
    if (errno!=EEXIST) {
      const int msgSize=bufSize+50; char msg[msgSize];
      sprintf_s(msg, msgSize, "\a\nCan't make folder %s", dir); perror(msg); return F;
    }
    sprintf_s(dir, bufSize, "%s_%d", baseName, ++sub);
  } while (T);
  return T;
} // openDir

FILE *openOutput(const char *dir, char *buf, const char *fName) {
//    ----------
  FILE *wfp=NULL; sprintf_s(buf, outSize, "%s\\%s.txt", dir, fName);
  if (fopen_s(&wfp, buf, "w")!=0) {
    const int msgSize=outSize+50; char msg[msgSize];
    sprintf_s(msg, msgSize, "\a\nCan't open for write %s", buf); perror(msg);
  }
  return wfp;
} // openOutput

typedef bool (*t_Write)(FILE *wfp);

bool writeSquare1(FILE *wfp) {
//   ------------
  for (int r=0; r<N; r++) {
    if (fprintf(wfp, "%1d", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %1d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %2d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %3d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %4d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %5d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %6d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %7d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %8d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %9d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %10d", square[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", square[r][0])<0) return F;
    for (int c=1; c<N; c++) { if (fprintf(wfp, " %11d", square[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++) {
      int v=square[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 *wfpNM, FILE *wfpOM, FILE *wfpXM, bool *writeError) {
//   -----------
  FILE *wfp;
  if (squareType==Normal) { ++numNormal; wfp=wfpNM; }
  else if (squareType==Magic) { ++numMagic; wfp=wfpOM; }
  else if (squareType==notMagic) { ++numNotMagic; wfp=wfpXM; } else assert(F);
  if (++numTotal==printNum) {
    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;
  }
  bool ok;
  if ((N==5)&(smallestRead>=0)&(biggestRead<=NN)) ok=writeSquareOrder5(wfp);
  else ok=writeSquareFW[fieldWidth](wfp)&&(fputc('\n', wfp)!=EOF);
  *writeError=!ok; return ok;
} // writeSquare

void handleResult(char *dir, char *NMfname, char *OMfname, char *XMfname, bool writeError) {
//   ------------
  bool allOneKind=writeError;
  if (!writeError)
  if (numNotMagic==numTotal) { allOneKind=T;
    if (numTotal==1) printf("\nThe square is not magic.\n");
    else printf("\nNone of the %d squares is magic.\n", numTotal);
  } else if (numMagic==numTotal) { allOneKind=T;
    if (numTotal==1) printf("\nThe square is other magic.\n");
    else printf("\nAll %d are other magic squares.\n", numTotal);
  } else if (numNormal==numTotal) { allOneKind=T;
    if (numTotal==1) printf("\nThe square is normal magic.\n");
    else  printf("\nAll %d are normal magic squares.\n", numTotal);
  } else if (numNotMagic==0) {
    remove(XMfname);
    printf("\n%d square%s normal magic.\n%d square%s other magic.\n",
           numNormal, numNormal==1 ? " is" : "s are", numMagic, numMagic==1 ? " is" : "s are");
  } else if (numMagic==0) {
    remove(OMfname);
    printf("\n%d square%s normal magic.\n%d square%s not magic.\n",
           numNormal, numNormal==1 ? " is" : "s are", numNotMagic, numNotMagic==1 ? " is" : "s are");
  } else if (numNormal==0) {
    remove(NMfname);
    printf("\n%d square%s other magic.\n%d square%s not magic.\n",
           numMagic, numMagic==1 ? " is" : "s are", numNotMagic, numNotMagic==1 ? " is" : "s are");
  } else
    printf("\nThe number of normal magic squares is %d.\nThe number of other magic squares is %d.\n"
           "The number of not magic squares is %d.\n", numNormal, numMagic, numNotMagic);

  if (allOneKind) { remove(NMfname); remove(OMfname); remove(XMfname); _rmdir(dir); }
  else printf("Output files are in folder %s\n\n", dir);
} // handleResult
//=================================================== main ==============================================

bool checkN() {
//   ------
  if (N<=0) { printf("\aN must be a positive integer.\n"); return F; }
  else if (N>maxNMagicConstant) {
    printf("\aMax order for magic constant calculation is %d.\n",  maxNMagicConstant); return F;
  }
  return T;
} // checkN

bool doAnother(bool *inputSize, bool writeError) {
//   ---------
  if (writeError) { perror("\a\nError writing file"); return F; }
  printf("\nContinue? input y (yes) or n (no) or the order of the squares: ");
  if (getYorOrder(&N)) { *inputSize=(N==0); return T; } return F;
} // doAnother

void outputElapsedTime(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);
} // outputtElapsedTime

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

int _tmain() {
//  ------
  outputLocalTime(); bool another=T, inputSize=T, ok=F;
  const char *inputPrompt="\nInput the order of the squares: ";
  do {
    bool writeError=F; if (inputSize) { printf(inputPrompt); N=getSize(); }
    if (checkN()) {
      initGlobals(); FILE *rfp=openInput();
      if (rfp!=NULL) {
        time_t startTime=time(NULL); char dir[bufSize];
        if (openDir(dir)) {
          if (allocateStore()) {
            char NMBuf[outSize], OMBuf[outSize], XMBuf[outSize];
            FILE *wfpNM=openOutput(dir, NMBuf, "NormalMagic"),
                 *wfpOM=openOutput(dir, OMBuf, "OtherMagic"),
                 *wfpXM=openOutput(dir, XMBuf, "NotMagic");
            if ( (wfpNM!=NULL)&(wfpOM!=NULL)&(wfpXM!=NULL) ) {
              while (readSquare(rfp)) if (!writeSquare(wfpNM, wfpOM, wfpXM, &writeError)) break;
              fclose(wfpNM); fclose(wfpOM); fclose(wfpXM);
              if (!readError) { handleResult(dir, NMBuf, OMBuf, XMBuf, writeError); if (!writeError) ok=T; }
            } else {
	            if (wfpNM!=NULL) fclose(wfpNM); if (wfpOM!=NULL) fclose(wfpOM); if (wfpXM!=NULL) fclose(wfpXM);
            }
	          freeStore(N);
          }
        }
        fclose(rfp); outputElapsedTime(startTime);
      }
    }
    another=doAnother(&inputSize, writeError);
  } while (another);

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