/*
 *  File:    Composite.cpp
 *  Author:  S Harry White
 *  Created: 2011-09-06
 *  Updated: 2017-10-27
 *  Updated: 2020-11-20
 *    Accept order 2 squares.
 *  Updated: 2021-12-21
 *    Tidy code.
 */

/*
 *  Makes composite squares of order m*n from squares of order m and squares of order n.
 */

#include "stdafx.h"
#include <assert.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>

typedef unsigned int Uint;
const int startSize=25; int **n1Square=NULL, **n2Square=NULL, allocatedSize1, allocatedSize2;
const bool F=false, T=true; bool *numberUsed=NULL;
//============================================ store ===========================================

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

void freeStore() {
//   ---------
  freeSquare(&n1Square, allocatedSize1); freeSquare(&n2Square, allocatedSize2);
  allocatedSize1=0; allocatedSize2=0;
  if (numberUsed!=NULL) { free(numberUsed); numberUsed=NULL; }
} // 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 

void swapSquares() {
//   -----------
  int **p=n1Square; n1Square=n2Square; n2Square=p;
  const int x=allocatedSize1; allocatedSize1=allocatedSize2; allocatedSize2=x;
} // swapSquares

bool allocateStore(int size1, int size2) {
//   -------------
  bool ok=T;

  if (size1<startSize) size1=startSize; if (size2<startSize) size2=startSize;
  if ((size1>size2)!=(allocatedSize1>allocatedSize2)) swapSquares();
  if ((size1>allocatedSize1)|(size2>allocatedSize2)) {
    if (size1>allocatedSize1) {
      freeSquare(&n1Square, allocatedSize1);
      if (ok=allocateSquare(&n1Square, size1)) allocatedSize1=size1;
    }
    if (ok) {
      if (size2>allocatedSize2) {
        freeSquare(&n2Square, allocatedSize2);
        if (ok=allocateSquare(&n2Square, size2)) allocatedSize2=size2;
      }
    }
    if (ok) {
      const int size=max(size1, size2); free(numberUsed);
      numberUsed=(bool *) malloc((size*size+1)*sizeof(bool)); ok=(numberUsed!=NULL);
    }
    if (!ok) freeStore();
  }
  return ok;
} //allocateStore
//============================================== input ============================================

void clearLine(int c) { while (c!='\n') c=getchar(); }
//   ---------

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

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

bool getY() {
//   ----
  int c; do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n'));
  clearLine(c); return (c=='Y')|(c=='y');
} // getY

const int bufSize=1024; int smallestRead, biggestRead; bool readError, zeroBased;

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 *which) {
//    ---------
  char buf[bufSize], *rFname=NULL; FILE *rfp=NULL;

  do {
    printf("\nEnter the name of the %s squares file: ", which);
    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); readError=T;
    }
  }
  return rfp;
} // openInput

bool readSquare(FILE *rfp, const int n, int **square) {
//   ----------
  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) ) { printf("\a\nError reading square from file.\n"); readError=T; }
        return F;
      }
    }
  }
  smallestRead=smallest; biggestRead=biggest; zeroBased=smallestRead==0; return T;
} // readSquare
//================================================= output ==============================================

bool changeDir(char *toDir) {
//   ---------
  if (_chdir(toDir)!=0) {
    char msg[bufSize]; sprintf_s(msg, bufSize, "Can't open folder %s", toDir); perror(msg); return F;
  }
  return T;
} // changeDir

bool openDir(const int n1, const int n2) {
//   -------
  char buf[bufSize], baseName[bufSize]; int sub=0;
  sprintf_s(baseName, bufSize, "%d-%d-CompositeSquares", n1, n2); strcpy_s(buf, bufSize, baseName);
  do {
    if (_mkdir(buf)==0) break;
    if (errno!=EEXIST) {
      char msg[bufSize];
      sprintf_s(msg, bufSize, "Can't make folder %s", buf); perror(msg); return F;
    }
    sprintf_s(buf, bufSize, "%s_%d", baseName, ++sub);
  } while (T);
  if (changeDir(buf)) { printf("Output files are in folder %s\n\n", buf); return T; } return F;
} // openDir

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

int smallestRead1, smallestRead2, biggestRead1, biggestRead2;
int fieldWidth(const int n1n1) { // including 1 space
//  ----------
  int fw, max12, tmp; const bool neg=(smallestRead1<0)|(smallestRead2<0);

  max12=smallestRead1+n1n1*smallestRead2; if (max12<0) max12=-max12;
  tmp=smallestRead1+n1n1*biggestRead2; if (tmp<0) tmp=-tmp; if (tmp>max12) max12=tmp;
  tmp=biggestRead1+n1n1*smallestRead2; if (tmp<0) tmp=-tmp; if (tmp>max12) max12=tmp;
  tmp=biggestRead1+n1n1*biggestRead2; if (tmp<0) tmp=-tmp; if (tmp>max12) max12=tmp;

  if (max12<=1) { fw=neg ? 3 : 2; }
  else { int i=max12, width=1; while ((i=i/10)!=0) ++width; if (neg) ++width; fw=width+1; }
  return fw;
} // fieldWidth

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; }
bool fprintFWb(FILE *fp, const int i) { return fprintf(fp, "%11d", i)>0; }

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

void closeFiles(FILE **rfp1, FILE **rfp2, FILE **wfp) {
//   ----------
  if (*rfp1!=NULL) { fclose(*rfp1); *rfp1=NULL; }
  if (*rfp2!=NULL) { fclose(*rfp2); *rfp2=NULL; }
  if (*wfp !=NULL) { fclose(*wfp);  *wfp =NULL; }
} // closeFiles

bool directoryChanged; int fileNumber;
bool openFiles(const int n1, const int n2,
//   ---------
               FILE **rfp1, FILE **rfp2, FILE **wfp) {
  directoryChanged=F; readError=F; *rfp1=openInput("first"); *rfp2=NULL; *wfp=NULL;
  if (*rfp1==NULL) return F; *rfp2=openInput("second");
  if (*rfp2==NULL) { fclose(*rfp1); *rfp1=NULL; return F; }

  if (openDir(n1, n2)) {
    directoryChanged=T; *wfp=openOutput(fileNumber=1);
    if (*wfp==NULL) { closeFiles(rfp1, rfp2, wfp); return F; } return T;
  }
  return F;
} // openFiles

enum magicType { notMagic, otherMagic, normalMagic };
Uint MagicConstant1, MagicConstant2;
magicType isMagic(const int n, const int which) {
//        -------
  int **x=which==1 ? n1Square : n2Square,
      sumX, sumY, sumXY=0, sumYX=0, chkSum=0; const int nm1=n-1;

  for (int i=0; i<n; i++) {
    sumX=0; sumY=0; for (int j=0; j<n; j++) { sumX+=x[i][j]; sumY+=x[j][i]; }
	  if (i==0) chkSum=sumX; if ((sumX!=chkSum)|(sumY!=chkSum)) return notMagic;
    sumXY+=x[i][nm1-i]; sumYX+=x[i][i];
  }
  if ((sumXY!=chkSum)|(sumYX!=chkSum)) return notMagic; return otherMagic;
} // isMagic

magicType isNormal(int n, int which) {
//        --------
  const int min=zeroBased ? 0 : 1, max=n*n+min-1, nm1=n-1;
  int **x=which==1 ? n1Square : n2Square, chkSum=0, sumX, sumY, sumXY=0, sumYX=0;
  Uint magicConstant=which==1 ? MagicConstant1 : MagicConstant2; if (zeroBased) magicConstant-=n;

  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=x[i][j]; numberUsed[tmp]=T; sumX+=tmp; sumY+=x[j][i]; }
    if (i==0) chkSum=sumX; if ((sumX!=chkSum)|(sumY!=chkSum)) return notMagic;
    sumXY+=x[i][nm1-i]; sumYX+=x[i][i];
  }
  if ((sumXY!=chkSum)|(sumYX!=chkSum)) return notMagic;
  if (chkSum!=magicConstant) return otherMagic;
  for (int i=min; i<=max; i++) if (!numberUsed[i]) return otherMagic; return normalMagic;
} // isNormal

bool checkSquare(const int n, const int which, bool *Latin) {
//   -----------
  *Latin=((smallestRead==0)&&(biggestRead==(n-1)))||((smallestRead==1)&&(biggestRead==n));
  if (!*Latin) {
    magicType mt;
    if ( (smallestRead<0)|(biggestRead>n*n) ) mt=isMagic(n, which); else mt=isNormal(n, which);
    if (mt==notMagic) printf("Square %d is not magic.\n", which);
    else if (mt==otherMagic) printf("Square %d is not normal magic.\n", which);
  }
  return T;
} // checkSquare

void zerobase(int **x, const int n) {
//   --------
  for (int r=0; r<n; ++r) for (int c=0; c<n; ++c) --x[r][c]; --smallestRead2; --biggestRead2; zeroBased=T;
} // zerobase

const int bytesPerFile=10000000;
bool makeSquares(const int n1, const int n2, FILE **prfp1, FILE **prfp2, FILE **pwfp) {
//   -----------
  FILE *rfp1=*prfp1, *rfp2=*prfp2, *wfp=*pwfp;
  const int n=n1*n2, n1n1=n1*n1; int count1=0, bytesWritten=0; bool Latin1=F, Latin2=F;

  while (readSquare(rfp1, n1, n1Square)) {
    smallestRead1=smallestRead; biggestRead1=biggestRead;
    if (!checkSquare(n1, 1, &Latin1)) return T; /* no write error */ ++count1; 
    while (readSquare(rfp2, n2, n2Square)) {
      smallestRead2=smallestRead; biggestRead2=biggestRead;
      if (!zeroBased) zerobase(n2Square, n2);
      if ( (count1==1)&&(!checkSquare(n2, 2, &Latin2)) ) return T; // no write error

      const int fw=fieldWidth(n1n1), fw0=fw-1, add=Latin1&Latin2 ? n1 : n1n1;
      if (fw>maxFieldWidth) { printf("Output format overflow.\n"); return F; }
      if (bytesWritten >= bytesPerFile) {
        fclose(wfp); *pwfp=openOutput(++fileNumber);
        if (*pwfp==NULL) { closeFiles(prfp1, prfp2, pwfp); return F; } bytesWritten=0; wfp=*pwfp;
      }
        
      for (int i=0; i<n2; ++i) {
        for (int r=0; r<n1; ++r) {
          for (int j=0; j<n2; ++j) {
            const int mul=n2Square[i][j]; int cStart;
            if (j==0) {
              if (!fprintFW[fw0](wfp, n1Square[r][0]+(add*mul))) return F; bytesWritten+=fw0; cStart=1;
            } else cStart=0;
            for (int c=cStart; c<n1; ++c) {
              if (!fprintFW[fw](wfp, n1Square[r][c]+(add*mul))) return F; bytesWritten+=fw;
            }
          }
          if (fputc('\n', wfp)==EOF) return F; ++bytesWritten;
        }
      }
      if (fputc('\n', wfp)==EOF) return F; ++bytesWritten;
    } // while (readSquare(rfp2
    rewind(rfp2);
  } // while (readSquare(rfp1
  return T;
} // makeSquares

void getMagicConstants(const int n1, const int n2) {
//   -----------------
   MagicConstant1=n1*(n1*n1+1)/2; MagicConstant2=n2*(n2*n2+1)/2;
} // getMagicConstants
//============================================== main ==============================================

bool validOrder(const int n) {
//   ----------
  if (n<=0) { printf("\aERROR: Order %d is not a positive integer.\n", n); return F;
  //} else if (n==2) {
  //  printf("\aERROR: There is no magic square of order 2.\n"); return F;
  } return T;
}// validOrder

bool validOrders(const int m, const int n) {
//   -----------
  if (validOrder(m)&&validOrder(n)) {
    const int mn=m*n, q=LONG_MAX/mn;
    if ((mn<0)|(mn>q)) {
      printf("\aERROR: Integer overflow for (%d x %d) squared.\n", m, n); return F;
    }
    return T;
  }
  return F;
}// validOrders

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

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

void checkContinue(bool *another) {
//   -------------
  if (*another) { printf("\nContinue? input y (yes) or n (no): "); *another=getY(); }
} // checkContinue

int main() {
//  ----
  bool another=T, writeError=F, ok=F; outputLocalTime();
  const char *input_order="\nInput the order of the %s squares: ";
  do {
    int n1=0, n2=0; printf(input_order, "first"); n1=getSize();
    printf(input_order, "second"); n2=getSize();
    if (validOrders(n1, n2)) {
      getMagicConstants(n1,n2);
      if (allocateStore(n1, n2)) {
        FILE *rfp1, *rfp2, *wfp;
        if (openFiles(n1, n2, &rfp1, &rfp2, &wfp)) {
          time_t startTime=time(NULL);
          writeError=!makeSquares(n1, n2, &rfp1, &rfp2, &wfp);
          closeFiles(&rfp1, &rfp2, &wfp); printElapsedTime(startTime);
        }
      } else { printf("\a\nERROR: Storage allocation failed.\n"); }
      if (writeError) { perror("\a\nError writing file"); another=F; }
      else { ok=T; another=(directoryChanged&&changeDir(".."))||readError; }
    } // if (validOrders
    checkContinue(&another);
  } while (another);
  freeStore();
  printf("\nPress a key to close the console.");
  while (!_kbhit()) Sleep(250); return ok?EXIT_SUCCESS:EXIT_FAILURE;
} // main