/*
 * LEVEE, or Captain Video;  A vi clone
 *
 * Copyright (c) 1982-2007 David L Parsons
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, without or
 * without modification, are permitted provided that the above
 * copyright notice and this paragraph are duplicated in all such
 * forms and that any documentation, advertising materials, and
 * other materials related to such distribution and use acknowledge
 * that the software was developed by David L Parsons (orc@pell.chi.il.us).
 * My name may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */
#include "levee.h"
#include "extern.h"

#define BUFSZ	sizeof(undo.coreblock)
#define AVAIL(x)	((x)<<1)
#define INDEX(x)	((1+x)>>1)

bool PROC
pushblock(u)
struct undostack *u;
{
    if (u->blockp == 0)
	if ((uwrite = OPEN_NEW(undobuf)) < 0)
	    return FALSE;
    if (BUFSZ == WRITE_TEXT(uwrite, u->coreblock, BUFSZ)) {
	u->blockp++;
	u->ptr = 0;
	return TRUE;
    }
    return FALSE;
}

bool PROC
pushw(u, i)
struct undostack *u;
int i;
{
    if (u->ptr >= PAGESIZE && !pushblock(u))
	return FALSE;
    u->coreblock[u->ptr++] = i;
    return TRUE;
}

bool PROC
pushmem(u, start, size)
struct undostack *u;
int start,size;
{
    int chunk;
    bool ok;

    ok = TRUE;
    while (ok && size > 0) {
	chunk = min(size, AVAIL(PAGESIZE-u->ptr));
	moveleft(&core[start], (char*)&u->coreblock[u->ptr], chunk);
	size -= chunk;
	start += chunk;
	if (size > 0)
	    ok = pushblock(u);
	else
	    u->ptr += INDEX(chunk);
    }
    return ok;
}

VOID PROC
zerostack(u)
struct undostack *u;
{
    if (u->blockp > 0)
	CLOSE_FILE(uwrite);
    u->blockp = 0;			 /* initialize the stack */
    u->ptr = 0;
}

bool PROC
uputcmd(u, size, start, cmd)
struct undostack *u;
int size,start;
char cmd;
{
    return(pushw(u, size) && pushw(u, start) && pushw(u, cmd));
}

VOID PROC
insert_to_undo(u, start, size)
struct undostack *u;
int start,size;
{
    if (uputcmd(u, size, start, U_DELC)) {
	fixmarkers(start, size);
	bufmax += size;
    }
    else
	error();
}

/* delete stuff from the buffer && put it into the undo stack */

bool PROC
delete_to_undo(u, start, lump)
struct undostack *u;
int start, lump;
{
    if (lump <= 0)
	return TRUE;
    else if (pushmem(u,start,lump) && uputcmd(u,lump,start,U_ADDC)) {
	moveleft(&core[start+lump], &core[start], bufmax-(start+lump));
	bufmax -= lump;
	fixmarkers(start,-lump);
	return TRUE;
    }
    else
	return FALSE;
}

/* copy stuff into the undo buffer */

bool PROC
move_to_undo(u, start, lump)
struct undostack *u;
int start,lump;
{
    return pushmem(u, start, lump) && uputcmd(u,lump,start,U_MOVEC);
}

bool PROC
popblock(u)
struct undostack *u;
{
    if (u->blockp > 0) {
	if (SEEK_POSITION(uread, (long)((--u->blockp)*BUFSZ), 0) < 0)
	    return FALSE;
	if (BUFSZ == READ_TEXT(uread, u->coreblock, BUFSZ)) {
	    u->ptr = PAGESIZE;
	    return TRUE;
	}
    }
    return FALSE;
}

bool PROC
popw(u, i)
struct undostack *u;
int *i;
{
    if (u->ptr < 1 && !popblock(u))
	return FALSE;
    *i = u->coreblock[--u->ptr];
    return TRUE;
}

bool PROC
popmem(u, start, size)
struct undostack *u;
int start, size;
{
    int chunk, loc;
    bool ok;

    loc = start+size;		/* running backwards */
    ok = TRUE;
    while (ok && size > 0) {
	chunk = min(size, AVAIL(u->ptr));
	size -= chunk;
	loc -= chunk;
	moveleft((char*)&u->coreblock[u->ptr-INDEX(chunk)], &core[loc], chunk);
	if (size > 0)
	    ok = popblock(u);
	else
	    u->ptr -= INDEX(chunk);
    }
    return(ok);
}
	
/* delete (I)nserted text */

bool PROC
takeout(save_undo,curp)
struct undostack *save_undo;
int *curp;
{
    int lump;

    return popw(&undo,curp) && popw(&undo,&lump)
			    && delete_to_undo(save_undo,*curp,lump);
}

bool PROC
copyover(save_undo,curp)
struct undostack *save_undo;
int *curp;
{
    int lump;
    
    return popw(&undo, curp) && popw(&undo, &lump)
			     && move_to_undo(save_undo, *curp, lump)
			     && popmem(&undo, *curp, lump);
}

bool PROC
putin(save_undo,curp)
struct undostack *save_undo;
int *curp;
{
    int lump;
    
    if (popw(&undo,curp) && popw(&undo,&lump) && (bufmax+lump < SIZE)) {
	insert_to_undo(save_undo, *curp, lump);
	moveright(&core[*curp], &core[*curp+lump], bufmax-*curp);
	if (popmem(&undo, *curp, lump))
	    return TRUE;
	else
	    moveleft(&core[*curp+lump], &core[*curp], bufmax-*curp);
    }
    return FALSE;
}

/* driver for undo -- returns last address modified || -1 if error */

int PROC
fixcore(topp)
int *topp;
{
    int curp;
    static struct undostack save_undo;
    bool closeio, ok;
    int cch;
    
    if (undo.blockp > 0 || undo.ptr > 0) {
	closeio = (undo.blockp > 0);
	if (closeio) {			/* save diskfile */
	    CLOSE_FILE(uwrite);		/* close current undo file */
	    rename(undobuf,undotmp);
	    uread = OPEN_OLD(undotmp);	/* reopen it for reading */
	    if (uread < 0)
		return -1;
	}
	*topp = SIZE+1;
	curp = -MAGICNUMBER;
	save_undo.blockp = save_undo.ptr = 0;
	ok = TRUE;
	while (ok && popw(&undo,&cch)) {
            switch (cch) {
	      case U_ADDC : ok = putin(&save_undo, &curp); break;
	      case U_MOVEC: ok = copyover(&save_undo, &curp); break;
	      case U_DELC : ok = takeout(&save_undo, &curp); break;
	    }
	    if (curp < *topp)
		*topp = curp;
	}
	if (curp >= 0)
	    undo = save_undo;
	if (closeio) {
	    CLOSE_FILE(uread);		/* Zap old buffer */
	    unlink(undotmp);
	}
	if (!ok)
	    error();
	return(curp);
    }
    return ERR;
}
