/*
    libundo, an easy to use undo/redo management library
    Copyright 1999 Matt Kimball

    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 2 of the License, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <string.h>

#include "undoP.h"

static void undo_history_stream_destroy(UNDO_MEMORY_STREAM *stream);
static size_t undo_history_stream_read(UNDO_MEMORY_STREAM *stream, 
									   size_t offset,
									   void *mem, size_t size);
static int undo_history_add_item(UNDO_HISTORY *history, 
								 void *mem, size_t size);
static UNDO_MEMORY_STREAM *undo_history_stream(UNDO_HISTORY *history);
static void undo_history_shrink(UNDO_HISTORY *history, size_t size);
static void undo_history_strip(UNDO_HISTORY *history, int count);
static void undo_history_strip_trailing(UNDO_HISTORY *history);

UNDO_HISTORY *undo_history_new(void) {
	UNDO_HISTORY *history;

	history = NEW(UNDO_HISTORY);

	return history;
}

int undo_history_destroy(UNDO_HISTORY *history) {
	free(history);

	UNDO_SUCCESS;
}

int undo_history_record(UNDO_HISTORY *history, UNDO_MEMORY_STREAM *stream) {
	void *mem;
	size_t size;
	int ret;

	if(history->memory_limit == 0)
		return UNDO_NOLIMIT;

	size = undo_memory_stream_length(stream);
	if(size > history->memory_limit) {
		undo_history_shrink(history, 0);
		UNDO_SUCCESS;
	}
	
	if(history->logical)
		undo_history_strip_trailing(history);
    
	undo_history_shrink(history, history->memory_limit - size);

	mem = malloc(size);
	if(mem == NULL)
		return UNDO_NOMEM;
	stream->read(stream, 0, mem, size);

	ret = undo_history_add_item(history, mem, size);
	if(ret) {
		free(mem);
	}

	return ret;
}

UNDO_MEMORY_STREAM *undo_history_undo(UNDO_HISTORY *history) {
	UNDO_MEMORY_STREAM *stream;

	if(history->ix == 0)
		return NULL;

	stream = undo_history_stream(history);

	if(stream)
		history->ix--;

	return stream; 
}

UNDO_MEMORY_STREAM *undo_history_redo(UNDO_HISTORY *history) {
	UNDO_MEMORY_STREAM *stream;

	if(history->ix >= history->length - 1)
		return NULL;

	stream = undo_history_stream(history);

	if(stream)
		history->ix++;

	return stream; 
}

unsigned undo_history_undo_count(UNDO_HISTORY *history) {
	if(history->length)
		return history->ix;

	return 0;
}

unsigned undo_history_redo_count(UNDO_HISTORY *history) {
	if(history->length) 
		return history->length - 1 - history->ix;

	return 0;
}

void undo_history_set_memory_limit(UNDO_HISTORY *history, size_t limit) {
	history->memory_limit = limit;
}

void undo_history_set_logical(UNDO_HISTORY *history, int onoff) {
	history->logical = onoff;
}

size_t undo_history_memory_usage(UNDO_HISTORY *history) {
	int ix;
	size_t total;

	total = 0;
	for(ix = 0; ix < history->length; ix++) {
		total += history->item[ix].size;
	}

	return total;
}

static void undo_history_stream_destroy(UNDO_MEMORY_STREAM *stream) {
	free(stream);
}

static size_t undo_history_stream_read(UNDO_MEMORY_STREAM *stream, 
									   size_t offset,
									   void *mem, size_t size) {
	UNDO_HISTORY *history;
	size_t pos;
	void *history_mem;
	size_t history_size;

	history = (UNDO_HISTORY *)stream->implementation;

	pos = 0;
	history_mem = history->item[history->ix].mem;
	history_size = history->item[history->ix].size;
	return undo_memory_stream_write(offset, mem, size,
									&pos, history_mem, history_size);
}

static int undo_history_add_item(UNDO_HISTORY *history, 
								 void *mem, size_t size) {
	UNDO_HISTORY_ITEM *new_item;

	new_item = (UNDO_HISTORY_ITEM *) realloc(history->item, 
					  sizeof(UNDO_HISTORY_ITEM) * (history->length + 1));
	if(new_item == NULL) {
		return UNDO_NOMEM;
	}
	history->item = new_item;

	history->length++;

	history->ix = history->length - 1;
	history->item[history->ix].mem = mem;
	history->item[history->ix].size = size;

	UNDO_SUCCESS;
}

static UNDO_MEMORY_STREAM *undo_history_stream(UNDO_HISTORY *history) {
	UNDO_MEMORY_STREAM *stream;

	stream = NEW(UNDO_MEMORY_STREAM);
	if(stream == NULL)
		return NULL;

	stream->implementation = history;
	stream->destroy = undo_history_stream_destroy;
	stream->read = undo_history_stream_read;

	return stream;
}

static void undo_history_shrink(UNDO_HISTORY *history, size_t size) {
	int first_saved;
	int ix;
	size_t total;

	total = 0;
	first_saved = 0;
	for(ix = 0; ix < history->length; ix++) {
		total += history->item[ix].size;
		while(total > size) {
			total -= history->item[first_saved].size;
			first_saved++;
		}
	}

	undo_history_strip(history, first_saved);
}

static void undo_history_strip(UNDO_HISTORY *history, int count) {
	int ix;

	if(count == 0)
		return;

	for(ix = 0; ix < count; ix++) {
		free(history->item[ix].mem);
	}

	memmove(history->item, history->item + count, 
			sizeof(UNDO_HISTORY_ITEM) * (history->length - count));
	history->length -= count;
	history->ix = history->length - 1;
}

static void undo_history_strip_trailing(UNDO_HISTORY *history) {
	int ix;
	UNDO_HISTORY_ITEM *new_item;
    
	for(ix = history->ix + 1; ix < history->length; ix++) {
		free(history->item[ix].mem);
	}

	if(history->length > history->ix + 1) {
		new_item = (UNDO_HISTORY_ITEM *) realloc(history->item, 
			sizeof(UNDO_HISTORY_ITEM) * (history->ix + 1));
		if(new_item == NULL) {
			return;
		}
		history->item = new_item;

		history->length = history->ix + 1;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1