/* Undo/redo system management functions.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    This program 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.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

#include "ne.h"


/* How many undo steps we (re)allocate whenever we need more. */

#define STD_UNDO_STEP_SIZE		(1024)

/* How many undo stream bytes we (re)allocate whenever we need more. */

#define STD_UNDO_STREAM_SIZE	(16*1024)


/* This is the main function for recording an undo step (though it should be
called through add_undo_step). It adds to the given undo buffer an undo step
with given line, position and length, possibly enlarging the undo step
buffer. The redo stream is reset. Note that this function is transparent with
respect to the various positive/negative conventions about len and pos. */


static int cat_undo_step(undo_buffer *ub, int line, int pos, int len) {

	undo_step *ud;

	if (!ub) return(FALSE);

	assert_undo_buffer(ub);

	if (ub->cur_step >= ub->steps_size) {
		if (ud = realloc(ub->steps, (ub->steps_size+STD_UNDO_STEP_SIZE)*sizeof(undo_step))) {
			ub->steps_size += STD_UNDO_STEP_SIZE;
			ub->steps = ud;
		}
		else return(OUT_OF_MEMORY);
	}

	ub->steps[ub->cur_step].line = line;
	ub->steps[ub->cur_step].pos = pos;
	ub->steps[ub->cur_step].len = len;

	ub->last_step = ++ub->cur_step;
	ub->last_stream = ub->cur_stream;
	reset_stream(&ub->redo);

	return(0);
}



/* This function activates the chaining feature of the undo system. Any
operations recorded between start_undo_chain() and end_undo_chain() will be
undone or redone as a single entity. These calls can be nested, since a
nesting index keeps track of multiple calls. */


void start_undo_chain(buffer *b) {

	assert_buffer(b);
	assert(b->undo.cur_step == 0 || b->link_undos || b->undo.steps[b->undo.cur_step-1].pos>=0);

	b->link_undos++;
}


/* See the comments to the previous function. */

void end_undo_chain(buffer *b) {

	assert_undo_buffer(&b->undo);

	if (--b->link_undos) return;

	if (b->undo.cur_step && b->undo.steps[b->undo.cur_step-1].pos < 0) b->undo.steps[b->undo.cur_step-1].pos = -(1+b->undo.steps[b->undo.cur_step-1].pos);
}


/* This function is the external interface to the undo recording system. It
takes care of recording a position of -pos-1 if the undo linking feature is
in use. A positive len records an insertion, a negative len records a
deletion. When an insertion is recorded, len characters have to be added to
the undo stream with add_to_undo_stream(). */

int add_undo_step(buffer *b, int line, int pos, int len) {

	return(cat_undo_step(&b->undo, line, b->link_undos ? -pos-1 : pos, len));
}


/* This function adds to the undo stream a block of len characters pointed
to by p. */

int add_to_undo_stream(undo_buffer *ub, const unsigned char *p, int len) {

	assert(len>0);
	assert(ub != NULL);
	assert(ub->cur_step && ub->steps[ub->cur_step-1].len>0);

	if (!ub) return(-1);

	assert_undo_buffer(ub);

	if (!ub->cur_step || ub->steps[ub->cur_step-1].len<0) return(-1);

	if (ub->cur_stream+len >= ub->streams_size) {
		unsigned char *new_stream;

		if (new_stream = realloc(ub->streams, (ub->streams_size+STD_UNDO_STREAM_SIZE+len))) {
			ub->streams_size += STD_UNDO_STREAM_SIZE+len;
			ub->streams = new_stream;
		}
		else return(OUT_OF_MEMORY);
	}

	memcpy(&ub->streams[ub->cur_stream], p, len);
	ub->last_stream = (ub->cur_stream += len);

	return(0);
}


/* This function resets the undo buffer. All the previous undo steps are
lost. */

void reset_undo_buffer(undo_buffer *ub) {
	ub->cur_step =
	ub->last_step =
	ub->cur_stream =
	ub->last_stream =
	ub->steps_size =
	ub->streams_size = 0;
	free(ub->streams);
	free(ub->steps);
	ub->streams = NULL;
	ub->steps = NULL;
	reset_stream(&ub->redo);
}


/* This function undoes the current undo step, which is the last one, if no undo
has still be done, or an intermediate one, if some undo has already been done. */


int undo(buffer *b) {

	if (!b) return(-1);

	assert_buffer(b);

	if (b->undo.cur_step == 0) return(NOTHING_TO_UNDO);

	/* Important! insert_stream() and delete_stream() do different things
	while undoing or redoing. */

	b->undoing = 1;

	do {

		b->undo.cur_step--;

		if (b->undo.steps[b->undo.cur_step].len) {

			goto_line(b, b->undo.steps[b->undo.cur_step].line);
			goto_pos(b, b->undo.steps[b->undo.cur_step].pos >= 0 ? b->undo.steps[b->undo.cur_step].pos : -(1+b->undo.steps[b->undo.cur_step].pos));

			if (b->undo.steps[b->undo.cur_step].len<0) {
				delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, -b->undo.steps[b->undo.cur_step].len);
			}
			else {
				insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->undo.streams + (b->undo.cur_stream -= b->undo.steps[b->undo.cur_step].len), b->undo.steps[b->undo.cur_step].len);
			}
		}

	} while(b->undo.cur_step && b->undo.steps[b->undo.cur_step-1].pos<0);

	b->undoing = 0;

	return(0);
}


/* This function redoes the last step undone. */


int redo(buffer *b) {

	if (!b) return(-1);

	assert_buffer(b);

	if (b->undo.cur_step == b->undo.last_step) return(NOTHING_TO_REDO);

	/* Important! insert_stream() and delete_stream() do different things
	while undoing or redoing. */

	b->redoing = 1;

	do {
		if (b->undo.steps[b->undo.cur_step].len) {

			goto_line(b, b->undo.steps[b->undo.cur_step].line);
			goto_pos(b, b->undo.steps[b->undo.cur_step].pos >= 0 ? b->undo.steps[b->undo.cur_step].pos : -(1+b->undo.steps[b->undo.cur_step].pos));

			if (b->undo.steps[b->undo.cur_step].len<0)
				insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->undo.redo.stream+(b->undo.redo.len += b->undo.steps[b->undo.cur_step].len-1), -b->undo.steps[b->undo.cur_step].len+1);
			else {
				delete_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->undo.steps[b->undo.cur_step].len-1);
				b->undo.cur_stream += b->undo.steps[b->undo.cur_step].len;
			}
		}

		b->undo.cur_step++;

	} while(b->undo.steps[b->undo.cur_step-1].pos<0);

	b->redoing = 0;

	return(0);
}
