/*
 * tkTextUndo.c --
 *
 *	This module provides the implementation of an undo stack.
 *
 * Copyright © 2015-2017 Gregor Cramer.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkTextUndo.h"
#include <assert.h>

#ifndef MAX
# define MAX(a,b) ((a) < (b) ? b : a)
#endif

#ifndef MIN
# define MIN(a,b) ((a) < (b) ? a : b)
#endif


typedef TkTextUndoMyAtom MyUndoAtom;


/*
 * Our list of undo/redo atoms is a circular double linked list.
 * It's circular beause the "last" pointer is connected with the
 * "root" pointer. The list starts either with the oldest undo atom,
 * or with the newest redo atom if no undo atom exists.
 *
 * 'stack->last' is always pointing to the newest undo item, or
 * NULL if no undo item exists.
 *
 * 'stack->root' is always pointing either to the oldest undo item,
 * or to the oldest redo item if no undo item exists.
 *
 * 'stack->current' is the current atom which receives all pushed
 * items (TkTextUndoPushItem), and is not yet linked into the list.
 * 'stack->current' can be NULL, in this case it has to be created
 * when the user is pushing an item.
 *
 * last ------------------+
 * root --+               |
 *        V               V
 *      +---+   +---+   +---+   +---+   +---+
 *   +->| A |-->| B |-->| C |-->| d |-->| e |--+
 *   |  +---+   +---+   +---+   +---+   +---+  |
 *   ------------------------------------------+
 *      undo: 3	                redo: 2
 *
 * A = oldest undo item
 * B = second oldest undo item
 * C = newest undo item
 * d = newest redo item
 * e = oldest redo item
 */


#define ATOM_SIZE(n) (offsetof(TkTextUndoMyAtom, data) \
	+ offsetof(TkTextUndoAtom, array) + (n)*sizeof(TkTextUndoSubAtom))


enum { InitialCapacity = 20 };


static void
FreeItems(
    const TkTextUndoStack stack,
    const TkTextUndoAtom *atom)
{
    TkTextUndoFreeProc *freeProc = stack->freeProc;
    const TkTextUndoSubAtom *arr;
    unsigned i, n;

    assert(atom);

    if (!freeProc) {
	return;
    }

    arr = atom->array;
    n = atom->arraySize;

    for (i = 0; i < n; ++i) {
	freeProc(stack, &arr[i]);
    }
}


static void
Release(
    TkTextUndoStack stack,
    MyUndoAtom *atom)
{
    MyUndoAtom *first, *root, *prev;

    if (!atom) {
	return;
    }

    assert(stack->root);
    first = atom;
    root = stack->root;
    prev = atom->prev;

    /*
     * Now delete all atoms starting at 'atom' until we reach the end (inclusive).
     */

    do {
	MyUndoAtom *next = atom->next;
	FreeItems(stack, &atom->data);
	ckfree(atom);
	atom = next;
    } while (atom != root);

    /*
     * Update the list pointers accordingly.
     */

    if (first == root) {
	stack->root = stack->last = NULL;
    } else {
	root->prev = prev;
	prev->next = root;
    }
}


static void
ResetCurrent(
    TkTextUndoStack stack,
    int force)
{
    TkTextUndoMyAtom *current = stack->current;

    if (current) {
	FreeItems(stack, &current->data);
    }

    if (force || !current || current->capacity > InitialCapacity) {
	static unsigned Size = ATOM_SIZE(InitialCapacity);
	current = stack->current = (TkTextUndoMyAtom *)ckrealloc(current, Size);
	memset((void *)current, 0, Size);
	current->capacity = InitialCapacity;
    }

    current->data.arraySize = 0;
    current->data.size = 0;
    current->undoSize = 0;
}


static MyUndoAtom *
SwapCurrent(
    TkTextUndoStack stack,
    MyUndoAtom *atom)
{
    MyUndoAtom *current = stack->current;

    assert(atom != current);

    if (current->capacity != current->data.size) {
	current = stack->current = (MyUndoAtom *)ckrealloc(current, ATOM_SIZE(current->data.arraySize));
	current->capacity = current->data.arraySize;
    }

    if (!atom) {
	/*
	 * Just use the 'stack->current' item.
	 */
	stack->current = NULL;
	return current;
    }

    /*
     * Exchange given 'atom' with 'stack->current', this means that
     * 'stack->current' will be linked into the list replacing 'atom',
     * and 'atom' will become 'stack->current'.
     */

    if (atom->next == atom) {
	current->next = current;
	current->prev = current;
    } else {
	current->next = atom->next;
	current->prev = atom->prev;
	atom->next->prev = current;
	atom->prev->next = current;
    }

    stack->current = atom;
    atom->data.arraySize = 0;
    atom->data.size = 0;
    atom->undoSize = 0;
    atom->next = atom->prev = NULL;

    if (stack->root == atom) {
	stack->root = current;
    }
    if (stack->last == atom) {
	stack->last = current;
    }

    return current;
}


static int
ClearRedoStack(
    TkTextUndoStack stack)
{
    MyUndoAtom *atom;

    if (stack->redoDepth == 0) {
	return 0;
    }

    atom = stack->last ? stack->last->next : stack->root;

    assert(atom);
    stack->redoDepth = 0;
    stack->redoSize = 0;
    stack->redoItems = 0;
    Release(stack, atom);

    return 1;
}


static void
InsertCurrentAtom(
    TkTextUndoStack stack)
{
    MyUndoAtom *atom;
    MyUndoAtom *current = stack->current;

    if (!current || current->data.arraySize == 0) {
	assert(!stack->doingUndo && !stack->doingRedo);
	return;
    }

    if (stack->maxSize > 0 && !stack->doingRedo) {
	unsigned newStackSize = current->data.size;

	if (stack->doingUndo) {
	    newStackSize = MAX(current->undoSize, newStackSize);
	}
	newStackSize += stack->undoSize + stack->redoSize;

	if (newStackSize > stack->maxSize) {
	    /*
	     * We do not push this atom, because the addtional size would
	     * exceed the maximal content size.
	     *
	     * Note that we must push an undo atom while performing a redo,
	     * but this case is already catched, and the size of this atom
	     * has already been taken into account (with the check of
	     * 'current->undoSize' when inserting the reverting redo atom;
	     * we assume that the new undo atom size is the same as the
	     * undo size before the redo).
	     */
	    if (stack->doingUndo) {
		/*
		 * We do not push this redo atom while peforming an undo, so all
		 * redoes are expired, we have to delete them.
		 */
		ClearRedoStack(stack);
	    } else {
		/*
		 * We do not push this undo atom, so the content becomes irreversible.
		 */
		stack->irreversible = 1;
	    }
	    FreeItems(stack, &stack->current->data);
	    ResetCurrent(stack, 0);
	    return;
	}
    }

    if (stack->doingRedo) {
	/*
	 * We'll push an undo atom while performing a redo.
	 */
	if (!stack->last) {
	    stack->last = stack->root;
	}
	atom = stack->last;
	SwapCurrent(stack, atom);
	stack->undoDepth += 1;
	stack->undoSize += atom->data.size;
	stack->undoItems += atom->data.arraySize;
    } else if (stack->doingUndo) {
	/*
	 * We'll push a redo atom while performing an undo.
	 */
	assert(stack->maxRedoDepth <= 0 || (int) stack->redoDepth < stack->maxRedoDepth);
	atom = stack->last ? stack->last->next : stack->root;
	SwapCurrent(stack, atom);
	stack->redoDepth += 1;
	stack->redoSize += atom->data.size;
	stack->redoItems += atom->data.arraySize;
    } else if (stack->last && stack->undoDepth == stack->maxUndoDepth) {
	/*
	 * We've reached the maximal stack limit, so delete the oldest undo
	 * before inserting the new item. The consequence is that now the content
	 * becomes irreversible. Furthermore all redo items will expire.
	 */
	ClearRedoStack(stack);
	assert(stack->last);
	atom = stack->last->next;
	stack->root = atom->next;
	stack->last = atom;
	stack->undoSize -= atom->data.size;
	stack->undoItems -= atom->data.arraySize;
	stack->irreversible = 1;
	FreeItems(stack, &atom->data);
	SwapCurrent(stack, atom);
	stack->undoSize += atom->data.size;
	stack->undoItems += atom->data.arraySize;
    } else {
	/*
	 * Just insert the newly undo atom. Furthermore all redo items will expire.
	 */
	ClearRedoStack(stack);
	if (stack->last == NULL) {
	    stack->last = stack->root;
	}
	atom = SwapCurrent(stack, NULL);
	if ((atom->prev = stack->last)) {
	    atom->next = stack->last->next;
	    stack->last->next->prev = atom;
	    stack->last->next = atom;
	} else {
	    atom->next = atom->prev = stack->root = atom;
	}
	stack->last = atom;
	stack->undoDepth += 1;
	stack->undoSize += atom->data.size;
	stack->undoItems += atom->data.arraySize;
    }

    if (!stack->doingUndo) {
	/*
	 * Remember the size of this undo atom, probably we need it for the
	 * decision whether to push a redo atom when performing an undo.
	 */
	atom->undoSize = atom->data.size;
    }

    /*
     * Reset the buffer for next action.
     */
    ResetCurrent(stack, 0);
}


static int
ResetStack(
    TkTextUndoStack stack,
    int irreversible)
{
    int contentChanged;

    assert(stack);

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    contentChanged = stack->undoDepth > 0 || stack->redoDepth > 0 || stack->current;

    if (contentChanged) {
	Release(stack, stack->root);
	ResetCurrent(stack, 1);
	stack->root = NULL;
	stack->last = NULL;
	stack->undoDepth = 0;
	stack->redoDepth = 0;
	stack->undoItems = 0;
	stack->redoItems = 0;
	stack->undoSize = 0;
	stack->redoSize = 0;
	stack->irreversible = irreversible;
	stack->pushSeparator = 0;

	if (stack->contentChangedProc) {
	    stack->contentChangedProc(stack);
	}
    }

    return TCL_OK;
}


TkTextUndoStack
TkTextUndoCreateStack(
    unsigned maxUndoDepth,
    int maxRedoDepth,
    unsigned maxSize,
    TkTextUndoPerformProc undoProc,
    TkTextUndoFreeProc freeProc,
    TkTextUndoStackContentChangedProc contentChangedProc)
{
    TkTextUndoStack stack;

#ifndef _MSC_VER /* MSVC erroneously triggers warning C4550 */
    assert(undoProc);
#endif

    stack = (TkTextUndoStack)ckalloc(sizeof(*stack));
    memset(stack, 0, sizeof(*stack));
    stack->undoProc = undoProc;
    stack->freeProc = freeProc;
    stack->contentChangedProc = contentChangedProc;
    stack->maxUndoDepth = maxUndoDepth;
    stack->maxRedoDepth = MAX(maxRedoDepth, -1);
    stack->maxSize = maxSize;

    return stack;
}


void
TkTextUndoDestroyStack(
    TkTextUndoStack *stackPtr)
{
    if (stackPtr) {
	TkTextUndoStack stack = *stackPtr;

	if (stack) {
	    assert(stack);
	    TkTextUndoClearStack(stack);
	    if (stack->current) {
		FreeItems(stack, &stack->current->data);
	    }
	    ckfree(stack);
	    *stackPtr = NULL;
	}
    }
}


int
TkTextUndoResetStack(
    TkTextUndoStack stack)
{
    return stack ? ResetStack(stack, 0) : TCL_ERROR;
}


int
TkTextUndoClearStack(
    TkTextUndoStack stack)
{
    return stack ? ResetStack(stack, stack->undoDepth > 0) : TCL_ERROR;
}


int
TkTextUndoClearUndoStack(
    TkTextUndoStack stack)
{
    if (!stack) {
	return TCL_OK;
    }

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    if (stack->undoDepth > 0) {
	TkTextUndoMyAtom *atom;

	assert(stack->last);
	stack->undoDepth = 0;
	stack->undoSize = 0;
	stack->undoItems = 0;
	atom = stack->root;
	stack->root = stack->last->next;
	stack->last = NULL;
	Release(stack, atom);
	ResetCurrent(stack, 1);
	stack->irreversible = 1;

	if (stack->contentChangedProc) {
	    stack->contentChangedProc(stack);
	}
    }

    return TCL_OK;
}


int
TkTextUndoClearRedoStack(
    TkTextUndoStack stack)
{
    if (!stack) {
	return TCL_OK;
    }

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    if (ClearRedoStack(stack) && stack->contentChangedProc) {
	stack->contentChangedProc(stack);
    }

    return TCL_OK;
}


int
TkTextUndoSetMaxStackDepth(
    TkTextUndoStack stack,
    unsigned maxUndoDepth,
    int maxRedoDepth)
{
    assert(stack);

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    if (maxUndoDepth > 0 || maxRedoDepth >= 0) {
	unsigned depth = stack->maxUndoDepth;

	if (depth == 0) {
	    depth = stack->undoDepth + stack->redoDepth;
	}

	if ((0 < maxUndoDepth && maxUndoDepth < depth)
		|| (0 <= maxRedoDepth && (unsigned) maxRedoDepth < (unsigned) stack->maxRedoDepth)) {
	    unsigned deleteRedos = MIN(stack->redoDepth, depth - maxUndoDepth);

	    if (0 <= maxRedoDepth && maxRedoDepth < stack->maxRedoDepth) {
		deleteRedos = MIN(stack->redoDepth,
			MAX(deleteRedos, (unsigned) stack->maxRedoDepth - maxRedoDepth));
	    }

	    stack->redoDepth -= deleteRedos;
	    depth = maxUndoDepth - deleteRedos;

	    if (deleteRedos > 0) {
		MyUndoAtom *atom = stack->root;

		/*
		 * We have to reduce the stack size until the depth will not
		 * exceed the given limit anymore. Start with oldest redoes,
		 * and continue with oldest undoes if necessary.
		 */

		for ( ; deleteRedos > 0; --deleteRedos) {
		    atom = atom->prev;
		    stack->redoSize -= atom->data.size;
		    stack->redoItems -= atom->data.arraySize;
		}

		Release(stack, atom);
	    }

	    if (maxUndoDepth > 0 && stack->undoDepth > depth) {
		MyUndoAtom *root = stack->root;
		MyUndoAtom *atom = stack->root;
		unsigned deleteUndos = stack->undoDepth - depth;

		stack->undoDepth -= deleteUndos;

		for ( ; deleteUndos > 0; --deleteUndos) {
		    stack->undoSize -= root->data.size;
		    stack->undoItems -= root->data.arraySize;
		    root = root->next;
		}

		stack->root = root;

		/* We have to delete undoes, so the content becomes irreversible. */
		stack->irreversible = 1;

		Release(stack, atom);
	    }

	    if (stack->contentChangedProc) {
		stack->contentChangedProc(stack);
	    }
	}
    }

    stack->maxUndoDepth = maxUndoDepth;
    stack->maxRedoDepth = MAX(maxRedoDepth, -1);
    return TCL_OK;
}


int
TkTextUndoSetMaxStackSize(
    TkTextUndoStack stack,
    unsigned maxSize,
    int applyImmediately)
{
    assert(stack);

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    if (applyImmediately
	    && 0 < maxSize
	    && maxSize < stack->undoSize + stack->redoSize) {
	unsigned size = stack->undoSize + stack->redoSize;
	MyUndoAtom *atom = stack->root;
	unsigned depth = stack->redoDepth;

	/*
	 * We have to reduce the stack size until the size will not exceed
	 * the given limit anymore. Start with oldest redoes, and continue
	 * with oldest undoes if necessary.
	 */

	while (depth > 0 && maxSize < size) {
	    atom = atom->prev;
	    size -= atom->data.size;
	    stack->redoSize -= atom->data.size;
	    stack->redoItems -= atom->data.arraySize;
	    depth -= 1;
	}

	while (atom->data.size == 0 && atom != stack->root) {
	    stack->redoItems += atom->data.arraySize;
	    atom = atom->next;
	    depth += 1;
	}

	if (depth < stack->redoDepth) {
	    stack->redoDepth = depth;
	    Release(stack, atom);
	}

	if (maxSize < size && stack->last) {
	    MyUndoAtom *root = stack->root;

	    depth = stack->undoDepth;

	    while (depth > 0 && maxSize < size) {
		size -= root->data.size;
		stack->undoSize -= root->data.size;
		stack->undoItems -= root->data.arraySize;
		depth -= 1;
		root = root->next;
	    }

	    while (root->data.size == 0 && depth < stack->undoDepth) {
		stack->undoItems += root->data.arraySize;
		depth += 1;
		root = root->prev;
	    }

	    if (depth < stack->undoDepth) {
		stack->undoDepth = depth;
		atom = stack->root;
		stack->root = root;
		/*
		 * We have to delete undoes, so the content becomes irreversible.
		 */
		stack->irreversible = 1;
		Release(stack, atom);
	    }
	}

	if (stack->contentChangedProc) {
	    stack->contentChangedProc(stack);
	}
    }

    stack->maxSize = maxSize;
    return TCL_OK;
}


static void
PushSeparator(
    TkTextUndoStack stack,
    int force)
{
    assert(stack);

    if (force || stack->pushSeparator) {
	/*
	 * When performing an undo/redo, exact one reverting undo/redo atom has
	 * to be inserted, not more. So we do not allow the push of separators
	 * as long as an undo/redo action is in progress.
	 */

	if (!stack->doingUndo && !stack->doingRedo) {
	    /*
	     * Do not trigger stack->contentChangedProc here, because this has been
	     * already done via TkTextUndoPushItem/TkTextUndoPushRedoItem.
	     */
	    InsertCurrentAtom(stack);
	}
    }

    stack->pushSeparator = 0;
}


void
TkTextUndoPushSeparator(
    TkTextUndoStack stack,
    int immediately)
{
    assert(stack);

    if (immediately) {
	PushSeparator(stack, 1);
    } else {
	/* Postpone pushing a separator until next item will be pushed. */
	stack->pushSeparator = 1;
    }
}


int
TkTextUndoPushItem(
    TkTextUndoStack stack,
    TkTextUndoItem item,
    unsigned size)
{
    MyUndoAtom *atom;
    TkTextUndoSubAtom *subAtom;

    assert(stack);
    assert(item);

    PushSeparator(stack, 0);

    if (stack->doingUndo && TkTextUndoRedoStackIsFull(stack)) {
	if (stack->freeProc) {
	    stack->freeProc(stack, (const TkTextUndoSubAtom *)item);
	}
	return TCL_ERROR;
    }

    atom = stack->current;

    if (!atom) {
	ResetCurrent(stack, 1);
	atom = stack->current;
    } else if (atom->data.arraySize == atom->capacity) {
	atom->capacity *= 2;
	atom = stack->current = (TkTextUndoMyAtom *)ckrealloc(atom, ATOM_SIZE(atom->capacity));
    }

    subAtom = ((TkTextUndoSubAtom *) atom->data.array) + atom->data.arraySize++;
    subAtom->item = item;
    subAtom->size = size;
    subAtom->redo = stack->doingUndo;
    atom->data.size += size;
    atom->data.redo = stack->doingUndo;

    if (stack->contentChangedProc && !stack->doingUndo && !stack->doingRedo) {
	stack->contentChangedProc(stack);
    }

    return TCL_OK;
}


int
TkTextUndoPushRedoItem(
    TkTextUndoStack stack,
    TkTextUndoItem item,
    unsigned size)
{
    int rc;

    assert(stack);
    assert(!TkTextUndoIsPerformingUndoRedo(stack));

    PushSeparator(stack, 1);
    stack->doingUndo = 1;
    rc = TkTextUndoPushItem(stack, item, size);
    stack->doingUndo = 0;

    return rc;
}


TkTextUndoItem
TkTextUndoSwapLastItem(
    TkTextUndoStack stack,
    TkTextUndoItem item,
    unsigned *size)
{
    TkTextUndoAtom *last;
    TkTextUndoSubAtom *subAtom;
    TkTextUndoItem oldItem;
    unsigned oldSize;

    assert(stack);
    assert(TkTextUndoGetLastUndoSubAtom(stack));

    last = stack->current ? &stack->current->data : &stack->last->data;
    subAtom = (TkTextUndoSubAtom *) last->array + (last->arraySize - 1);
    oldSize = subAtom->size;
    last->size -= oldSize;
    last->size += *size;
    stack->undoSize -= oldSize;
    stack->undoSize += *size;
    oldItem = subAtom->item;
    subAtom->item = item;
    subAtom->size = *size;
    *size = oldSize;

    return oldItem;
}


int
TkTextUndoDoUndo(
    TkTextUndoStack stack)
{
    int rc;

    assert(stack);

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    InsertCurrentAtom(stack);

    if (stack->undoDepth == 0) {
	rc = TCL_ERROR;
    } else {
	MyUndoAtom *atom;

	assert(stack->last);

	stack->actual = atom = stack->last;
	stack->doingUndo = 1;
	stack->undoDepth -= 1;
	stack->undoSize -= stack->actual->data.size;
	stack->undoItems -= stack->actual->data.arraySize;
	stack->undoProc(stack, &stack->actual->data);
	stack->last = stack->undoDepth ? stack->last->prev : NULL;
	stack->actual = NULL;

	if (!stack->current || stack->current->data.arraySize == 0) {
	    /*
	     * We didn't receive reverting items while performing an undo.
	     * So all redo items are expired, we have to delete them.
	     */
	    stack->redoDepth = 0;
	    stack->redoSize = 0;
	    stack->redoItems = 0;
	    Release(stack, stack->last ? stack->last->next : stack->root);
	} else {
	    FreeItems(stack, &atom->data);
	    InsertCurrentAtom(stack);
	}

	stack->doingUndo = 0;
	rc = TCL_OK;

	if (stack->contentChangedProc) {
	    stack->contentChangedProc(stack);
	}
    }
    return rc;
}


int
TkTextUndoDoRedo(
    TkTextUndoStack stack)
{
    int rc;

    assert(stack);

    if (stack->doingUndo || stack->doingRedo) {
	return TCL_ERROR;
    }

    InsertCurrentAtom(stack);

    if (stack->redoDepth == 0) {
	rc = TCL_ERROR;
    } else {
	MyUndoAtom *atom;

	stack->actual = atom = stack->last ? stack->last->next : stack->root;
	stack->doingRedo = 1;
	stack->redoDepth -= 1;
	stack->redoSize -= atom->data.size;
	stack->redoItems -= atom->data.arraySize;
	stack->undoProc(stack, &atom->data);
	stack->last = atom;
	stack->actual = NULL;

	if (!stack->current || stack->current->data.arraySize == 0) {
	    /*
	     * Oops, we did not receive reverting items while performing a redo.
	     * So we cannot apply the preceding undoes, we have to remove them.
	     * Now the content will become irreversible.
	     */
	    if (stack->undoDepth > 0) {
		stack->undoDepth = 0;
		stack->undoItems = 0;
		stack->undoSize = 0;
		atom = stack->root;
		stack->root = stack->last->next;
		stack->last = NULL;
	    } else {
		stack->root = atom->next;
	    }
	    Release(stack, atom);
	    stack->irreversible = 1;
	} else {
	    FreeItems(stack, &atom->data);
	    InsertCurrentAtom(stack);
	}

	stack->doingRedo = 0;
	rc = TCL_OK;

	if (stack->contentChangedProc) {
	    stack->contentChangedProc(stack);
	}
    }

    return rc;
}

int
TkTextUndoStackIsFull(
    const TkTextUndoStack stack)
{
    if (!stack) {
	return 1;
    }
    if (stack->doingUndo) {
	return stack->maxRedoDepth >= 0 && (int) stack->redoDepth >= stack->maxRedoDepth;
    }
    return stack->maxUndoDepth > 0 && stack->undoDepth >= stack->maxUndoDepth;
}


const TkTextUndoAtom *
TkTextUndoFirstUndoAtom(
    TkTextUndoStack stack)
{
    assert(stack);

    if (stack->current && stack->current->data.arraySize && !stack->doingUndo) {
	return &(stack->iter = stack->current)->data;
    }

    if (stack->undoDepth > 0 && stack->last != stack->actual) {
	return &(stack->iter = stack->last)->data;
    }

    stack->iter = NULL;
    return NULL;
}


const TkTextUndoAtom *
TkTextUndoNextUndoAtom(
    TkTextUndoStack stack)
{
    assert(stack);

    if (stack->iter) {
	if (stack->iter == stack->current) {
	    if (stack->undoDepth > 0 && stack->last != stack->actual) {
		return &(stack->iter = stack->last)->data;
	    }
	    stack->iter = NULL;
	} else if (stack->iter != stack->root && (stack->iter = stack->iter->prev) != stack->actual) {
	    return &stack->iter->data;
	} else {
	    stack->iter = NULL;
	}
    }

    return NULL;
}


const TkTextUndoAtom *
TkTextUndoFirstRedoAtom(
    TkTextUndoStack stack)
{
    assert(stack);

    if (stack->redoDepth > 0 && stack->root->prev != stack->actual) {
	return &(stack->iter = stack->root->prev)->data;
    }

    stack->iter = NULL;

    if (stack->current && stack->current->data.arraySize && stack->doingUndo) {
	return &stack->current->data;
    }

    return NULL;
}


const TkTextUndoAtom *
TkTextUndoNextRedoAtom(
    TkTextUndoStack stack)
{
    assert(stack);

    if (stack->iter) {
	if (stack->iter != stack->root
		&& (stack->iter = stack->iter->prev) != stack->last
		&& stack->iter != stack->actual) {
	    return &stack->iter->data;
	}

	stack->iter = NULL;

	if (stack->current && stack->current->data.arraySize && stack->doingUndo) {
	    return &stack->current->data;
	}
    }

    return NULL;
}


/* Additionally we need stand-alone object code. */
extern void TkTextUndoSetContext(TkTextUndoStack stack, TkTextUndoContext context);
extern TkTextUndoContext TkTextUndoGetContext(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetMaxUndoDepth(const TkTextUndoStack stack);
extern int TkTextUndoGetMaxRedoDepth(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetMaxSize(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetCurrentDepth(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetCurrentSize(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetCurrentUndoStackDepth(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetCurrentRedoStackDepth(const TkTextUndoStack stack);
extern unsigned TkTextUndoCountUndoItems(const TkTextUndoStack stack);
extern unsigned TkTextUndoCountRedoItems(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetCurrentUndoSize(const TkTextUndoStack stack);
extern unsigned TkTextUndoGetCurrentRedoSize(const TkTextUndoStack stack);
extern unsigned TkTextUndoCountCurrentUndoItems(const TkTextUndoStack stack);
extern unsigned TkTextUndoCountCurrentRedoItems(const TkTextUndoStack stack);
extern int TkTextUndoContentIsIrreversible(const TkTextUndoStack stack);
extern int TkTextUndoContentIsModified(const TkTextUndoStack stack);
extern int TkTextUndoIsPerformingUndo(const TkTextUndoStack stack);
extern int TkTextUndoIsPerformingRedo(const TkTextUndoStack stack);
extern int TkTextUndoIsPerformingUndoRedo(const TkTextUndoStack stack);
extern const TkTextUndoAtom *TkTextUndoCurrentUndoAtom(const TkTextUndoStack stack);
extern const TkTextUndoAtom *TkTextUndoCurrentRedoAtom(const TkTextUndoStack stack);
extern const TkTextUndoSubAtom *TkTextUndoGetLastUndoSubAtom(const TkTextUndoStack stack);
extern int TkTextUndoUndoStackIsFull(const TkTextUndoStack stack);
extern int TkTextUndoRedoStackIsFull(const TkTextUndoStack stack);

/* vi:set ts=8 sw=4: */
