/*
 *  File:    Rotate.cpp
 *  Author:  S Harry White
 *  Created: 2010-11-16
 *  Updated: 2022-01-21
 *    Tidy code.
 *  Updated: 2023-01-24
 *    Open output files in the current folder.
 *  Updated: 2023-02-22
 *    Improve openInput and openOutput.
 *  Updated: 2023-07-06
 *    Add support for non-square rectangles.
 */

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

const bool F=false, T=true; bool readError;
int R, C, // The order of the rectangles.
    RC,   // R*C
    Rm1,  // R-1
    Cm1,  // C-1
    Rm2,  // R-2
    Cm2,  // C-2
    **iSquare=NULL, numSquares, printNum;

const int bufSize=1024; bool bell=T; char fmt[bufSize];
bool reportError(char *msg) {
//   -----------
  printf("%sError: %s.\n", bell ? "\a\n" : "",  msg); return bell=F;
} // reportError

void initGlobals() { RC=R*C; Rm1=R-1; Cm1=C-1; Rm2=Rm1-1; Cm2=Cm1-1; numSquares=0; printNum=100000; readError=F; }
//   -----------
//==================================================== store ======================================================

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

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

bool allocateSquares() {
//   ---------------
  bool ok=T; freeSquare(&iSquare,R); return allocateSquare(&iSquare);
} // allocateSquares
//=================================================== 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

int getInt(char *prompt) {
//  -------
  printf(prompt); int n=0; scanf_s("%d", &n); clearLine(getchar());
  if (n<0) reportError("Invalid input"); return n;
} // getInt

bool getYorInt(int *n) { // 'y' or 'n' or a number
//   ---------
  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

bool getInts(int *p, int *q, int c) {
//   -------
  bool ok=F; *p=0; *q=0; if (c<0) 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'; *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;
      }
    }
    if (*q==0) *q=*p; ok=T;
  }  
  clearLine(c); if (!ok) return reportError("Invalid input"); return T;
} // getInts

bool getYorInts(int *p, int *q) {
//   ----------
  bool ok=F; int c; *p=-1; do { c=getchar(); } while ((c==' ')|(c=='\t')|(c=='\n') );
  if ( (c=='Y')|(c=='y') ) ok=T; else if ( (c!='N')&(c!='n') ) return getInts(p, q, c);  
  clearLine(c); return ok;
} // getYorInts

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

const int 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]) {
//    ---------
  char *rFname=NULL; FILE *rfp=NULL;
  do {
    printf("\nEnter the name of the %ss file: ", R==C ? "square" : "rectangle");
    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, rFname, "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 getWidth(int i) {
//  --------
  bool isSigned=i<0; int width=1; while ((i=i/10)!=0) ++width; if (width>10) width=10;
  return isSigned ? width+1 : width;
} // getWidth

const int maxFieldWidth=10; int FW0, FW;
bool readSquare(FILE *rfp) {
//   ----------
  int smallest=LONG_MAX, biggest=LONG_MIN;
  for (int r=0; r<R; r++) for (int c=0; c<C; c++) {
	  int tmp, rv;
    if ( (rv=fscanf_s(rfp, "%d", &tmp))==1) {
      if (tmp<smallest) smallest=tmp; if (tmp>biggest) biggest=tmp; iSquare[r][c]=tmp;
    } else {
      if ( (rv!=EOF)|(r!=0)|(c!=0) ) { readError=T; printf("\a\nError reading square from file.\n"); } return F;
    }
  }
  const int sWidth=getWidth(smallest), bWidth=getWidth(biggest); FW0=sWidth>bWidth ? sWidth : bWidth; FW=FW0+1;
  if (FW>maxFieldWidth) return reportError("Output format overflow"); return T;
} // readSquare
//============================================= rotate and 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_s(obuf, outSize, s); 
} // stripName

FILE *openOutput(char *inFname, const int rotation) {
//    ----------
  int sub=0; FILE *wfp=NULL; const int baseSize=bufSize+25, outSize=bufSize+50;
  char baseName[baseSize], buf[outSize]; stripName(inFname, buf);
  sprintf_s(baseName, baseSize, "%sRot%d", buf, rotation);
  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) printf(".. writing squares to file %s\n", buf);
  else {
    char msg[outSize+50]; sprintf_s(msg, outSize+50, "\a\nCan't open for write %s", buf); perror(msg);
  }
  return wfp;
} // openOutput

void writeNumberOfSquares(char *s, const int num) {
//   --------------------
  const int msq=num/1000000, tsq=num % 1000000/1000, rsq=num % 1000;
  if (msq==0) if (tsq==0) printf("%s %d\n", s, rsq); else printf("%s %d,%03d\n", s, tsq, rsq);
  else printf("%s %d,%03d,%03d\n", s, msq, tsq, rsq);
} // writeNumberOfSquares

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

bool rotateSquare(const int rotation, FILE *wfp) {
//   -------------
  int **x=iSquare;
  if (++numSquares==printNum) { writeNumberOfSquares("..", numSquares); printNum+=printNum; }
  switch (rotation) {
  case 0: for (int i=0; i<R; i++) {
            if (!fprintFW[FW0](wfp, x[i][0])) return F;
            for (int j=1; j<C; j++) if (!fprintFW[FW](wfp, x[i][j])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  case 1: for (int i=0; i<C; i++) {
            if (!fprintFW[FW0](wfp, x[Rm1][i])) return F;
            for (int j=Rm2; j>=0; j--) if (!fprintFW[FW](wfp, x[j][i])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  case 2: for (int i=Rm1; i>=0; i--) {
            if (!fprintFW[FW0](wfp, x[i][Cm1])) return F;
            for (int j=Cm2; j>=0; j--) if (!fprintFW[FW](wfp, x[i][j])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  case 3: for (int i=Cm1; i>=0; i--) {
            if (!fprintFW[FW0](wfp, x[0][i])) return F;
            for (int j=1; j<R; j++) if (!fprintFW[FW](wfp, x[j][i])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  case 4: for (int i=0; i<R; i++) {
            if (!fprintFW[FW0](wfp, x[i][Cm1])) return F;
            for (int j=Cm2; j>=0; j--) if (!fprintFW[FW](wfp, x[i][j])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  case 5: for (int i=Cm1; i>=0; i--) {
            if (!fprintFW[FW0](wfp, x[Rm1][i])) return F;
            for (int j=Rm2; j>=0; j--) if (!fprintFW[FW](wfp, x[j][i])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  case 6: for (int i=Rm1; i>=0; i--) {
            if (!fprintFW[FW0](wfp, x[i][0])) return F;
            for (int j=1; j<C; j++) if (!fprintFW[FW](wfp, x[i][j])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  default: for (int i=0; i<C; i++) {
            if (!fprintFW[FW0](wfp, x[0][i])) return F;
            for (int j=1; j<R; j++) if (!fprintFW[FW](wfp, x[j][i])) return F;
            if (fputc('\n', wfp)==EOF) return F;
          } break;
  }
  return fputc('\n', wfp)!=EOF;
} // rotateSquare
//======================================================= main =================================================

bool checkRotation(const int r) {
//   -------------
  if ((r<0)|(r>8) ) { printf("\aERROR: Rotation number range is 0 to 8.\n"); return F; } return T;
} // checkRotation

bool checkRC(const int nr, const int nc) {
//   -------
  if ((nr<=0)|(nc<=0)) return reportError("Invalid order");
  return T;
} // checkRC

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 main() {
//  ----
  bool anotherOrder=T, inputOrder=T, ok=F, writeError=F; char buf[bufSize]; outputLocalTime();
  do {
    if (inputOrder) { printf("\nOrder? "); if (!getInts(&R, &C, -1)) break; }
    if (checkRC(R, C)&&allocateSquares()) {
      initGlobals(); FILE *rfp=openInput(buf);
      if (rfp!=NULL) {
        int rotation=0; bool anotherRotation=T, inputRotation=T;         
        do {
          if (inputRotation) rotation=getInt("\nInput the rotation number, 0 to 8: ");
          time_t startTime=time(NULL);
          if (checkRotation(rotation)) {
            FILE *wfp=openOutput(buf, rotation);
            if (wfp!=NULL) {
              numSquares=0;
              while (readSquare(rfp)) {
                if (rotation==8) {
                  for (int r=0; r<8; ++r) { if (writeError=!rotateSquare(r,wfp)) break; }
                } else { writeError=!rotateSquare(rotation,wfp); } if (writeError) break;
              }
              if (!writeError) { ok=T; printf("\nNumber of %ss: %d", R==C ? "squares" : "rectangles", numSquares); }
              fclose(wfp);
            }
          }
          if (writeError) { perror("\n\aError writing file"); anotherRotation=F; }
          else {
            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);
            printf("\nAnother rotation? input y (yes) or n (no) or the rotation number: ");
            if (getYorInt(&rotation)) { inputRotation=(rotation<0); rewind(rfp); } else anotherRotation=F; 
          }  
        } while (anotherRotation);
        fclose(rfp);
      } // rfp!=NULL
      freeSquare(&iSquare, R);
    } // checkRC && allocateSquares
    if (writeError) anotherOrder=F;
    else {
      printf("\nAnother order? input y (yes) or n (no) or the order of the squares: ");
      if (getYorInts(&R, &C)) inputOrder=(R<0); else anotherOrder=F;
    }
  } while (anotherOrder);
  printf("\nPress a key to close the console");
  while (!_kbhit()) Sleep(250); return ok ? EXIT_SUCCESS : EXIT_FAILURE;
} // main