/* ------------------------------------------------------------------------- */
/*                      Resident Commands                                    */
/* ------------------------------------------------------------------------- */

#include "pcstdio.h"
#include "pcdefs.h"             /* header containing all #defines */
#include "pcglobal.h"           /* global variable declarations */
/* -------------------- Element Entry Commands ----------------------------- */

KNumber()       /* process numeric entries */
{   char *velt, *elt, *lp;  int NonTerm();  char *AskMem(), **Madr();
    if (CurLocked()) return;  CmdStart("NUMBER: ");
    if(FillBuf(keybuf, NonTerm, EntBuf, 25) > 0 && (velt=AskMem(9)))
    {   elt = (char *) Madr(velt);  *(elt+TYPE) = NUMBER;  lp = EntBuf;
        getnum(&lp, elt+VALUE, 1);
        if (*lp) 
        {   CmdStart("\007Illegal character '%c' in number");  printf(*lp);
            Mfree(velt);  LvCmdLine();  return;
        }
        PutElt(velt, curx, cury);
    }
    ZapCmdLine();  gotkey = 1;
}

KLabel()        /* process label entries */
{   int len;
    if (CurLocked()) return;  CmdStart("LABEL: ");
    if (keybuf == '"') keybuf = 0;
    if ((len=FillBuf(keybuf, NonTerm, EntBuf, MAXCOLW+1)) >= 0)
        MakLElt(len);
}

MakLElt(len)    /* make a label element from string in EntBuf */
int len;
{   char *velt, *elt;
    if (velt=AskMem(len+1))
    {   elt = (char *) Madr(velt);  *(elt+TYPE) = LABEL;
        copystr(EntBuf, elt+VALUE);  PutElt(velt, curx, cury);
    }
    ZapCmdLine();  gotkey = 1;
}

KBackSpace()
{   ZapCmdLine();  }

KNewline()
{   gotkey = 0; }

KOther()
{   if (NonTerm(keybuf)) KLabel(); else
    {   CmdStart("\007Unknown command");  LvCmdLine();   }
}

/* ------------------ Cursor & Screen Motion Commands ------------------ */

KBottom()
{   if (lastx && (cmdarg=lastx-curx)) KDown();   }

KTop()
{   if (lastx && (cmdarg=curx-1)) KUp();   }

KFarRight()
{   if (lasty && (cmdarg=lasty-cury)) KRight();   }

KFarLeft()
{   if (lasty && (cmdarg=cury-1)) KLeft();   }

KDown()
{   int i;
    if ((i=curx+cmdarg) > MAXLINE) i = MAXLINE;
    if (CondMov(i,cury)) return;
    if ((w->homex= i - w->lines+1) < w->minx) w->homex = w->minx;  curx = i;
    MovPage();
}

KUp()
{   int i;
    if ((i=curx-cmdarg) < w->minx) i = w->minx;
    if (CondMov(i,cury)) return;
    w->homex = curx = i;  /* otherwise, move screen */
    MovPage();
}

KRight()
{   int i;
    if ((i=cury+cmdarg) > MAXCOL) i = MAXCOL;
    if (CondMov(curx,i)) return;
    if ((w->homey = i - w->cols + 1) < w->miny) w->homey = w->miny;  cury = i;
    while ((w->cols=NumCols(w)) < i - w->homey+1) ++w->homey;
    MovPage();
}

KLeft()
{   int i;
    if ((i=cury-cmdarg) < w->miny) i = w->miny;
    if (CondMov(curx,i)) return;
    cury = w->homey = i;  MovPage();
}

KExchange()     /* exchange point and mark */
{   int tempx, tempy;
    tempx = curx;  tempy = cury;
    if (!CondMov(w->markx,w->marky))
    {   curx = w->homex = w->markx;  cury = w->homey = w->marky;  MovPage(); }
    w->markx = tempx;  w->marky = tempy;
}

KSetMark()
{   w->markx = curx;  w->marky = cury;
    CmdStart("Mark Set at ");  outloc(curx,cury);
    LvCmdLine();
}

KUnivArg()      /* Universal Argument */
{   int arg, arg_shows, univ_arg;
    arg_shows = 0;  univ_arg = keybuf; /* save keystroke that got us here */
    do
    {   cmdarg *= 4;
        if (arg_shows || ArgEcho(""))
        {   CmdStart("Arg: %d");  printf(cmdarg);  arg_shows = 1;   }
    } while (NextKey() == univ_arg);
    if (isnumber(keybuf))
    {   CmdStart("Arg: ");
        if ((arg=getint(keybuf)) != -1) cmdarg = arg;
    }
    gotkey = 1;
}

KAbort()
{   BEEP;  ZapCmdLine();   }

KReDraw()       /* redraw current window centered on current line */
{   int newhome;
    w->homex = ((newhome=curx-(w->lines>>1)) < w->minx)? w->minx : newhome;
    if (w->homex > (newhome = MAXLINE - w->lines + 1)) w->homex = newhome;
    ReDraw(w);
}

KReCalc()       /* perform an immediate recalculation */
{   NoCursor();  ReCalc();   }

/* ------------ Element & Screen Manipulation Utilities -------------------- */

MakSRegion()    /* make a region around the single element <curx,cury> */
{   rangex[0] = rangex[1] = curx;  rangey[0] = rangey[1] = cury;   }

MakRegion()     /* make a region normally */
{   rangex[0] = w->markx;  rangey[0] = w->marky;
    rangex[1] = curx;  rangey[1] = cury;  
    order(rangex);  order(rangey);
}

order(range)    /* used by MakRegion() to order ranges properly */
int range[];
{   int temp;
    if (range[1] < range[0])
    {   temp = range[0]; range[0] = range[1];  range[1] = temp;   }
}

MovPage()
{   ReDraw(w);  cmdarg = 1;   }

OnScreen(cw,x,y)        /* predicate returns TRUE if <x,y> is showing */
struct window *cw;  int x,y;    /* FALSE otherwise */
{   return(x >= cw->homex && x < cw->homex + cw->lines && 
        y >= cw->homey && y < cw->homey + cw->cols);
}

CondMov(x,y)    /* move to <x,y> if showing & return TRUE */
int x,y;
{   if (!OnScreen(w,x,y)) return(0);
    movecur(x,y);  cmdarg = 1;  return(1);
}

CondPrt(x,y)    /* print <x,y> if showing anywhere */
int x,y;
{   int shows;
    shows = WCondPrt(w,x,y);
    return(shows += (w2->active && w2->curbuf == w->curbuf &&
           WCondPrt(w2,x,y)));
}

WCondPrt(cw,x,y)        /* print <x,y> if showing in specified window */
struct window *cw;  int x,y;
{   int shows;
    if ((shows=OnScreen(cw,x,y))) prval(cw,x,y);  return(shows);
}

posncur(cw,x,y) /* posn screen cursor at symbolic <x,y> */
struct window *cw;  int x,y;
{   Tposn(linepos(cw,x), colpos(cw,y));   }

linepos(cw,x)   /* screen position of x in window cw */
struct window *cw;  int x;
{   return((x < cw->minx)? cw->toffx + 1 : x - cw->homex + cw->tdispx);  }

colpos(cw,y)    /* returns screen position of y in window cw */
struct window *cw;  int y;
{   int pos, col;
    if (y == 1) return(cw->toffy + YAXWIDTH);   /* special case */
    pos = cw->tdispy;
    for (col=cw->homey; col<y; col++) pos += colwth(cw,col);
    return(pos);
}

colwth(cw,y)    /* returns column width of y in window 'cw' */
struct window *cw;  int y;
{   return((y > MAXCOL || indcolw[y] == 0xFF)?  cw->colwidth : indcolw[y]);  }

movecur(x,y)    /* put element cursor at symbolic <x,y> in current window */
int x,y;
{   char *elt, *GetElt();  int i;
    if (NoCurUpd) return;       /* updating other window - set in K__OPage() */
    if (x != curx || y != cury) NoCursor();  Trev();
    /* now output reverse at <x, y> */

    /* keep cursor within bounds */
    if ((curx=x) < w->homex) curx = w->homex;
      else if (curx > (i=w->homex+w->lines-1)) curx = i;
    if ((cury=y) < w->homey) cury = w->homey;
      else if (cury > (i=w->homey+w->cols-1)) cury = i;

    if (!Trvstr[0])
    {   Tposn(linepos(w,curx), colpos(w,cury)-1);  Tputch('<');   }
    prval(w, curx, cury);  if (!Trvstr[0]) Tputch('>');

    /* clear command line and print formula text (if any) */
    Tposn(Tlines,1);  TCLEOL();
    if (*((elt=GetElt(curx, cury))+TYPE) == FORMULA)
    {   Tnorm();  format("Formula: %s");
        printf(DecForm(elt, EntBuf, 0));  Trev();
    }
    /* and update */
    UpdPos();
}

NoCursor()      /* leave <curx,cury> in normal video  -- current window only */
{   int ly, temp;
    if (!Trvstr[0] && !WCondPrt(w,curx,cury-1)) /* blank '<' at start */
    {   if (w->miny > 1) prval(w,curx,1); else OutYAx(w,curx);   }
    Tnorm();  ly = prval(w, curx, cury);
    if (!Trvstr[0])                             /* blank '>' at end */
    {   Tputch(' ');  WCondPrt(w,curx,ly+=cury);   }
    temp = w->fullscreen;  w->fullscreen = FALSE;
    for (ly=w->homey; ly<cury; ly++)
        if (*(GetElt(curx,ly)+TYPE) == LABEL) prval(w, curx, ly);
    w->fullscreen = temp;
}

UpdPos()        /* update position and buffer indicator */
{   struct buffer *b;
    if((freespace = sizmem() << 1) < WARN_MEM)
    {   Tcolor(C_ERROR);  Tposn(Tlines, Tcols-32);  Trev();
	format("Low Memory!");  Tnorm();
    }
    if (LastMem == NULL)
    {  Tcolor(C_ERROR);  Tposn(Tlines, Tcols-32);  Trev();
       format("Out of Memory!"); Tnorm();
    }
    Tposn(Tlines,Tcols-15);  Tcolor(c_axis);
    format((b=(struct buffer *)Madr(Buff[w->curbuf]))->bname);
    format((modified)? " * " : " ");
    outloc(curx,cury);  posncur(w, curx, cury);
}

prval(cw,x,y)   /* print <x,y> element */
struct window *cw;  int x,y;
{   int i, j, width, locdp, ny, locjust, blnk_fill;
    char *cp, *elt, *errmsg = "Error!";  char ascii[MAXCOLW+2];

    Tcolor(c_norm);
    if (finfp != NULL || !(width=colwth(cw,ny=y))) return(1);
    blnk_fill = cw->fullscreen;         /* for efficiency & readability only */
    if (!(elt=GetElt(x,y)))
    {   if (blnk_fill) {cp = "";  goto bl;} else return(1);   }
    locjust = GetJust(cw,x,y);  locdp = GetDP(cw,x,y);
    switch (*(elt+TYPE))
    {   case LABEL:     cp = elt+VALUE;
                     bl:posncur(cw,x,y);  i = width - strlen(cp);
                        if (i > 0)
                            switch (locjust)
                            {   case 'c':   i >>= 1;
                                case 'r':   blank_out(i);  width -= i;
                            }
                        while (width--)
                        {   if (*cp) Tputch(*cp++);
                              else if (blnk_fill) Tputch(' ');
                                else break;
                            while (!width && *cp && ny+1 < cw->homey+cw->cols
                                && !GetElt(x,ny+1)) width = colwth(cw,++ny);
                        } break;

        case FORMULA:   if (locdp == 'f')
                        {   cp = (char *) DecForm(elt,EntBuf,0);  goto bl;   }
                        Tcolor(c_form);
        case NUMBER:    if (OKNum(elt))
                        {   if (locdp == '*')
                            {   i = *((double *)(elt+VALUE));
                                {   posncur(cw, x, y);
                                    while (width--)
                                        if (i>0) {Tputch('*'); --i;}
                                          else if (blnk_fill) Tputch(' ');
                                }  break;
                            }
                            if (locdp == 'f') locdp = DFDECPL;
                            ftoa(elt+VALUE, ascii, (locdp < ' ')? 'f' : locdp,
                                 width, locdp);
                            cp = ascii-1;  i=j=0;  while (*++cp == ' ') ++i;
                            if (*cp == '-' || *cp == '(') Tcolor(c_neg);
                            switch (locjust)
                            {   case 'l':   break;
                                case 'c':   j = i >> 1;  break;
                                default:    j = i;
                            }
                            if (blnk_fill)
                            {   posncur(cw,x,y);  blank_out(j);   }
                              else Tposn(linepos(cw,x), colpos(cw,y)+j);
                            format(cp);
                            if (blnk_fill) blank_out(i-j);  break;
                        } /* otherwise, handle as error */
        default:        cp = errmsg;  Tcolor(C_ERROR);  
                        if (locjust == 0xFF) locjust = 'r';
                        goto bl;
    }
    return(ny-y+1);     /* return number of columns output */
}

blank_out(n)    /* output n blanks from current cursor position */
int n;
{   while (n--) Tputch(' ');   }

GetDP(cw, x, y) /* get format byte for location */
struct window *cw;  int x, y;
{   char locdp;  char *GetVElt();
    if ((locdp=coldp[y]) == 0xFF) 
    {   locdp = GetVElt(x,0);  if (locdp == 0xFF) locdp = cw->decpl;   }
    return(locdp);
}    

GetJust(cw, x, y)       /* get justification byte for location */
struct window *cw;  int x, y;
{   char locjust;
    if ((locjust=coljust[y]) == 0xFF) 
    {   locjust = (char) (((int) GetVElt(x,0)) >> 8);
        if (locjust == 0xFF) locjust = cw->just;
    }
    return(locjust);
}    

/* convert float to ascii using the conversion spec "<type><width>.<decpl>"
   <type> can be:
        f       Normal decimal output.
        e       Exponential (scientific) notation to as many decimal
                places as possible within <width> (i.e., <decpl> is ignored).
                Maintains one leading blank.
        &       Uses "f<width>.2" output, except that commas are inserted
                in integer portion for accountant $ display, and negative
                numbers are parenthesized.
        $       As above, but only integer $ using "f<width>.0"
   Ascii must be able to hold <width>+1 characters.  Returns ascii.
*/

ftoa(pflnum, ascii, type, width, decpl)
double *pflnum;  char *ascii;  int type, width, decpl;
{   static char conv[8] = "%-.";  char ljnum[100], *cp;  int i, loctype;
    double abs(), a;
 sw:switch (type)
    {   case '&':   loctype = 'f';  decpl = 2;  break;
        case '$':   loctype = 'f';  decpl = 0;  break;
        case 'e':   decpl = width - 8;  loctype = 'e';
                 cd:if (decpl<0) decpl = 0; else if (decpl>15) decpl = 15;
                    break;
        case 'f':   if (abs(*pflnum) > 1.E80) {type = 'e'; goto sw;}
		    loctype = 'f';  goto cd;
        default:    return(ascii);
    }
    cp = conv+3;  *cp++ = decpl/10 + '0';  *cp++ = decpl%10 + '0';
    *cp++ = 'l';  *cp++ = loctype;  *cp = 0;
    i = sprintf(ljnum, conv, *pflnum);
    if ((type == '$' || type == '&') && ljnum[0] == '-')        
    {   ljnum[0] = '(';  ljnum[i++] = ')';  ljnum[i] = 0;   }
    cp = &ljnum[i];  i = 0;                     /* right justify */
    if (type == '$') i = (ljnum[0] == '(')? 6 : 5;
    while (width >= 0)                          /* and add commas if nec */
    {   if (type == '&' && *cp == '.') i = 5;
        *(ascii+width--) = (cp < ljnum)? ' ' : 
                           (!--i && isnumber(*cp))? ',' : *cp--;
        if (!i) i = 4;
    }
    if (cp >= ljnum) *ascii = '*';              /* mark overflow */
    return(ascii);
}

CurLocked()             /* TRUE if cursor position is locked (+ message) */
{   if (IsLocked(curx,cury))
    {   CmdStart("\007Cannot change locked entry"); LvCmdLine(); return(1);  }
    return(0);
}

IsLocked(x,y)           /* TRUE if <x,y> is a locked formula */
{   char *elt;
    return((elt=GetElt(x,y)) && *(elt+TYPE) == FORMULA && 
           *((int *)(elt+RELABS)) < 0);
}

PutElt(velt, x, y)      /* put an element into the screen array */
char *velt;  int x, y;
{   int lx, ly, changed;
    char **vnewrow, **newrow, **sp;
    if (IsLocked(x,y)) {BEEP;  return(0);}

    if (y > scrny)      /* must completely reallocate screen buffer */
    {   for (lx=1; lx<=lastx; lx++)
        {   if (!(vnewrow = (char **) AskMem(y+y+2)))
            {rt:if (velt) Mfree(velt);  return(0);   }
            newrow = Madr(++vnewrow);  sp = Madr(screen[lx]);
            *newrow = *sp;
            for (ly=1; ly<=y; ly++)
                *(newrow+ly) = (ly<=scrny)? *(sp+ly) : 0;
            Mfree(--screen[lx]);  screen[lx] = vnewrow;
        }
        scrny = y;
    }
    if (y > lasty) lasty = y;
    while (x > lastx)   /* extend screen buffer one line */
       if (screen[++lastx] = (char **) AskMem(scrny+scrny+2))
       {   *Madr(++screen[lastx]) = (char *) -1;   /* to suppress warning */
           for (ly=1; ly<=scrny; ly++) *(Madr(screen[lastx])+ly) = 0;
       } else {lastx--;  goto rt;}
    changed = (updform && (IsNumElt(GetElt(x,y)) || IsNumElt(Madr(velt))));
    if (*(sp=Madr(screen[x])+y)) Mfree(*sp);
    *sp = velt;  
#ifdef TRACE
    Tposn(Tlines-3,1);
    Format("Putelt %d ");Printf(tracecnt++);
    Format("velt %d "); Printf(*(int *)&velt);
    Format("sp %d ");Printf((int)sp);

#endif
    if (changed) ReCalc();  return(modified=1);
}

/*	VIRTUAL MEMORY VERSION!!
char *GetVElt(x, y)    /* return virtual addr of screen <x,y> location */
int x, y;
{   if (x > lastx || y > lasty) return(0);
    return(*(Madr(screen[x])+y));
}

char *GetElt(x, y)      /* return memory location of elt at <x,y> */
int x,y;
{   return(Madr(GetVElt(x,y)));   }
*/

char *GetElt(x, y)	/* Non-VM Version!! */
int x, y;
{   if (x > lastx || y > lasty) return(0);
    return(*(screen[x]+y));
}

char *GetVElt(x, y)	/* Non-VM Version!! */
int x, y;
{   return(GetElt(x,y));   }

ReDraw(cw)      /* redraw a window */
struct window *cw;
{   int x, bottom;
    if (cw == w && w2->active && synch)         /* synch other win */
    {   if (w->tdispy == w2->tdispy)            /* horizontal split */
        {   if (w2->homey != w->homey)
            {   w2->homey = w->homey;  w2->fullscreen = FALSE;   }
        }
        else if (w2->homex != w->homex)
        {   w2->homex = w->homex;  w2->fullscreen = FALSE;   }
    }
    cw->fullscreen = FALSE;  if (finfp != NULL || !Axis(cw)) return;
    if (cw->minx > 1) DrLine(cw,1);     /* top titles fixed */
    bottom = cw->homex + UsedLines(cw) - cw->minx;
    for (x=cw->homex; x<bottom; x++) 
    {   if (kbhit()) return; else DrLine(cw, x);   }
    cw->fullscreen = TRUE;  movecur(curx,cury);  
}

DrLine(cw, x)   /* draw a line at x in window 'cw' */
struct window *cw;  int x;
{   int y, maxy;
    if (finfp != NULL) return;  if (foutfp == NULL) Tnorm();
    if (cw->miny > 1) prval(cw, x, 1);  /* left column titles fixed */
    maxy = (lasty < cw->homey + cw->cols)? lasty : cw->homey + cw->cols - 1;
    y = cw->homey;  while (y <= maxy) y += prval(cw,x,y);
}

IsNumElt(elt)   /* predicate returns TRUE if elt is numeric */
char *elt;
{   char *ep;
    return(elt && (*(ep=elt+TYPE)==NUMBER || *ep==FORMULA));    }

NumCols(cw)     /* compute number of columns showing in 'cw' */
struct window *cw;
{   int ypos, ncol;
    for (ncol = 0, ypos = colpos(cw,cw->homey); 
         ypos <= cw->toffy + cw->tcols; ncol++)
            ypos += colwth(cw,cw->homey+ncol);
    return(--ncol);
}

UsedLines(cw)   /* returns number of lines on window used, including title */
struct window *cw;
{   int bottom, win_bot;
    bottom = (lastx < (win_bot=cw->homex+cw->lines))? lastx : win_bot-1;
    if (cw == w && curx > bottom && curx < win_bot) bottom = curx;
    return(bottom - cw->homex + cw->minx + 1);
}
