/*
 *  File:    BorderedSquare1.cpp
 *  Author:  S Harry White
 *  Created: 2009-02-15
 *  Updated: 2021-12-15
 *    Tidy code.
 *  Updated: 2022-12-31
 *    Changed to compile with g++ on macOS.
 */

/*
 *  Makes a bordered magic square of order n using an algorithm developed for the HP-41C pocket calculator.
 */

#include <curses.h>
#include <errno.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>

const bool F=false, T=true; const int maxN=1000;
#define bufSize 1024
//================================================= input =================================================

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

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("%d", &n); clearLine(getchar()); return n; }
//  -------
//================================================= output ================================================

bool openDir() {
//   -------
  const char *baseName="./BorderedSquare"; char buf[bufSize], msg[bufSize+50]; int sub=0;
  strcpy(buf, baseName);
  do {
    if (mkdir(buf, 0775)==-1) {
      if (errno!=EEXIST) {
        snprintf(msg, bufSize+50, "Can't make folder %s", buf); perror(msg); return F;
      }
      snprintf(buf, bufSize, "%s_%d", baseName, ++sub);
    } else break;
  } while (T);
  if (chdir(buf)==-1) {
    snprintf(msg, bufSize+50, "Can't open folder %s", buf); perror(msg); return F;
  }
  printf("Output files are in folder %s\n", buf); return T;
} // openDir

FILE *open_File(const int n) {
//    ---------
  char buf[bufSize]; FILE *wfp=NULL; snprintf(buf, bufSize, "./Order%d.txt", n);
  wfp=fopen(buf, "a");
  if (wfp==NULL) {
    char msg[bufSize+50];
    snprintf(msg, bufSize+50, "\a\nCan't open for write %s", buf); perror(msg);
  }
  return wfp; 
} // open_File

int fieldWidth(const int n) {
//  ----------
  if (n==1) return 1; int i=n*n, width=1; while ((i=i/10)!=0) ++width;
  if (width>7) width=7; /* limited by fprintFW7 */ return width;
} // 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; }

static t_fprintFW fprintFW[]=
  { NULL, fprintFW1, fprintFW2, fprintFW3, fprintFW4, fprintFW5, fprintFW6, fprintFW7 };
//================================================= make =============================================

// For a square of even order n, computes the bones number for the cell given by row and col.
double get_even(double n, double row, double col) {
//     --------
  const double nplus1=n+1, mid=(n+1)/2; bool chs=F; // change sign
  double x=fabs(mid-row), y=fabs(mid-col), result=0;

  if (row==col) { /* NW-SE diagonal */ chs=(row>mid); y=(x==1.5) ? 2 : 0; }
  else {
    if (mid<col) { // E side
      if (col<row) { // below SE diagonal
        if (y==0.5) { /* S column east of center */ y=(x==1.5) ? 0 : ((int)x%2==0) ? -x+0.5 : x+x; }
        else { /* rest of SSE corner */ chs=((int)y%2==0); y=x+y; }
      } else { // above SE diagonal
        if ((row+col)==nplus1) { /* NE diagonal */ y=(x==0.5) ? 3 : 1; }
        else {
          if ((row+col)>nplus1) { /* below NE diagonal */ double tmp=y; y=x; x=tmp;
            if (mid<row) { // below center
              if (y==0.5) { /* E row south of center */ chs=(x==3.5);
                y=(x==1.5) ? -2 : ((int)x%2==0) ? x+x : -x+0.5;
              } else { /* rest of ESE corner */ chs=((y==1.5)||((int)(x+y)%2==0)); y=y+0.5; }
            } else { // above center
              if (y==0.5) { /* E row north of center */ chs=(x==1.5)||((int)x%2==0); y=(x==1.5) ? -3 : x+0.5;
              } else { /* rest of ENE corner */
                chs=(y==2.5) ? (int)x%2==0 : (y==3.5) ? (int)x%2!=0 : (int)y%2==0; y=-y+0.5;
              }
            }
          } else { // above NE diagonal
            if (y==0.5) { /* N column east of center */ chs=T; y=((int)x%2==0) ? -x+0.5 : x+x; }
            else { /* rest of NNE corner */ chs=((int)y%2!=0); y=x+y; }
          }
        }
      }
    } else { // W side
      if (col<row) { // below NW diagonal
        if ((row+col)==nplus1) { /* SW diagonal */ chs=T; y=(x==0.5) ? 3 : 1; }
        else {
          if ((row+col)>nplus1) { /* SSW corner */ chs=((int)y%2!=0); y=(x==1.5) ? 3 : -x-x+y+0.5; }
          else { /* above SW diagonal */ double tmp=y; y=x; x=tmp;
            if (mid<row) { // below center
              if (y==0.5) { /* W row south of center */ chs=(x!=1.5)&(x!=3.5);
                y=(x==1.5) ? -3 : ((int)x%2==0) ? x+x : -x+0.5;
              } else { /* rest of WSW corner */ chs=(y!=1.5)&&((int)(x+y)%2!=0); y=y+0.5; }
            } else { // above center
              if (y==0.5) { /* W row north of center */ chs=((int)x%2!=0); y=(x==1.5) ? -2 : x+0.5;
              } else { // rest of WNW corner
                chs=(y==2.5) ? (int)x%2!=0 : (y==3.5) ? (int)x%2==0 : (int)y%2!=0; y=-y+0.5;
              }
            }
          }
        }
      } else { /* NNW corner */ chs=(int)y%2==0; y=(x==1.5) ? 0 : -x-x+y+0.5; }
    }
  }
  result=2*(x*x)+y; if (chs) result=-result; return result;
} //get_even

// For an odd order square, computes the bones number for the cell given by rrel and crel, where:
//                   rrel=row-(n+1)/2            crel=column-(n+1)/2
int get_odd(int rrel, int crel) {
//  -------
  int x=rrel, y=crel; bool chs=F; // change sign

  if (x==y) { // NW-SE diagonal
    x=crel; crel=0; if (x!=0) { /* not center */ chs=(x>0); /* SE diagonal */ x=1; }
  } else { // not NW-SE diagonal
    int tmp; chs=(x>y); /* below NW-SE diagonal */ tmp=y; y=x; x=tmp;
    if (x==0) { /* middle column N-S */ chs=!chs; x=2; }
    else {
      if (x>0) { // E side
        if (chs) { /* SSE corner */ chs=F; x=1;
        } else { /* not SSE corner */ x=x+y;
          if (x==0) { /* NE diagonal */ crel=0; x=-1;
          } else { // not NE diagonal
            if (x>0) { /* below NE diagonal */ x=rrel; rrel=crel; crel=x;
              if (x>0) { /* ESE corner */ chs=T; x=2; } else { /* middle row E or ENE corner */ x=0; }
            } else { /* NNE corner */ chs=T; x=1; }
          }
        }
      } else { // W side
        if (chs) { /* below NW-SE diagonal */ x=x+y;
          if (x==0) { /* SW diagonal */ crel=0; x=-1; }
          else { // not SW diagonal
            if (x>0) { /* SSW corner */ x=-1; }
            else { /* above SW diagonal */ x=rrel; rrel=crel; crel=x;
              if (x>0)  { /* WSW corner */ chs=F; x=2; } else { /* middle row W or WNW corner */ x=0; }
            }
          }
        } else { /*NNW corner */ x=-1; }
      }
    }
  }
  x=x+(2*((rrel*rrel)+crel)); if (chs) x=-x; return x;
} //get_odd

bool make_even(double n, FILE *wfp, bool *isCorrect) {
//   ---------
  const double midc=((n*n)+1)/2; const int fw=fieldWidth((int)n);
  double sumY[maxN+1], sumXY=0, sumYX=0; for (int col=1; col<=n; col++) sumY[col]=0;

  for (int row=1; row<=n; row++) {
    double sumX=0;
    for (int col=1; col<=n; col++) {
      const double value=get_even(n, row, col); sumX+=value; sumY[col]+=value;
      if ( (row+col)==(n+1) ) sumXY+=value; if ( row==col ) sumYX+=value;
      if (!fprintFW[fw](wfp, (int)(value+midc))) return F;
      if (fputc(col==n ? '\n' : ' ', wfp)==EOF) return F;
    }
    if (sumX!=0) *isCorrect=F;
  }
  if ( (sumXY!=0)|(sumYX!=0) ) *isCorrect=F;
  for (int col=1; col<=n; col++) if (sumY[col]!=0) *isCorrect=F;
  return fputc('\n', wfp)!=EOF; 
} // make_even

bool make_odd(int n, FILE *wfp, bool *isCorrect) {
//   --------
  const int mid=(n+1)/2, midc=((n*n)+1)/2, fw=fieldWidth(n);
  int sumY[maxN+1], sumXY=0, sumYX=0; for (int col=1; col<=n; col++) sumY[col]=0;

  for (int row=1; row<=n; row++) {
    int sumX=0;
    for (int col=1; col<=n; col++) {
      const int value=get_odd(row-mid, col-mid); sumX+=value;  sumY[col]+=value;
      if ( (row+col)==(n+1) ) sumXY+=value; if ( row==col ) sumYX+=value;
      if (!fprintFW[fw](wfp, value+midc)) return F;
      if (fputc(col==n ? '\n' : ' ', wfp)==EOF) return F;
    } 
    if (sumX!=0) *isCorrect=F;
  }
  if ( (sumXY!=0)|(sumYX!=0) ) *isCorrect=F;
  for (int col=1; col<=n; col++) if (sumY[col]!=0) *isCorrect=F;
  return fputc('\n', wfp)!=EOF; 
} // make_odd
//============================================ main ==========================================

bool validOrder(const int n) {
//   ----------
  if (n==2) { printf("\aERROR: There is no magic square of order 2.\n"); return F; }
  else if ((n==1)|(n==4)) {
    printf("There is no bordered magic square of order %i.\n"
           "Making a normal magic square.\n", n); return T; }
  else if (n<=0) { printf("\aERROR: N must be a positive integer.\n"); return F; }
  else if (n>maxN) {
    printf("\aERROR: The maximum order is set at %d.\n", maxN); return F; }
  return T;
} // validOrder

int main() {
//  ----
  bool another=T, inputSize=T, noWriteError=T, ok=F;
  if (openDir()) {
    int n=0;
    do {    
      if (inputSize) { printf("\nInput the order of the square: "); n=getSize(); }
      if (validOrder(n)) {
        FILE *wfp=NULL; bool isCorrect=T;

        if ( (wfp=open_File(n))!=NULL) {
          noWriteError=n%2 ?  make_odd(n, wfp, &isCorrect) : make_even(n, wfp, &isCorrect);
          fclose(wfp);
          if (!isCorrect) printf("\a\nERROR! NOT a Magic Square! Please report by email\n");
        }
      }
      if (noWriteError) { ok=T;
        printf("\nMake another square? "
          "input y (yes) or n (no) or the order of the square: ");
        if (getYorOrder(&n)) inputSize=(n==0); else another=F;
      } else { perror("\nError writing file\n"); another=F; }
    } while (another);
  }
  return ok?EXIT_SUCCESS:EXIT_FAILURE;
} // main