/* ------------------------------------------------------------------------- */
/*                   Replication Commands (Overlay)                          */
/* ------------------------------------------------------------------------- */

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

/*      Function table here is compiled ONLY IF overlays are activated.
        Functions in overlay PCIO.C are set to zero in that case.
*/

#ifdef OVLON
int KErase();
int KRErase();
int KPick();
int KRPick();
int KPut();
int KRPut();
int KInsLine();
int KDelLine();
int KInsColumn();
int KDelColumn();

extern  int KLabel();
extern  int KFormula();
extern  int KNumber();
extern  int KBackSpace();
extern  int KNewline();
extern  int KBottom();
extern  int KFarRight();
extern  int KFarLeft();
extern  int KTop();
extern  int KDown();
extern  int KUp();
extern  int KRight();
extern  int KLeft();
extern  int KExchange();
extern  int KSetMark();
extern  int KUnivArg();
extern  int KAbort();
extern  int KReDraw();
extern  int KReCalc();
extern  int KOther();

int (*Functions[])() =   {
        &KBackSpace,
        &KNewline,
        &KDown,
        &KUp,
        &KRight,
        &KLeft,

        &KLabel,                /* These functions are mainly in PCCOMD.C       */
        &KFormula,
        &KNumber,
        &KBottom,
        &KFarRight,
        &KFarLeft,
        &KTop,
        &KExchange,
        &KSetMark,
        &KUnivArg,
        &KAbort,
        &KReDraw,
        &KReCalc,
                        /* these functions are in this overlay */
        &KErase,
        &KRErase,
        &KPick,
        &KRPick,
        &KPut,
        &KRPut,
        &KInsLine,
        &KDelLine,
        &KInsColumn,
        &KDelColumn,

        1,1,1,1,1,1,1,1,1,1,1,1,
/*                         these functions are in overlay PCIO.C
        &KPosition,
        &KNxtUnlkd,
        &KWidth,
        &KFormat,
        &KFormat,
        &KJust,
        &KPrint,
        &KRPrint,
        &KRead,
        &KWrite,
        &KSave,
        &KZap,
*/
        2,2,2,2,2,2,2,2,2,2,2,2,2,2,
/*                              these functions are in overlay PCWIN.C
        &KLock,
        &KRLock,
        &KUnlock,
        &KRUnlock,
        &KEdit,
        &KWin2,
        &KWin1,
        &KWhere,
        &KUpdate,
        &KTitles,
	   &KF1Hlp,				/* XXX */
	   &KSF1Hlp,				/* XXX */
	   &KAF1Hlp,				/* XXX */
	   &KCF1Hlp,				/* XXX */
        &KHelp,
        &KMHelp,
        &KXHelp,
        &KExit,
*/
        3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
/*                              these functions are in overlay PCBUF.C
        &BSwap,
        &BReDraw,
        &KSwitchBuf,
        &KKillBuf,
        &KStatBuf,
        &KFind,
        &KAssocBuf,
        &KWinOther,
        &KDnPage,
        &KUpPage,
        &KRtPage,
        &KLtPage,
        &KDnOPage,
        &KUpOPage,
        &KRtOPage,
        &KLtOPage,
*/              
        &KOther    };
#endif

int Rrestans, Rprevans;         /* globals for saving user's Rel/Abs answers */

KErase()        /* delete an element */
{   MakSRegion();  Erase();   }

KRErase()
{   MakRegion();  NoCursor();  Erase();   }

Erase()         /* delete a range of elements */
{   int x, y, tempupd;
    FillPBuf(1);  tempupd = updform;  updform = 0;
    for (x=rangex[0]; x<=rangex[1]; x++)
        for (y=rangey[0]; y<=rangey[1]; y++)
            ReplElt(x, y, 0, x, y);
    ZapCmdLine();  if (updform=tempupd) keybuf = gotkey = Keys[RECALC];
}

KPick()         /* Pick up a single element */
{   MakSRegion();  FillPBuf(1);  ZapCmdLine();   }

KRPick()        /* Pick up a region */
{   MakRegion();  FillPBuf(1);  ZapCmdLine();   }

KPut()
{   MakSRegion();  Put();   }

KRPut()
{   MakRegion();  NoCursor();  Put();   }

Put()           /* put the element in the 'pick' buffer */
{   int tempupd, rangetype, x, y;
    if (!pickbuf) {CmdStart("\007Buffer empty");  LvCmdLine();  return;}
    if ((rangetype=GetRType()) != SINGLE)
        switch (picktype)       /* check for validity of operation */
        {   case SINGLE:break;
            case LINE:  if (rangetype != COL) goto bp;  break;
            case COL:   if (rangetype != LINE) goto bp; break;
            default: bp:CmdStart("\007Cannot copy buffer into defined range");
                        LvCmdLine();  return;
        }
    NoCursor();  tempupd = updform;  updform = 0;       /* turn off updating */
    Rrestans = 0;  Rprevans = 'y';              /* initialize answer globals */
    for (x=rangex[0]; x<=rangex[1]; x++)
        for (y=rangey[0]; y<=rangey[1]; y++)
        {   switch (picktype)
            {   case LINES:     cmdarg = pickx[1] - pickx[0] + 1;
                                if (KInsLine()) DumpPBuf(x, 1);  
                                break;
                case COLS:      cmdarg = picky[1] - picky[0] + 1;
                                if (KInsColumn()) DumpPBuf(1, y);  
                                break;
                default:        DumpPBuf(x, y);  break;
            }
            if (abort) goto rt;
        }
 rt:ZapCmdLine();  if(updform=tempupd) keybuf = gotkey = Keys[RECALC];
}

KInsLine()
{   int x, y, i, OK;  char **scr, **Madr(), **AskMem();
    if (lastx+cmdarg > MAXLINE)
    {   CmdStart("\007Too many lines");  LvCmdLine();  return(0);   }
    OK = 1;  NoCursor();
    for (x=curx; x<curx+cmdarg; x++)
    {   if (x > lastx)  
        {   if (PutElt(0, x, 1)) continue;  else {OK = 0;  break;}   }
        if (!(scr=AskMem(scrny+scrny+2))) {OK = 0;  break;}
        for (i=lastx; i>=x; i--) /* move screen pointers down one */
            screen[i+1] = screen[i];
        scr = Madr(screen[x]=scr);      /* actual address of new line */
        ++screen[x];                    /* increment over size */
        *(++scr) = (char *) -1;  lastx++;
        for (y=1; y<=scrny; y++) *(scr+y) = 0;
    }
    AdjForm(cmdarg,0);  InDeUpd(OK);
}

InDeUpd(OK)     /* common exit from Ins and Del Line/Column */
int OK;
{   ReDraw(w);  if (w2->active && w2->curbuf == w->curbuf) ReDraw(w2);
    modified = cmdarg = 1;  return(OK);
}

KDelLine()
{   int x;
    if (!lastx || curx > lastx)
    {   CmdStart("\007No line to delete");  LvCmdLine();  return(0);   }
    rangex[0] = curx;  rangex[1] = curx+cmdarg-1;
    rangey[0] = 1;  rangey[1] = scrny;  FillPBuf(0);  picktype = LINES;
    for (x=curx; x<=lastx; x++)
    {   if (x < curx+cmdarg) Mfree(screen[x]-1);
        if (x <= lastx-cmdarg) screen[x] = screen[x+cmdarg];
        if (x > lastx-cmdarg) screen[x] = 0;
    }
    if ((lastx -= cmdarg) < 0) lastx = 0;  AdjForm(-cmdarg,0);  InDeUpd(1);
}

KInsColumn()
{   int x, y, OK, offset, tempupd;  char **Mchg();
    if (lasty+cmdarg > MAXCOL)
    {   CmdStart("\007Too many columns");  LvCmdLine();  return(0);   }
    if (cury > lasty) return(1);  NoCursor();
    tempupd = updform;  updform = 0;    /* turn OFF formula updating */
    OK = PutElt(0, 1, lasty+cmdarg);  updform = tempupd;
    if (!OK) return(0);
    for (x=1; x<=lastx; x++)    /* move the screen elements over */
        for (y=lasty; y>=cury; y--) 
            if (y >= cury+cmdarg) Shuffle(x,y,-cmdarg); else
            {   *(Mchg(screen[x])+y) = 0;  indcolw[y] = coldp[y] = (char) -1; }
    AdjForm(0,cmdarg);  InDeUpd(1);
}

Shuffle(x, y, offset)   /* some common code from Ins/Del Column */
int x, y, offset;
{   char **pcp;
    pcp = Mchg(screen[x])+y;    *pcp = *(pcp+offset);
    if (x == 1)
    {   indcolw[y] = indcolw[y+offset];  coldp[y] = coldp[y+offset];   }
}

KDelColumn()
{   int x, y, count;
    if (!lastx || cury > lasty)
    {   CmdStart("\007No column to delete");  LvCmdLine();  return(0);   }
    rangex[0] = 1;  rangex[1] = lastx;
    rangey[0] = cury;  rangey[1] = cury+cmdarg-1;  FillPBuf(0);  
    picktype = COLS;  count = cmdarg;
    while (count--)
    {   for (x=1; x<=lastx; x++)        /* move elements internally */
        {   for (y=cury; y<lasty; y++) Shuffle(x,y,1);
            *(Mchg(screen[x])+lasty) = 0;
        }
        indcolw[lasty] = coldp[lasty] = (char) -1;  --(w->cols);
        if (--lasty == curx) break;
    }
    while (colpos(w,w->homey+w->cols) <= w->tcols+1) w->cols++;  --(w->cols);
    AdjForm(0,-cmdarg);  InDeUpd(1);
}

AdjForm(dx, dy)         /* adjust formulas based on dx, dy displacements */
int dx,dy;
{   int x, y, changed;  char *elt, *fp, *GetVElt(), *GetElt();
    for (x=1; x<=lastx; x++)
        for (y=1; y<=lasty; y++)
            if ((elt=GetElt(x,y)) && *(elt+TYPE) == FORMULA)
            {   fp = elt+FORMTXT;  changed = 0;
                while (*fp)
                    if (*fp++ == VARELT)  /* variable */
                        changed += AdjVar(elt, fp++, dy, cury) +
                                   AdjVar(elt, fp++, dx, curx);
                if (changed) Mchg(GetVElt(x,y));        /* mark as changed */
            }
}

AdjVar(elt, var, delta, cur)    /* adjust a single variable inside formula */
char *elt, *var;  int delta, cur;
{   int locvar, diff;
    locvar = *var;
    if (!delta || locvar < cur) return(0);              /* unaffected */
    diff = locvar - cur - 1;
    if ((delta < 0 && diff < 0 && diff >= delta) || (locvar += delta) <= 0)
    {   locvar = 0;  *((double *)(elt+VALUE)) = ERRVALUE;   }   /* error */
    *var = locvar;  return(1);
}

/* ---------------------------------------------------------------------- */
/*                      Pick Buffer Manipulation                          */
/* ---------------------------------------------------------------------- */

EmptyPBuf()     /* Empty pick buffer */
{   int i, nlines;
    if (!pickbuf) return;
    nlines = pickx[1] - pickx[0] + 1;
    for (i=1; i<=nlines; i++)
        EmptyLine(*(Madr(pickbuf)+i), picky[1]-picky[0]+1);
    Mfree(pickbuf);  pickbuf = 0;
}

EmptyLine(vlp, ncols)   /* Empty a line of ncols elements */
char **vlp;  int ncols;
{   int i;  char *velt;
    if (!vlp) return;
    for (i=1; i<=ncols; i++)
        if (velt = *(Madr(vlp)+i)) Mfree(velt);
    Mfree(vlp);
}

FillPBuf(copy)  /* fill pick buffer according to <rangex[], rangey[]> */
int copy;       /* copy == TRUE => copies not the elements themselves */
{   char **lp, *velt, *CopyElt();
    int i, j, nlines, ncols;
    NoCursor();  EmptyPBuf();                   /* make sure it's empty */
    nlines = rangex[1] - rangex[0] + 1;  ncols = rangey[1] - rangey[0] + 1;
    if (!(pickbuf=AskMem(nlines+nlines))) return(0);
    for (i=1; i<=nlines; i++)
    {   if (!(lp = *((char ***)(Madr(pickbuf)+i)) = AskMem(ncols+ncols))) 
            return(pickbuf=0);
        for (j=1; j<=ncols; j++)
        {   velt = GetVElt(rangex[0]+i-1, rangey[0]+j-1);
            *(Madr(lp)+j) = (copy)? CopyElt(velt) : velt;
        }
    }
    picktype = GetRType();
    pickx[0] = rangex[0];  pickx[1] = rangex[1];
    picky[0] = rangey[0];  picky[1] = rangey[1];
    return(1);
}

GetRType()      /* return type of range */
{   if (rangex[0]==rangex[1]) return((rangey[0]==rangey[1])? SINGLE : LINE);
    return((rangey[0]==rangey[1])? COL : RECTANGLE);
}

char *CopyElt(velt)    /* make a copy of an element in new space */
char *velt;     /* ask for SIZE-3 bytes to get at least SIZE */
{   int i, *ip;  char *elt, *vnelt, *nelt;
    if (!(elt=(char *)Madr(velt)) || 
        !(vnelt=(char *)AskMem(*(ip=(int *)(elt+SIZE))-3))) return(0);
    nelt = (char *) Madr(vnelt);
    for (i=2; i < *ip; i++) *(nelt+i) = *(elt+i);
    return(vnelt);
}

DumpPBuf(x,y)   /* Dump the pick buffer to spreadsheet starting at <x,y> */
int x, y;
{   char **vlp, *elt;  int i, j, nlines, ncols;
    nlines = pickx[1] - pickx[0] + 1;  ncols = picky[1] - picky[0] + 1;
    for (i=1; i<=nlines; i++)
    {   vlp = (char **) *(Madr(pickbuf)+i);
        for (j=1; j<=ncols; j++)
        {   ReplElt(pickx[0]+i-1, picky[0]+j-1, *(Madr(vlp)+j), x+i-1, y+j-1);
            if (abort) return(0);
        }
    }
}

ReplElt(oldx, oldy, oldelt, newx, newy)    /* replicate an element */
int oldx, oldy;  char *oldelt;  int newx, newy;
{   int relabs, emphvar, varbit, tx, *ip, answer;
    char *formp, *elt, *velt;

    velt = CopyElt(oldelt);
    if ((oldelt && !velt) || !PutElt(velt, newx, newy)) {abort = 1;  goto rt;}
    if (!(elt=(char *)Madr(velt)) || *(elt+TYPE) != FORMULA) goto rt;

    relabs = *(ip=(int *)(elt+RELABS)) & NORELABS;
    if (relabs == NORELABS)    /* must query */
    {st:relabs = emphvar = 0;  answer = Rprevans;
        varbit = 1;  formp = elt+FORMTXT;
        while (*formp)
        {   if (*formp == VARELT || *formp == EXTELT)   /* at a variable */
            {gn:if (!Rrestans)
                {   CmdStart("Formula: %c%d=%s  Relative? ");
                    printf(letcoord(oldy));  printf(oldx);
                    printf(DecForm(elt, EntBuf, ++emphvar));
                    answer = tolower(NextKey());
                }
             sw:switch (answer)
                {   case 'y':   relabs |= varbit;  break;
                    case '!':   Rrestans = answer = Rprevans;  goto sw;
                    case BEL:   BEEP;  abort = 1;  return;
                    case DEL:   goto st;
                    case '?':   CmdStart(
                "y=rel; !=repeat prev ans; DEL=restart; C-G=quit; other=abs");
                                NextKey();  --emphvar;  goto gn;
                    default:    break;
                }
                varbit <<= 1;  formp += 2;  Rprevans = answer;
            }
            ++formp;
        }
        if (!Rrestans)  /* don't mark anyone -- he probably didn't mean it! */
            *((int *)((char *) Mchg(oldelt) + RELABS)) = relabs; /* mark buf */
    }

    formp = elt+FORMTXT;
    while (*formp)              /* now adjust the variables accordingly */
        if (*formp == VARELT || *formp == EXTELT)  /* at a variable */
        {   if (relabs & 1)     /* relative bit is set */
            {   *++formp += newy-oldy;
                if (*formp < 1 || *formp > MAXCOL)
                {er: *formp = 0; *((double *)(elt+ERRVAL)) = ERRVALUE;  break;}
                tx = (*++formp & 0377) + newx-oldx;
                if (tx < 1 || tx > MAXLINE) goto er; else *formp++ = tx;
            } else formp += 3;
            relabs >>= 1;
        } else ++formp;
    CalcForm(elt+FORMTXT, elt+VALUE);
 rt:Tnorm();  CondPrt(newx, newy);
}
