/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved
 *
 * Code Contributions By:	Bram Moolenaar			mool@oce.nl
 *							Tim Thompson			twitch!tjt
 *							Tony Andrews			onecom!wldrdg!tony 
 *							G. R. (Fred) Walter		watmath!watcgl!grwalter 
 */

/*
 * undo.c: multi level undo facility
 *
 * The saved lines are stored in a list of lists:
 *
 * u_oldhead----------------------------------------------+
 *                                                        |
 *                                                        V
 *              +--------------+    +--------------+    +--------------+
 * u_newhead--->| u_header     |    | u_header     |    | u_header     |
 *              |     uh_next------>|     uh_next------>|     uh_next---->NULL
 *       NULL<--------uh_prev  |<---------uh_prev  |<---------uh_prev  |
 *              |     uh_entry |    |     uh_entry |    |     uh_entry |
 *              +--------|-----+    +--------|-----+    +--------|-----+
 *                       |                   |                   |
 *                       V                   V                   V
 *              +--------------+    +--------------+    +--------------+
 *              | u_entry      |    | u_entry      |    | u_entry      |
 *              |     ue_next  |    |     ue_next  |    |     ue_next  |
 *              +--------|-----+    +--------|-----+    +--------|-----+
 *                       |                   |                   |
 *                       V                   V                   V
 *              +--------------+            NULL                NULL
 *              | u_entry      |
 *              |     ue_next  |
 *              +--------|-----+
 *                       |
 *                       V
 *                      etc.
 *
 * Each u_entry list contains the information for one undo or redo.
 * u_curhead points to the header of the last undo (the next redo), or is
 * NULL if nothing has been undone.
 *
 * All data is allocated with alloc_line(), thus it will be freed as soon as
 * we switch files!
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "ops.h"		/* for endop and startop */

struct u_entry
{
	struct u_entry	*ue_next;	/* pointer to next entry in list */
	linenr_t		ue_top;		/* number of line above undo block */
	linenr_t		ue_bot;		/* number of line below undo block */
	char			*ue_botptr;	/* pointer to line below undo block */
	char			**ue_array;	/* array of lines in undo block */
	long			ue_size;	/* number of lines in ue_array */
};

struct u_header
{
	struct u_header	*uh_next;	/* pointer to next header in list */
	struct u_header	*uh_prev;	/* pointer to previous header in list */
	struct u_entry	*uh_entry;	/* pointer to first entry */
	FPOS			 uh_curpos;	/* cursor position before saving */
};

static struct u_header *u_oldhead = NULL;	/* pointer to oldest header */
static struct u_header *u_newhead = NULL;	/* pointer to newest header */
static struct u_header *u_curhead = NULL;	/* pointer to current header */
static int				u_numhead = 0;		/* current number of headers */
static int				u_synced = TRUE;	/* entry lists are synced */

/*
 * variables for "U" command
 */
static char	   *u_line_ptr = NULL;		/* saved line for "U" command */
static linenr_t u_line_lnum;			/* line number of line in u_line */
static colnr_t	u_line_colnr;			/* optional column number */

static void u_getbot __ARGS((void));
static int u_savecommon __ARGS((linenr_t, linenr_t, int, linenr_t));
static void u_undoredo __ARGS((void));
static void u_freelist __ARGS((struct u_header *));
static void u_freeentry __ARGS((struct u_entry *, long));

/*
 * save the current line for both the "u" and "U" command
 */
	int
u_saveCurpos()
{
	return (u_save((linenr_t)(Curpos.lnum - 1), (linenr_t)(Curpos.lnum + 1)));
}

/*
 * Save the lines between "top" and "bot" for both the "u" and "U" command.
 * "top" may be 0 and bot may be line_count + 1.
 * Returns FALSE when lines could not be saved.
 */
	int
u_save(top, bot)
	linenr_t top, bot;
{
	if (top > line_count || top >= bot || bot > line_count + 1)
		return FALSE;	/* rely on caller to do error messages */

	if (top + 2 == bot)
		u_saveline((linenr_t)(top + 1));

	return (u_savecommon(top, bot, FALSE, (linenr_t)0));
}

/*
 * save the line "lnum" (used by :s command)
 * the line is handed over to the undo routines
 * The line is replaced, so the new bottom line is lnum + 1.
 */
	int
u_savesub(lnum)
	linenr_t	lnum;
{
	return (u_savecommon(lnum - 1, lnum + 1, TRUE, lnum + 1));
}

/*
 * a new line is inserted before line "lnum" (used by :s command)
 * The line is inserted, so the new bottom line is lnum + 1.
 */
 	int
u_inssub(lnum)
	linenr_t	lnum;
{
	return (u_savecommon(lnum - 1, lnum, TRUE, lnum + 1));
}

/*
 * save the lines "lnum" - "lnum" + nlines (used by delete command)
 * the lines are handed over to the undo routines
 * The lines are deleted, so the new bottom line is lnum.
 */
	int
u_savedel(lnum, nlines)
	linenr_t	lnum;
	long		nlines;
{
	return (u_savecommon(lnum - 1, lnum + nlines, TRUE, lnum));
}

	static int 
u_savecommon(top, bot, nocopy, newbot)
	linenr_t top, bot;
	int nocopy;
	linenr_t newbot;
{
	linenr_t		lnum;
	long			i;
	struct u_header *uhp;
	struct u_entry	*uep;
	long			size;

	/*
	 * if u_synced == TRUE make a new header
	 */
	if (u_synced)
	{
		/*
		 * if we undid more than we redid, free the entry lists before and
		 * including u_curhead
		 */
		while (u_curhead != NULL)
			u_freelist(u_newhead);

		/*
		 * free headers to keep the size right
		 */
		while (u_numhead > p_ul && u_oldhead != NULL)
			u_freelist(u_oldhead);

		if (p_ul < 0)			/* no undo at all */
			goto noundo;

		/*
		 * make a new header entry
		 */
		uhp = (struct u_header *)alloc_line((unsigned)sizeof(struct u_header));
		if (uhp == NULL)
			goto nomem;
		uhp->uh_prev = NULL;
		uhp->uh_next = u_newhead;
		if (u_newhead != NULL)
			u_newhead->uh_prev = uhp;
		uhp->uh_entry = NULL;
		uhp->uh_curpos = Curpos;	/* save cursor position for undo */
		u_newhead = uhp;
		if (u_oldhead == NULL)
			u_oldhead = uhp;
		++u_numhead;
	}
	else	/* find line number for ue_botptr for previous u_save() */
		u_getbot();

	size = bot - top - 1;
#ifndef UNIX
		/*
		 * With Amiga and MSDOS we can't handle big undo's, because then
		 * alloc_line would have to allocate a block larger than 32K
		 */
	if (size >= 8000)
		goto nomem;
#endif

	/*
	 * add lines in front of entry list
	 */
	uep = (struct u_entry *)alloc_line((unsigned)sizeof(struct u_entry));
	if (uep == NULL)
		goto nomem;

	uep->ue_size = size;
	uep->ue_top = top;
	uep->ue_botptr = NULL;
	if (newbot)
		uep->ue_bot = newbot;
		/*
		 * use 0 for ue_bot if bot is below last line or if the buffer is empty, in
		 * which case the last line may be replaced (e.g. with 'O' command).
		 */
	else if (bot > line_count || bufempty())
		uep->ue_bot = 0;
	else
		uep->ue_botptr = nr2ptr(bot);	/* we have to do ptr2nr(ue_botptr) later */

	if (size)
	{
		if ((uep->ue_array = (char **)alloc_line((unsigned)(sizeof(char *) * size))) == NULL)
		{
			u_freeentry(uep, 0L);
			goto nomem;
		}
		for (i = 0, lnum = top + 1; i < size; ++i)
		{
			if (nocopy)
				uep->ue_array[i] = nr2ptr(lnum++);
			else if ((uep->ue_array[i] = save_line(nr2ptr(lnum++))) == NULL)
			{
				u_freeentry(uep, i);
				goto nomem;
			}
		}
	}
	uep->ue_next = u_newhead->uh_entry;
	u_newhead->uh_entry = uep;
	u_synced = FALSE;
	return TRUE;

nomem:
	if (ask_yesno("no undo possible; continue anyway") == 'y')
	{
noundo:
		if (nocopy)
			for (lnum = top + 1; lnum < bot; ++lnum)
				free_line(nr2ptr(lnum));
		return TRUE;
	}
	return FALSE;
}

	void
u_undo(count)
	int count;
{
	/*
	 * If we get an undo command while executing a macro, we behave like the 
	 * original vi. If this happens twice in one macro the result will not
	 * be compatible.
	 */
	if (u_synced == FALSE)
	{
		u_sync();
		count = 1;
	}

	startop.lnum = 0;			/* unset '[ mark */
	endop.lnum = 0;				/* unset '] mark */
	while (count--)
	{
		if (u_curhead == NULL)						/* first undo */
			u_curhead = u_newhead;
		else if (p_ul > 0)							/* multi level undo */
			u_curhead = u_curhead->uh_next;			/* get next undo */

		if (u_numhead == 0 || u_curhead == NULL)	/* nothing to undo */
		{
			u_curhead = u_oldhead;					/* stick u_curhead at end */
			beep();
			return;
		}

		u_undoredo();
	}
}

	void
u_redo(count)
	int count;
{
	while (count--)
	{
		if (u_curhead == NULL || p_ul <= 0)		/* nothing to redo */
		{
			beep();
			return;
		}

		u_undoredo();

		u_curhead = u_curhead->uh_prev;			/* advance for next redo */
	}
}

/*
 * u_undoredo: common code for undo and redo
 *
 * The lines in the file are replaced by the lines in the entry list at u_curhead.
 * The replaced lines in the file are saved in the entry list for the next undo/redo.
 */
	static void
u_undoredo()
{
	char		**newarray = NULL;
	linenr_t	oldsize;
	linenr_t	newsize;
	linenr_t	top, bot;
	linenr_t	lnum;
	linenr_t	newlnum = INVLNUM;
	long		i;
	long		newcount = 0, oldcount = 0;
	struct u_entry *uep, *nuep;
	struct u_entry *newlist = NULL;

	CHANGED;
	for (uep = u_curhead->uh_entry; uep != NULL; uep = nuep)
	{
		top = uep->ue_top;
		bot = uep->ue_bot;
		if (bot == 0)
			bot = line_count + 1;
		if (top > line_count || top >= bot || bot > line_count + 1)
		{
			emsg("u_undo: line numbers wrong");
			return;
		}

		if (top < newlnum)
		{
			newlnum = top;
			Curpos.lnum = top + 1;
		}
		oldsize = bot - top - 1;	/* number of lines before undo */

		newsize = uep->ue_size;		/* number of lines after undo */

		/* delete the lines between top and bot and save them in newarray */
		if (oldsize)
		{
			if ((newarray = (char **)alloc_line((unsigned)(sizeof(char *) * oldsize))) == NULL)
			{
				/*
				 * We have messed up the entry list, repair is impossible.
				 * we have to free the rest of the list.
				 */
				while (uep != NULL)
				{
					nuep = uep->ue_next;
					u_freeentry(uep, uep->ue_size);
					uep = nuep;
				}
				break;
			}
			/* delete backwards, it goes faster in some cases */
			for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum)
				newarray[i] = delsline(lnum, oldsize != newsize);
		}

		/* adjust the marks if the number of lines does not change */
		if (oldsize == newsize)
			for (i = 0; i < oldsize; ++i)
				adjustmark(newarray[i], uep->ue_array[i]);

		/* insert the lines in u_array between top and bot */
		if (newsize)
		{
			for (lnum = top, i = 0; i < newsize; ++i, ++lnum)
				appendline(lnum, uep->ue_array[i]);
			free_line((char *)uep->ue_array);
		}
		newcount += newsize;
		oldcount += oldsize;
		uep->ue_size = oldsize;
		uep->ue_array = newarray;
		uep->ue_bot = top + newsize + 1;

		/*
		 * insert this entry in front of the new entry list
		 */
		nuep = uep->ue_next;
		uep->ue_next = newlist;
		newlist = uep;
	}

	u_curhead->uh_entry = newlist;

	/*
	 * If we deleted or added lines, report the number of less/more lines.
	 * Otherwise, report the number of changes (this may be incorrect
	 * in some cases, but it's better than nothing).
	 */
	if ((oldcount -= newcount) != 0)
		msgmore(-oldcount);
	else if (newcount > p_report)
		smsg("%ld change%s", newcount, plural(newcount));

	if (u_curhead->uh_curpos.lnum == Curpos.lnum)
		Curpos.col = u_curhead->uh_curpos.col;
	else
		Curpos.col = 0;
	updateScreen(CURSUPD);
}

/*
 * u_sync: stop adding to the current entry list
 */
	void
u_sync()
{
	if (u_synced)
		return;				/* already synced */
	u_getbot();				/* compute ue_bot of previous u_undo */
	u_curhead = NULL;
}

/*
 * u_getbot(): compute the line number of the previous u_undo
 */
	static void
u_getbot()
{
	register struct u_entry *uep;

	if (u_newhead == NULL || (uep = u_newhead->uh_entry) == NULL)
	{
		emsg("undo list corrupt");
		return;
	}

	if (uep->ue_botptr != NULL)
		if ((uep->ue_bot = ptr2nr(uep->ue_botptr, uep->ue_top)) == 0)
		{
			emsg("undo line missing");
			uep->ue_bot = uep->ue_top + 1;	/* guess what it is */
		}

	u_synced = TRUE;
}

/*
 * u_freelist: free one entry list and adjust the pointers
 */
	static void
u_freelist(uhp)
	struct u_header *uhp;
{
	register struct u_entry *uep, *nuep;

	for (uep = uhp->uh_entry; uep != NULL; uep = nuep)
	{
		nuep = uep->ue_next;
		u_freeentry(uep, uep->ue_size);
	}

	if (u_curhead == uhp)
		u_curhead = NULL;

	if (uhp->uh_next == NULL)
		u_oldhead = uhp->uh_prev;
	else
		uhp->uh_next->uh_prev = uhp->uh_prev;

	if (uhp->uh_prev == NULL)
		u_newhead = uhp->uh_next;
	else
		uhp->uh_prev->uh_next = uhp->uh_next;

	free_line((char *)uhp);
	--u_numhead;
}

/*
 * free entry 'uep' and 'n' lines in uep->ue_array[]
 */
	static void
u_freeentry(uep, n)
	struct u_entry *uep;
	register long n;
{
	while (n)
		free_line(uep->ue_array[--n]);
	free_line((char *)uep);
}

/*
 * invalidate the undo buffer; called when storage has already been released
 */

	void
u_clearall()
{
	u_newhead = u_oldhead = u_curhead = NULL;
	u_synced = TRUE;
	u_numhead = 0;
	u_line_ptr = NULL;
	u_line_lnum = 0;
}

/*
 * save the line "lnum" for the "U" command
 */
	void
u_saveline(lnum)
	linenr_t lnum;
{
	if (lnum == u_line_lnum)		/* line is already saved */
		return;
	if (lnum < 1 || lnum > line_count)	/* should never happen */
		return;
	u_clearline();
	u_line_lnum = lnum;
	if (Curpos.lnum == lnum)
		u_line_colnr = Curpos.col;
	else
		u_line_colnr = 0;
	u_line_ptr = save_line(nr2ptr(lnum));	/* when out of mem alloc() will give a warning */
}

/*
 * clear the line saved for the "U" command
 * (this is used externally for crossing a line while in insert mode)
 */
	void
u_clearline()
{
	if (u_line_ptr != NULL)
	{
		free_line(u_line_ptr);
		u_line_ptr = NULL;
		u_line_lnum = 0;
	}
}

/*
 * Implementation of the "U" command.
 * Differentiation from vi: "U" can be undone with the next "U".
 * We also allow the cursor to be in another line.
 */
	void
u_undoline()
{
	colnr_t t;

	if (u_line_ptr == NULL || u_line_lnum > line_count)
	{
		beep();
		return;
	}
		/* first save the line for the 'u' command */
	u_savecommon(u_line_lnum - 1, u_line_lnum + 1, FALSE, (linenr_t)0);
	u_line_ptr = replaceline(u_line_lnum, u_line_ptr);

	t = u_line_colnr;
	if (Curpos.lnum == u_line_lnum)
		u_line_colnr = Curpos.col;
	Curpos.col = t;
	Curpos.lnum = u_line_lnum;
	cursupdate();
	updateScreen(VALID_TO_CURSCHAR);
}


syntax highlighted by Code2HTML, v. 0.9.1