/* ------------------------------------------------------------------------ */
/*                     Main Program & Utilities                             */
/* ------------------------------------------------------------------------ */

#include "pcstdio.h"
#include "pcdefs.h"             /* header containing all #defines */

char *hithere = "PERFECT CALC 1.15 (c) 1982, 1983 Perfect Software Inc.\n";

/*      Change history:
	1.15    7/1/83   	user not allowed to fill memory to point where
				can not save the spreadsheet. (Change AskMem)
	1.14	6/27/83		repair bugs in power, exp functions, faster
				Fpoly.
	1.13	6/23/83		make divide by 0 produce error
				install low memory warning
				fix bug in npv
	1.12	6/20/83		changed pcio.c so that no ^L terminates
				last page of ^X-^P printout file.
				Save values with formulas in save file.
	1.11 	6/13/83		made ^Z terminate ^X-^P printout file,
				removed "IBM-PC" from prompts.	
				Fixed sign bug in sine/cosine.
	1.10 	5/26/83		added IBM-PC function key support
	1.09	3/23/83		atan(); overflow in sprintf(); formatting;
				stricter on number input
	1.08	3/2/83		Better NULL x function key handling
	1.07	2/22/83		better position(x,y) in PCIO.C from C/80
	1.06	2/16/83		PCCALC.C update from C/80 version
	1.05	2/15/83		Fixed disk full bug and other typos.
	1.04	1/27/83		Duplicate keys added; fclose() with \a.
	1.03	1/6/83		Monochrome graphics monitor fixed.
	1.02	12/18/82	Kill memory bug fixed.
	1.01	12/14/82	Arbitrary recalculation order.
        1.00    10/18/82        KEdit() bug fix; messages; reading speed
        0.9     9/27/82         Linked buffers; Wyse compatibility
        0.8     9/14/82         Mem; compute algorithms; LST:
        0.7     8/26/82         Left/right/center justification; bug fixes
        0.0X    8/12/82         Better PCCONFIG; PCIO write fix
        0.6     8/2/82          Replicate fixed; lock/unlock; printer setup
        0.5     7/7/82          Link to MENU; ANSI terminals; bug fixes
        0.4     6/11/82         First version displayed to PSI
        0.3     5/27/82         Multiple buffers
        0.2     5/20/82         Virtual memory
        0.1     4/1/82          Complete revision from ZenCalc learning.
        0.0     2/18/82         First version given to CSCA for evaluation.
*/

/* GLOBALS */
struct window           /* def'n of a screen window */
{   int active;                 /* flag -- TRUE if window is active */
    int fullscreen;             /* flag -- TRUE if window screen complete */
    int cx, cy;                 /* V: current element */
    int lines, cols;            /* V: # lines & columns showing */
    int homex, homey;           /* V: current upper lefthand position */
    int minx, miny;             /* V: min <x,y> (for fixed titles) */
    int colwidth, decpl, just;  /* V: global column width, decpl, & just */
    int markx, marky;           /* V: mark location */
    int tlines, tcols;          /* T: actual # lines, columns available */
    int tdispx, tdispy;         /* T: displacement on terminal of home 
                                      position corrected for fixed titles */
    int toffx, toffy;           /* T: offset of this window (starts at 1,1) */
    int OK_EOL;                 /* T: OK to use CLEOL on this window */
    int OK_EOS;                 /* T: OK to use CLEOS on this window */
    int curbuf, prevbuf;        /* current, previous buffer */
} win[MAXWIN], *w, *w2;
int synch;                      /* Flag: windows in synch */

/* */ int tracecnt = 0;		/* used  in debugging */

struct buffer                   /* definition of a buffer */
{   int bsize;                  /* from the memory manager */
    char bname[9];              /* name of this buffer */
    char bfile[17];             /* filename in this buffer */
    char **bscp;                /* screen pointer -- like **pickbuf */
    int blx, bly, blsy;         /* lastx, lasty, scrny for this buffer */
    char *bdp, *bcolw, *bj;     /* coldp[], indcolw[], ... for this buffer */
    int bmod;                   /* whether or not buffer has been modified */
    int linked;                 /* other buffer this one is linked to */
    int updx[2], updy[2];       /* update region for buffer */
    /* Following buffer items duplicate window items */
    int bcx, bcy;               /* V: current element */
    int bhomex, bhomey;         /* V: current upper lefthand position */
    int bcolwidth, bdecpl, bjust;/* V: global column width and dec place */
    int bmarkx, bmarky;         /* V: mark location */
};
struct buffer *Buff[MAXBUF];    /* pointers to buffer definitions */

char **screen[256];
int curx, cury;
int lastx, lasty, scrny;
int keybuf;                     /* current (or last) keystroke entry */
int gotkey;                     /* flag indicating that keybuf has next key */
int gotch;                      /* character gotten by kbdhit() */

char coldp[53];                 /* individual column decimal places */
char coljust[53];               /* individual column justification */
char indcolw[53];               /* individual column widths */

int updform = 1;                /* formula update flag */

int fullscreen;                 /* UNUSED */
int abort;                      /* abort (command) flag */
int modified;                   /* something modified since last write */
int NoCurUpd;                   /* No cursor update flag - set in K__OPage() */

char *EntBuf;                   /* buffer for entries */
int initdone;                   /* flag indicating initialization done */

char **pickbuf;                 /* pick buffer pointer */
int picktype;                   /* type of pick buffer */
int pickx[2], picky[2];         /* origin of pick buffer */

int cmdarg = 1;                 /* integer command argument */
int rangex[2], rangey[2];       /* range argument  -- rangex[0] is flag */
int chrcnt;                     /* running character count */

char *file;                     /* file to do I/O on */
FILE *chanfp;                   /* channel to do I/O on */
char *prfile;                   /* file for print output */
int gotfile;                    /* got file from command line */

char *swp_file = SWAPFILE;
char *Aswp_file = ASWAPFILE;
FILE *swpfp;                    /* channel for swap file (always open) */

int _fmode = 0;
int _stack = 1024;              /* smaller than usual stack size */

unsigned freespace;             /* amount of main memory avail */
char *LastMem;                  /* last memory used (or flag showing Mem OK) */

FILE *finfp = NULL, *foutfp = NULL;     /* like old I/O, but file pointers */
extern int (*Functions[])();    /* in PCIO.C */
char *getmem();
int Tabsx, Tabsy;               /* absolute terminal position */
int vid_mode = 0;               /* video mode: set if inverse */

/* Configuration information from PC.OVL -- EXACTLY 512 bytes last 2 records */
int Tlines, Tcols;
char Tinstr[LIN];  int Tinnul;
char Tdeinstr[LDEIN];  int Tdeinnul;
char Tdcpstr[LDCP];  int Tdcpnul;
char Tclsstr[LCLS];  int Tclsnul;
char Teosstr[LEOS];  int Teosnul;
char Teolstr[LEOL];  int Teolnul;
char Trvstr[LRV];
char Tnvstr[LNV];
int prtwidth;
char Pinstr[LPIN], Pdeinstr[LPDEIN];
char Tbottom[LBOTTOM];
int Keys[LKEYS];

#include "pcprintf.c"           /* a special printf */

main(argc, argv)
int argc; char *argv[];
{   int n; 
    if (argc > 1) {Init(strupper(argv[1]));  gotfile = 1;  KRead();}
      else Init(DFFILE);
    while (1)
    {   n = NextFunc();
        if (isalpha(keybuf)) KLabel(); else (*Functions[n])();
    }
}

OpnSwpFile()    /* open overlay file on default or disk A: */
{   _fmode = 0x8000;
    if (!(swpfp = fopen(swp_file, "r"))) swpfp = fopen(Aswp_file, "r");
    _fmode = 0;  return(swpfp);
}

Init(buffile)
FILE *buffile;
{   static char entbuf[100], loc_file[17] = "", loc_prfile[17] = "PRN";
    int ch;
#ifdef IBM
    Tinit();
#endif
    format(hithere);
    if ((ch=open(swp_file,0x8000)) == -1) ch = open(Aswp_file,0x8000);
    if (ch == -1 || lseek(ch,-512L,2) == -1L || read(ch,&Tlines,512) != 512)
    {   CleanUp();  format("\007Cannot read overlay file\n");  exit();  }
#ifndef IBM
    Tinit();
#endif
    close(ch); 
    EntBuf = entbuf;            /* string buffer for entries */
    file = loc_file;            /* string buffer for filename */
    copystr(buffile, file);
    prfile = loc_prfile;        /* string buffer for print filename */
    InitForm();                 /* initialize formula module */
    KZap();                     /* clear memory */
    initdone = 1;               /* set initdone flag for subsequent Clear() */
}

CleanUp()
{   Tposn(Tlines,1);  Tnorm();  Tdeinit();   }

Axis(cw)        /* draw axes across top and down left sides of screen */
struct window *cw;
{   int i, maxx;
    if (finfp != NULL) goto rt;  ClrWindow(cw);
    /* draw top across */
    Tcolor(c_axis);  Trev();  format("   ");  cw->cols = NumCols(cw);
    if (cw->miny > 1) DrwHead(cw, 1);
    for (i = 0; i < cw->cols; i++)
    {   if (kbhit()) return(FALSE);  DrwHead(cw, cw->homey+i);   }
    /* draw down axis */
    if (cw->minx > 1) OutYAx(cw, 1);  maxx = cw->homex + cw->lines - 1;
    for (i = cw->homex; i <= maxx; i++)
    {   if (kbhit()) {  Tcolor(c_norm);  return(FALSE);  }
	OutYAx(cw, i);
    }
    if (Tbottom[0]) {Tposn(Tlines+1,1);  format(Tbottom);}
 rt:Tcolor(c_norm);  return(TRUE);
}

ClrWindow(cw)   /* clear window 'cw' and leave at upper lefthand */
struct window *cw;
{   int x, y, n, i;
    Tposn(x=cw->toffx, y=cw->toffy);
    if (cw->OK_EOS) {TCLEOS();  return;}
    Tnorm();  n = cw->tlines;  if (cw->toffx + n < Tlines) ++n;
    while (n--)
    {   Tposn(x++, cw->toffy);
        if (cw->OK_EOL) TCLEOL(); else
        {   i = cw->tcols+1;  while (i--) Tputch(' ');   }
    }
    Tposn(cw->toffx, y);
}

OutYAx(cw, x)   /* output a Y-axis number */
struct window *cw;  int x;
{   Tcolor(c_axis);  Trev();  Tposn(linepos(cw, x), cw->toffy);
    if (x < 10) format("  %d");
      else if (x < 100) format(" %d");
        else format("%d");  printf(x);
    Tnorm();
}

DrwHead(cw,y)   /* draw an axis header for column y */
struct window *cw;  int y;
{   int stpos, width, i;
    if (!(width=colwth(cw,y))) return;  Tcolor(c_axis);  Trev();
    Tposn(cw->toffx, stpos=colpos(cw,y));  Tputch('|');
    for (i=1; i<width-1; i++) Tputch(' ');  Tputch('|');
    Tposn(cw->toffx,stpos+(width>>1));  Tputch(letcoord(y));  Tnorm();
}

NextFunc()      /* get next function */
{   int i;  int isnumber();
    if (!gotkey) NextKey();  gotkey = 0;
    if (keybuf == DEL) return(0);       /* special case: DEL -> BackSpace() */
    if (isnumber(keybuf)) return(NUMCOMD >> 1);
    /* scan for function number; return match/2 for actual function number */
    for(i=0; Keys[i]; i++) if (keybuf == Keys[i]) break;
    return(i >> 1);
}

/* Notes about special keys:
   - DEL vs. BKSP:  DEL always works for backspacing out of FillBuf()
     or any function; BKSP will work, too, if Keys[0] == '\b'.  This is 
     the usual implementation; however, for terminals which use the 
     ^H, ^J, ^K, ^L arrow keys, ^H = BKSP must be made separate.
   - '\r' rather than '\n' is checked for at all times, since '\n' == ^J
     is also a common arrow key code sent.
   - Thus, the usual mapping is:
        ^H == BKSP == DEL == BackSpace()
        ^J  Unknown command
        ^K  DelLine()
        ^L  ReDraw()
        ^M  NewLine()
     and the mapping for terminals with such arrow keys is:
        ^H, ^J, ^K, ^L == arrow keys
        DEL == BackSpace()
        ^C  DelLine() "Close line"
        ^R  ReDraw()
        ^M  NewLine()
*/

NextKey()       /* Get next key typed */
                /* Routines which read-ahead should use this instead of
                   getchar() since it knows about special keys */
{gk:keybuf = getkey();
    switch (keybuf)
    {   case EOF:       QuitRead();  goto gk;
        case ESC:       ArgEcho("Escape:");  keybuf = getkey()+0400;  break;
        case '\\':
        case CTRLX:	ArgEcho("eXtended:");  keybuf = getkey()+01000;  break;
        case '\b':      if (Keys[0] == '\b') keybuf = DEL;  break;
        case BEL:       BEEP;  abort = 1;  break;
    }
    if (keybuf != DEL) chrcnt++; else chrcnt--;
    return(keybuf);     /* return single first character */
}

/* --------------------------------------------------------------------- */
/*                        General Utilities                              */
/* --------------------------------------------------------------------- */

ArgEcho(str)    /* echo something if between typed keys */
char *str;
{   int countdown;
    if (finfp != NULL) return(1);
    countdown = 5000;  while (countdown--) if (kbhit()) return(0);
    CmdStart(str);  return(1);
}

getkey()        /* replacement for getchar() to handle ungotten chars */
{   int c;  struct window *cw;  struct buffer *Mchg(), *b2;
    if (finfp != NULL) return(((c=getc(finfp)) == '\n')? '\r' : c);
    while (!(c=kbhit()))
    {   if (!w->fullscreen) ReDraw(w);
        if (w2->active && !w2->fullscreen)
        {   if (w2->curbuf != w->curbuf)
            {   (b2=Mchg(Buff[w2->curbuf]))->bhomex = w2->homex;
                b2->bhomey = w2->homey;  BReDraw();
            } else ReDraw(w2);
        }
    }
    gotch = 0;  return(c);
}

kbhit()
{   return((gotch)? gotch : (gotch=bdos(6,-1)));   }

Tputch(c)       /* output character ONLY if input is not from a file */
char c;         /* quit if someone reports an error (types BEL) */
{   if (finfp != NULL)
    {   if (c == BEL)
        {   QuitRead();  CmdStart("\007Bad data file -- read aborted");   }
        return(c);
    }
    if (foutfp != NULL)
    {   if (ferror(foutfp) || (c=putc(c, foutfp)) == EOF) abort = 1;
	return(c);
    }
    if (c == '\n') putch('\r');  return(putch(c));
}

QuitRead()      /* quit reading from a data file */
{   fclose(chanfp);  updform = 1;  ReCalc();
    modified = 0;  finfp = NULL;  ReDraw(w);  ZapCmdLine();
}

getint(first)   /* get a non-neg integer via NextKey(); */
char first;     /* return -1 if backed out or didn't type a number */
{   char buf[6];  char *bp;  int isnumber();
    if (FillBuf(first, isnumber, buf, 6) <= 0) return(-1);
    bp = buf;  return(toint(&bp));
}

getnum(pbufp, pflnum, conv)     /* scan ascii buffer thru a floating number */
char **pbufp;  double *pflnum;  int conv;  /* convert to float if requested */
{   char locnum[40];  char *lnp, *lbufp;
    lbufp = *pbufp;     /* play with a local copy */
    lnp = locnum;
    /* strictly check for valid number */
    /* cannot have '-' or '+' followed by CR */
    if ( (*lbufp == '-') && !*(lbufp+1) ) goto re;
    /* if first char valid, store in lnp else it is an error */
    if ( isnumber(*lbufp) || *lbufp == '-' || *lbufp == '.')
	 *lnp++=*lbufp++;
      else goto re;
    while (*lbufp == '.' || isnumber(*lbufp))
        *lnp++ = *lbufp++;              /* scan mantissa */
    if (tolower(*lbufp) == 'e')         /* scan exponent */
    {   *lnp++ = 'E';  lbufp++;
        if (*lbufp == '+') lbufp++;
          else if (*lbufp == '-') *lnp++ = *lbufp++;
        while (isnumber(*lbufp)) *lnp++ = *lbufp++;
    }
 re:*lnp = 0;  if (conv) sscanf(locnum, "%lf", pflnum);  *pbufp = lbufp;
}

FillBuf(first, test, buf, len)          /* fill up a buffer from input */
int first, (*test)(); char *buf; int len;
{   int c;  char *bp, *bend;
    bp = buf;  bend = buf+len-1;  c = (first)? first : NextKey();
    while (1)
    {   if (abort) return(-1);
          else if (c == DEL)
               {   if (--bp < buf) return(-1); else backup(1);   }
            else if (!(*test)(c)) break;
              else if (bp < bend) Tputch(*bp++ = c);
                else BEEP;      /* don't allow past buffer edge */
        c = NextKey();
    }
    *bp = 0;  return(bp-buf);
}

NonTerm(k)    /* returns TRUE if key is not command terminator key */
int k;
{   return(k >= ' ' && k < 0200);   }

strcmp(str1, str2)      /* compare str1 against str2 */
char *str1, *str2;
{   while (*str1 && *str2 && *str1 == *str2)  {str1++;  str2++;}
    return(!*str1 && !*str2);   
}

isnumber(c)
int c;
{   return(c >= '0' && c <= '9');   }

isalpha(c)
int c;
{   int lc;  return((lc=tolower(c)) >= 'a' && lc <= 'z');  }

tolower(c)
int c;
{   return((c >= 'A' && c <= 'Z')? c+('a'-'A') : c);  }

toupper(c)
int c;
{   return((c >= 'a' && c <= 'z')? c-('a'-'A') : c);   }

strupper(s)	/* make an entire string uppercase */
char *s;
{   char *t;  t = s;  while (*t) {*t = toupper(*t);  ++t;}  return(s);   }

letcoord(y)     /* return a letter coordinate from integer y */
int y;
{   return((y > 26)? y+('A'-27) : (y)? y+('a'-1) : '?');        }

numcoord(y)     /* letter coord to numeric coord */
int y;
{   return((y > 'Z')? y - ('a'-1) : y - ('A'-27));   }

toint(pstr)     /* string to integer */
char **pstr;
{   int n, neg;
    neg = n = 0;  if (**pstr == '-') {neg = 1;  *(*pstr)++;}
    while (isnumber(**pstr)) n = n*10 + *(*pstr)++ - '0';
    return((neg)? -n : n);
}

fromint(n, pps) /* integer to string */
int n; char **pps;
{   if (n < 0) {*(*pps)++ = '-'; n = -n;}
    if (n < 10) *(*pps)++ = n + '0';
    else {fromint(n/10, pps);  *(*pps)++ = n%10 + '0';}
    **pps = 0;
}

outloc(x,y)     /* output location <x,y> as characters */
int x,y;
{   format("%c%d");  printf(letcoord(y));  printf(x);   }

backup(n)
int n;
{   while (n--) {Tputch('\b');  Tputch(' ');  Tputch('\b');  }   }

confirm(msg)    /* make sure user thinks about it */
char *msg;
{   CmdStart(msg);  return(tolower(NextKey()) == 'y');   }

CmdStart(str)    /* Start a command string */
char *str;
{   if (finfp != NULL) {Tputch(*str);  return;}
    Tposn(Tlines,1);  TCLEOL();  Trev();  UpdPos();
    Tposn(Tlines,1);  Tnorm();
    Tcolor((*str == '\007')? C_ERROR : c_comd);  format(str);
}

ZapCmdLine()
{   cmdarg = 1;  rangex[0] = abort = 0;
    if (finfp == NULL) movecur(curx,cury);  
}

LvCmdLine()
{   cmdarg = 1;  rangex[0] = abort = 0;  posncur(w,curx,cury);  }

/* ---------------- Memory Management Section ---------------------------- */

char *AskMem(n) /* try to get new memory -- BEEP and message if cannot */
unsigned n;
{   char *ch;
         /*  Note: we keep length of the block as the first 2 chars and
        zero out the last byte; per old C/80 memory management protocol. */
/* if (sizmem() - n < 02600)  */
    if ((LastMem=getmem(n+=3)) == NULL) goto rt;	 
    if ( (ch=getmem(/*05320*/ 02600)) == NULL)
       {   rlsmem(LastMem,n);
rt:	   Tcolor(C_ERROR); 
	   Tposn(Tlines, Tcols-36);
	   Trev(); 
	   format("!Out of Memory! %d"); 
/* */	   printf((unsigned)LastMem);
	   Tnorm(); 
	   BEEP;
	   movecur(curx,cury);
	   return(0);
	   }
       else 
	   if (rlsmem(ch,/*05320*/ 02600) != 0) 
              {format("Unable to free memory\n");}
    *((int *)LastMem) = n;  *(LastMem+n-1) = 0;
    return(LastMem);
}

Minit()
{   fclose(swpfp);  rbrk();  allmem();  LastMem = (char *) !NULL;   }

char **Madr(vir_addr)	/* these just return their values for now */
char **vir_addr;  {   return(vir_addr);   }

struct buffer *Mchg(vir_addr)	/* necessary because of prev. defn. */
struct buffer *vir_addr;  {   return(vir_addr);   }

MFree (cp)
unsigned *cp;  {   return(rlsmem(cp, *cp));   }
