/* UNDO system
   Copyright (C) 1992 Joseph H. Allen

This file is part of JOE (Joe's Own Editor)

JOE is free software; you can redistribute it and/or modify it under the 
terms of the GNU General Public License as published by the Free Software 
Foundation; either version 1, or (at your option) any later version.  

JOE is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
details.  

You should have received a copy of the GNU General Public License along with 
JOE; see the file COPYING.  If not, write to the Free Software Foundation, 
675 Mass Ave, Cambridge, MA 02139, USA.  */ 

#include "b.h"
#include "bw.h"
#include "blocks.h"
#include "ublock.h"
#include "undo.h"

extern int lightoff;

#define SMALL 1024

static UNDO undos={{&undos,&undos}};
static UNDO frdos={{&frdos,&frdos}};

int inundo=0;
int inredo=0;

UNDOREC frrecs={{&frrecs,&frrecs}};

UNDOREC *alrec()
 {
 UNDOREC *rec=(UNDOREC *)alitem(&frrecs,sizeof(UNDOREC));
 return rec;
 }

void frrec(rec)
UNDOREC *rec;
 {
 if(rec->del)
  if(rec->len<SMALL) free(rec->small);
  else
   {
   B *b=rec->big;
   bonline(b);
   brm(b);
   }
 enquef(UNDOREC,link,&frrecs,rec);
 }

UNDO *undomk(b)
B *b;
 {
 UNDO *undo=(UNDO *)alitem(&frdos,sizeof(UNDO));
 undo->nrecs=0;
 undo->ptr=0;
 undo->last=0;
 undo->first=0;
 undo->b=b;
 izque(UNDOREC,link,&undo->recs);
 enquef(UNDO,link,&undos,undo);
 return undo;
 }

void undorm(undo)
UNDO *undo;
 {
 frchn(&frrecs,&undo->recs);
 demote(UNDO,link,&frdos,undo);
 }

extern int dostaupd;

void doundo(bw,ptr)
BW *bw;
UNDOREC *ptr;
 {
 dostaupd=1;
 if(ptr->del)
  {
  if(ptr->len<SMALL)
   binsm(bw->cursor,ptr->small,(int)ptr->len);
  else
   {
   B *b=ptr->big;
   bonline(b);
   binsb(bw->cursor,bcpy(b->bof,b->eof));
   boffline(b);
   }
  }
 else
  {
  P *q=pdup(bw->cursor);
  pfwrd(q,ptr->len);
  bdel(bw->cursor,q);
  prm(q);
  }
 bw->b->changed=ptr->changed;
 }

int uundo(bw)
BW *bw;
 {
 UNDOREC *upto;
 UNDO *undo=bw->b->undo;
 if(!undo) return -1;
 if(!undo->nrecs) return -1;
 if(!undo->ptr)
  {
  pgoto(bw->cursor,undo->recs.link.prev->where);
  undo->ptr= &undo->recs;
  /* If this return is uncommented, then uundo will jump to where the undo
  is about to occur before actually undoing anything
  return 0; */
  }
 if(undo->ptr->link.prev==&undo->recs) return -1;
 upto=undo->ptr->link.prev->unit;
 loop:
 undo->ptr=undo->ptr->link.prev;
 pgoto(bw->cursor,undo->ptr->where);
 inundo=1;
 doundo(bw,undo->ptr);
 inundo=0;
 if(upto && upto!=undo->ptr) goto loop;
 return 0;
 }

int uredo(bw)
BW *bw;
 {
 UNDOREC *upto;
 UNDOREC *ptr;
 UNDO *undo=bw->b->undo;
 if(!undo) return -1;
 if(!undo->ptr) return -1;
 if(undo->ptr==&undo->recs) return -1;
 upto=undo->recs.link.prev->unit;
 do
  {
  ptr=undo->recs.link.prev;
  pgoto(bw->cursor,ptr->where);
  inredo=1;
  doundo(bw,ptr);
  inredo=0;
  frrec(deque(UNDOREC,link,ptr));
  undo->ptr=undo->ptr->link.next;
  }
  while(upto && upto!=ptr);
 return 0;
 }

void umclear()
 {
 UNDO *undo;
 for(undo=undos.link.next;undo!=&undos;undo=undo->link.next)
  {
  UNDOREC *rec;
  for(rec=undo->recs.link.next;rec!=&undo->recs;rec=rec->link.next) rec->min=0;
  }
 }

/* Eliminate excess undo records */

static void undogc(undo)
UNDO *undo;
 {
 UNDOREC *unit=undo->recs.link.next->unit;
 int flg=0;
 if(undo->ptr && undo->ptr->link.prev==&undo->recs) flg=1;
 if(unit)
  while(unit!=undo->recs.link.next)
   frrec(deque(UNDOREC,link,undo->recs.link.next));
 frrec(deque(UNDOREC,link,undo->recs.link.next));
 --undo->nrecs;
 if(flg) undo->ptr=undo->recs.link.next;
 }

void undomark()
 {
 UNDO *undo;
 for(undo=undos.link.next;undo!=&undos;undo=undo->link.next)
  if(undo->first)
   {
   undo->first->unit=undo->last;
   undo->last->unit=undo->first;
   undo->first=undo->last=0;
   if(++undo->nrecs==UNDOKEEP) undogc(undo);
   }
 }

/* Delete the alternate time-line after the user has resumed editing after
 * undoing some number of changes
 */

static void undoover(undo)
UNDO *undo;
 {
 undo->ptr=0;
 }

void undoins(undo,p,size)
UNDO *undo;
P *p;
long size;
 {
 UNDOREC *rec;
 if(inredo) return;
 if(!inundo) if(undo->ptr && undo->ptr!=&undo->recs) undoover(undo);
 rec=undo->recs.link.prev;
 if(rec!=&undo->recs && rec->min && !rec->del &&
    (p->byte==rec->where+rec->len || p->byte==rec->where)) rec->len+=size;
 else
  {
  rec=alrec();
  rec->del=0;
  if(!undo->first) undo->first=rec;
  undo->last=rec;
  rec->where=p->byte;
  rec->min=1;
  rec->unit=0;
  rec->len=size;
  rec->changed=undo->b->changed;
  enqueb(UNDOREC,link,&undo->recs,rec);
  }
 }

UNDOREC yanked={{&yanked,&yanked}};
int nyanked=0;
int inyank=0;
int justkilled=0;

int uyapp(bw)
BW *bw;
 {
 UNDOREC *rec=yanked.link.prev;
 if(rec!=&yanked) rec->where=bw->cursor->byte;
 return 0;
 }

void yankdel(where,b)
long where;
B *b;
 {
 UNDOREC *rec;
 long size=b->eof->byte;

 /* Store in yank buffer */
 rec=yanked.link.prev;
 if(!inyank)
 if(rec!=&yanked && where==rec->where && justkilled)
  {
  if(rec->len+size>=SMALL)
   {
   if(rec->len<SMALL)
    {
    rec->big=bmk(NULL);
    binsm(rec->big->bof,rec->small,(int)rec->len);
    boffline(rec->big);
    free(rec->small);
    }
   bonline(rec->big);
   binsb(rec->big->eof,bcpy(b->bof,b->eof));
   boffline(rec->big);
   }
  else
   {
   rec->small=(char *)realloc(rec->small,rec->len+size);
   brmem(b->bof,rec->small+rec->len,(int)size);
   }
  rec->len+=size;
  }
 else if(rec!=&yanked && where+size==rec->where && justkilled)
  {
  if(rec->len+size>=SMALL)
   {
   if(rec->len<SMALL)
    {
    rec->big=bmk(NULL);
    binsm(rec->big->bof,rec->small,(int)rec->len);
    boffline(rec->big);
    free(rec->small);
    }
   bonline(rec->big);
   binsb(rec->big->bof,bcpy(b->bof,b->eof));
   boffline(rec->big);
   }
  else
   {
   rec->small=(char *)realloc(rec->small,rec->len+size);
   mbkwd(rec->small+size,rec->small,(int)rec->len);
   brmem(b->bof,rec->small,(int)size);
   }
  rec->len+=size;
  rec->where=where;
  }
 else
  {
  if(++nyanked==100)
   frrec(deque(UNDOREC,link,yanked.link.next)), --nyanked;
  rec=alrec();
  if(size<SMALL)
   {
   rec->small=(char *)malloc(size);
   brmem(b->bof,rec->small,(int)b->eof->byte);
   }
  else
   {
   rec->big=bcpy(b->bof,b->eof);
   boffline(rec->big);
   }
  rec->where=where;
  rec->len=size;
  rec->del=1;
  enqueb(UNDOREC,link,&yanked,rec);
  }
 }

void undodel(undo,where,b)
UNDO *undo;
long where;
B *b;
 {
 UNDOREC *rec;
 long size=b->eof->byte;
 if(inredo)
  {
  brm(b);
  return;
  }
 if(!inundo) if(undo->ptr && undo->ptr!=&undo->recs) undoover(undo);

 yankdel(where,b);

 /* Store in undo buffer */
 rec=undo->recs.link.prev;
 if(rec!=&undo->recs && rec->min && rec->del && where==rec->where)
  {
  if(rec->len+size>=SMALL)
   {
   if(rec->len<SMALL)
    {
    rec->big=bmk(NULL);
    binsm(rec->big->bof,rec->small,(int)rec->len);
    boffline(rec->big);
    free(rec->small);
    }
   bonline(rec->big);
   binsb(rec->big->eof,b);
   boffline(rec->big);
   }
  else
   {
   rec->small=(char *)realloc(rec->small,rec->len+size);
   brmem(b->bof,rec->small+rec->len,(int)size);
   brm(b);
   }
  rec->len+=size;
  }
 else if(rec!=&undo->recs && rec->min && rec->del && where+size==rec->where)
  {
  if(rec->len+size>=SMALL)
   {
   if(rec->len<SMALL)
    {
    rec->big=bmk(NULL);
    binsm(rec->big->bof,rec->small,(int)rec->len);
    boffline(rec->big);
    free(rec->small);
    }
   bonline(rec->big);
   binsb(rec->big->bof,b);
   boffline(rec->big);
   }
  else
   {
   rec->small=(char *)realloc(rec->small,rec->len+size);
   mbkwd(rec->small+size,rec->small,(int)rec->len);
   brmem(b->bof,rec->small,(int)size);
   brm(b);
   }
  rec->len+=size;
  rec->where=where;
  }
 else
  {
  rec=alrec();
  if(size<SMALL)
   {
   rec->small=(char *)malloc(size);
   brmem(b->bof,rec->small,(int)b->eof->byte);
   brm(b);
   }
  else
   {
   rec->big=b;
   boffline(b);
   }
  if(!undo->first) undo->first=rec;
  undo->last=rec;
  rec->where=where;
  rec->min=1;
  rec->unit=0;
  rec->len=size;
  rec->del=1;
  rec->changed=undo->b->changed;
  enqueb(UNDOREC,link,&undo->recs,rec);
  }
 }

B *yankbuf=0;
long yankwhere= -1;

int uyank(bw)
BW *bw;
 {
 UNDOREC *ptr=yanked.link.prev;
 if(ptr!=&yanked)
  {
  if(ptr->len<SMALL)
   binsm(bw->cursor,ptr->small,(int)ptr->len);
  else
   {
   B *b=ptr->big;
   bonline(b);
   binsb(bw->cursor,bcpy(b->bof,b->eof));
   boffline(b);
   }
  pfwrd(bw->cursor,ptr->len);
  yankbuf=bw->b;
  yankwhere=bw->cursor->byte;
  return 0;
  }
 else return -1;
 }

int uyankpop(bw)
BW *bw;
 {
 if(bw->b==yankbuf && bw->cursor->byte==yankwhere)
  {
  P *q;
  UNDOREC *ptr=yanked.link.prev;
  deque(UNDOREC,link,&yanked);
  enqueb(UNDOREC,link,ptr,&yanked);
  q=pdup(bw->cursor);
  pbkwd(q,ptr->len);
  inyank=1;
  bdel(q,bw->cursor);
  inyank=0;
  prm(q);
  return uyank(bw);
  }
 else return uyank(bw);
 }

/* Clear changed-flag: make buffer look unmodified */

int unotmod(bw)
BW *bw;
 {
 bw->b->changed=0;
 msgnw(bw,"Modified flag cleared");
 return 0;
 }

int ucopy(bw)
BW *bw;
 {
 if(markv(1) && !square)
  {
  B *b=bcpy(markb,markk);
  yankdel(markb->byte,b);
  brm(b);
  if(lightoff) unmark(bw);
  return 0;
  }
 else
  {
  msgnw(bw,"No block");
  return -1;
  }
 }
