/*****
* history.c : generic history routines
*
* This file Version	$Revision$
*
* Creation date:		Sun Aug 30 17:57:36 CEST 1998
* Last modification: 	$Date$
* By:					$Author$
* Current State:		$State$
*
* Author:				XmHTML Developers Account
*
* Copyright (C) 1994-1998 by Ripley Software Development 
* All Rights Reserved
*
* This file is part of the XmHTML Widget Library
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*****/
/*****
* ChangeLog 
* $Log$
*****/ 
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <X11/Intrinsic.h>	/* pull in for typedefs */

#include "history.h"

#ifdef DEBUG
static Boolean debug = False;
#define Debug(MSG) do { \
	if(debug) { printf MSG ; fflush(stdout); } }while(0)
#else
#define Debug(MSG)	/* emtpy */
#endif

/*** External Function Prototype Declarations ***/

/*** Public Variable Declarations ***/

/*** Private Datatype Declarations ****/

typedef struct hnode{
	String file;		/* file to which this history node belongs	*/
	int line_no;		/* referenced line number					*/
	String location;	/* referenced hyperlink, NULL = unused		*/
	struct hnode *next;	/* next history item						*/
	struct hnode *prev;	/* previous history item					*/
}HistoryNode;

typedef struct lnode{
	Widget owner;			/* owner of this history data	*/
	HistoryNode *head;		/* head of history list			*/
	HistoryNode *tail;		/* last inserted history item	*/
	HistoryNode *current;	/* current history item			*/
	struct lnode *next;		/* next history list			*/
	struct lnode *prev;		/* previous history list		*/
}HistoryList;

typedef struct{
	HistoryList *head;		/* head of history data			*/
	HistoryList *tail;		/* last inserted history		*/
}HistoryAdmin;

/*** Private Function Prototype Declarations ****/
static HistoryList *historyFind(Widget w);

static HistoryList *historyRegister(Widget w, String file, int line_no,
	String location);

static void historyDeleteNodes(HistoryNode *node);

static void historyUnregister(Widget w);

static HistoryData *historyReturnNode(HistoryNode *node);

/*** Private Variable Declarations ***/
static HistoryAdmin root;
static Boolean initialized = 0;

/**********
***** Private Routines
**********/

/*****
* Name: 		historyFind
* Return Type: 	HistoryList*
* Description: 	locate the history list for the given widget.
* In: 
*	w:			widget for which to locate the history list (if any)
* Returns:
*	A pointer to the historyList for the given widget, or NULL.
*****/
static HistoryList*
historyFind(Widget w)
{
	HistoryList *hlist = NULL;

	for(hlist = root.head; hlist != NULL && hlist->owner != w;
		hlist = hlist->next);

	return(hlist);
}

/*****
* Name: 		historyRegister
* Return Type: 	HistoryList*
* Description:	initialize & register a new history list for the given
*				widget. The given data is the default history item
*				for the given widget.
* In: 
*	w:			widget to register;
*	file:		filename of default history item;
*	line_no:	line number of default history item;
*	location:	location of default history item;
* Returns:
*	A newly allocated history list, or NULL.
*****/
static HistoryList* 
historyRegister(Widget w, String file, int line_no, String location)
{
	HistoryList *hlist = NULL;
	HistoryNode *node  = NULL;

	if((hlist = (HistoryList*)malloc(sizeof(HistoryList))) == NULL)
		return(NULL);

	if((node = (HistoryNode*)malloc(sizeof(HistoryNode))) == NULL)
	{
		free(hlist);
		return(NULL);
	}

	/* copy history data into the history node */
	node->file = strdup(file);
	node->line_no = line_no;
	if(location)
		node->location = strdup(location);
	else
		node->location = NULL;

	/*****
	* This is the first history node, so we haven't got a previous or
	* next node.
	*****/
	node->next = NULL;
	node->prev = NULL;

	/* Initialize the HistoryList for this widget */
	hlist->owner   = w;
	hlist->head    = node;
	hlist->current = node;
	hlist->tail    = node;
	hlist->next    = NULL;		/* no next historylist	*/
	hlist->prev    = NULL;		/* initialize anyway	*/

	/*****
	* If we are already managing historyList's for a widget,
	* append current HistoryList to the list of already managed
	* historyLists's.
	*****/
	if(root.head)
	{
		hlist->prev = root.tail;	/* set previous HistoryList */
		/*****
		* Next HistoryList for the current HistoryList is the newly allocated
		* HistoryList.
		*****/
		root.tail->next = hlist;
	}
	else
	{
		root.head = hlist;	/* very first HistoryList being managed. */
	}

	root.tail = hlist;		/* last inserted HistoryList	*/

	/* all done */
	return(hlist);
}

/*****
* Name: 		historyDeleteNodes
* Return Type: 	void
* Description: 	deletes all given history nodes
* In: 
*	node:		first history node to be deleted.
* Returns:
*	nothing.
*****/
static void
historyDeleteNodes(HistoryNode *node)
{
	HistoryNode *tmp = node;

	/* delete all given nodes */
	while(tmp != NULL)
	{
		node = node->next;
		free(tmp->file);
		if(tmp->location)
			free(tmp->location);
		free(tmp);
		tmp = node;
	}
}

/*****
* Name:			historyUnregister
* Return Type: 	void
* Description: 	erase the history list being managed for the given widget.
* In: 
*	w:			widget for which to delete the history list.
* Returns:
*	nothing.
*****/
static void
historyUnregister(Widget w)
{
	HistoryList *hlist;

	/* get the list associated with the given widget */
	if((hlist = historyFind(w)) == NULL)
		return;

	/* disconnect this historylist from it's pre- and successors */

	/* Check if this list is the head of the managed history lists */
	if(hlist == root.head)
	{
		/* next item becomes the new head of the list */
		root.head = hlist->next;

		/* invalidate previous reference (if any) */
		if(root.head)
			root.head->prev = NULL;
		else
			root.tail = NULL;		/* this was the last history list	*/
	}
	else if(hlist == root.tail)
	{
		/* It's the last of the managed history lists */
		root.tail = hlist->prev;	/* this item becomes the new tail	*/
		root.tail->next = NULL;		/* which has no next list to it		*/
	}
	else
	{
		/* nor head or tail, reconnect previous and next history list	*/
		hlist->prev->next = hlist->next;
		hlist->next->prev = hlist->prev;
	}

	/* delete all nodes for this list */
	historyDeleteNodes(hlist->head);

	/* and delete the list */
	free(hlist);
}

/*****
* Name: 
* Return Type: 
* Description: 
* In: 
*
* Returns:
*
*****/
static HistoryData*
historyReturnNode(HistoryNode *node)
{
	if(node)
	{
		static HistoryData hdata;
		hdata.file     = node->file;
		hdata.line_no  = node->line_no;
		hdata.location = node->location;
		return(&hdata);
	}
	return(NULL);
}

/**********
* Public Routines
**********/

/*****
* Name: 		HistoryInitialize
* Return Type: 	void
* Description: 	initializes the root node of the history mechanism.
* In: 
*	nothing.
* Returns:
*	nothing.
*****/
void
HistoryInitialize(int level)
{
	root.head = NULL;
	root.tail = NULL;
	initialized  = True;	

#ifdef DEBUG
	debug = level;
#endif
}

/*****
* Name: 		HistoryAdd
* Return Type: 	Boolean
* Description: 	store data in the history list of the given widget.
* In: 
*	w:			widget for which to update the history;
*	file:		filename to be stored;
*	line_no:	line number to be stored;
*	location:	location of to be stored;
* Returns:
*	True on success, False on failure.
*****/
Boolean
HistoryAdd(Widget w, String file, int line_no, String location)
{
	HistoryList *hlist;
	HistoryNode *node;

	Debug(("history.c: HistoryAdd, file = %s, location = %s, line = %i\n",
		file, location ? location : "(null)", line_no));

	/* sanity */
	if(w == NULL || file == NULL)
		return(False);

	if(!initialized)
		HistoryInitialize(1);

	if((hlist = historyFind(w)) == NULL)
	{
		if((hlist = historyRegister(w, file, line_no, location)) == NULL)
			return(False);
		return(True);
	}

	/*****
	* First verify if the current node isn't equal to the data that is
	* to be stored.
	*****/
	if(hlist->current)
	{
		node = hlist->current;
		if(!strcmp(node->file, file) &&
			node->line_no == line_no &&
			((location && node->location && !strcmp(node->location, location))||
			(!location && !node->location)))
				return(False);
	}

	if((node = (HistoryNode*)malloc(sizeof(HistoryNode))) == NULL)
		return(False);

	node->file = strdup(file);
	node->line_no = line_no;
	if(location)
		node->location = strdup(location);
	else
		node->location = NULL;

	/*****
	* When we get here, we know we already have an item in the list
	* for this widget (HistoryRegister does that for us), so
	* we just append the item and don't have to check the validity
	* of the history head.
	*****/
	node->prev = hlist->current;
	node->next = NULL;

	/* delete all items after the current item, they have become invalid */
	historyDeleteNodes(hlist->current->next);

	/* this is the new current item */
	hlist->current->next = node;
	hlist->current = hlist->current->next;

	/* and it is also the last element in the list */
	hlist->tail    = hlist->current;

	/* all done */
	return(True);
}

/*****
* Name: 		HistoryRemove
* Return Type: 	void
* Description: 	remove all history data associated with the given widget.
* In: 
*	w:			widget for which to destroy all history data.
* Returns:
*	nothing.
*****/
void
HistoryRemove(Widget w)
{
	/* sanity */
	if(w == NULL)
		return;

	/* remove the history data for the given widget */
	historyUnregister(w);
}

/*****
* Name: 		HistoryForward
* Return Type: 	HistoryData*
* Description: 	move the history one step forward and return the associated
*				data.
* In: 
*	w:			widget for which to return history data.
* Returns:
*	History data, or NULL
*****/
HistoryData*
HistoryForward(Widget w)
{
	HistoryList *hlist;

	/* sanity */
	if(w == NULL || !initialized)
		return(NULL);

	/* get history list for this widget */
	if((hlist = historyFind(w)) == NULL)
		return(NULL);

	/* move to the next history item in the list */
	if(hlist->current && hlist->current->next)
	{
		Debug(("history.c: HistoryForward, current: file = %s, location = %s, "
			"line = %i\n", hlist->current->file,
			hlist->current->location ? hlist->current->location : "(null)",
			hlist->current->line_no));

		hlist->current = hlist->current->next;

		Debug(("history.c: HistoryForward, next: file = %s, location = %s, "
			"line = %i\n", hlist->current->file,
			hlist->current->location ? hlist->current->location : "(null)",
			hlist->current->line_no));

		return(historyReturnNode(hlist->current));
	}

	/* no items or last item */
	return(NULL);
}

/*****
* Name: 		HistoryCurrent
* Return Type: 	HistoryData*
* Description: 	return the data associated with the current history item.
* In: 
*	w:			widget for which to return history data.
* Returns:
*	History data, or NULL
*****/
HistoryData*
HistoryCurrent(Widget w)
{
	HistoryList *hlist;

	/* sanity */
	if(w == NULL || !initialized)
		return(NULL);

	/* get history list for this widget */
	if((hlist = historyFind(w)) == NULL)
		return(NULL);

	return(historyReturnNode(hlist->current));
}

/*****
* Name: 		HistoryBack
* Return Type: 	HistoryData*
* Description: 	move the history one step back and return the associated
*				data.
* In: 
*	w:			widget for which to return history data.
* Returns:
*	History data, or NULL
*****/
HistoryData*
HistoryBack(Widget w)
{
	HistoryList *hlist;

	/* sanity */
	if(w == NULL || !initialized)
		return(NULL);

	/* get history list for this widget */
	if((hlist = historyFind(w)) == NULL)
		return(NULL);

	/* move to the previous history item in the list */
	if(hlist->current && hlist->current->prev)
	{
		Debug(("history.c: HistoryBack, current: file = %s, location = %s, "
			"line = %i\n", hlist->current->file,
			hlist->current->location ? hlist->current->location : "(null)",
			hlist->current->line_no));

		hlist->current = hlist->current->prev;

		Debug(("history.c: HistoryBack, previous: file = %s, location = %s, "
			"line = %i\n", hlist->current->file,
			hlist->current->location ? hlist->current->location : "(null)",
			hlist->current->line_no));

		return(historyReturnNode(hlist->current));
	}

	/* no items or first item */
	return(NULL);
}

/*****
* Name:			HistoryRemove
* Return Type: 	Boolean
* Description: 	remove all references to the given file.
* In: 
*	w:			widget for which to update the history;
*	file:		file for which to delete all history references;
* Returns:
*	True on success, False on failure.
*****/
Boolean
HistoryRemoveRefs(Widget w, String file)
{
	HistoryList *hlist;
	HistoryNode *node, *tmp;

	/* sanity */
	if(w == NULL || file == NULL)
		return(False);

	/* get history list for this widget */
	if((hlist = historyFind(w)) == NULL)
		return(False);

	/* Delete all nodes that reference the given file */
	node = hlist->head;
	do
	{
		/* store reference to next item */
		tmp = node->next;

		/* check */
		if(!(strcmp(node->file, file)))
		{
			/* free allocated memory */
			free(node->file);
			if(node->location)
				free(node->location);

			/* reconnect previous and next nodes */
			if(node == hlist->current)
			{
				/*****
				* Item to be deleted is the current item. The most logical
				* thing to do is to move the history forward.
				*****/
				if(node->next)
					hlist->current = node->next;
				else
					hlist->current = node->prev;	/* fallback */
			}

			/* this is the head of the stored history data */
			if(node == hlist->head)
			{
				/* move forward */
				hlist->head = node->next;
				if(hlist->head)
					hlist->head->prev = NULL;
				else
					hlist->tail = NULL;			/* this was the last item! */
			}
			else if(node == hlist->tail)
			{
				/* It's the last of the managed items */
				hlist->tail = node->prev;
				hlist->tail->next = NULL;	/* which has no next item	*/
			}
			else
			{
				/* nor head or tail, reconnect previous and next items */
				node->prev->next = node->next;
				node->next->prev = node->prev;
			}
		}
		node = tmp;
	}while(node != NULL);

	/* all done */
	return(True);
}

/*****
* Name:			HistoryVerifyBack
* Return Type: 	Boolean
* Description: 	verify if a backward move in the history yields a valid
*				result.
* In: 
*	w:			widget for which to check the history data.
* Returns:
*	True if a backward move is allowed, False if not.
*****/
Boolean
HistoryVerifyBack(Widget w)
{
	HistoryList *hlist;

	/* sanity */
	if(w == NULL || !initialized)
		return(False);

	/* get history list for this widget */
	if((hlist = historyFind(w)) == NULL)
		return(False);

	/* Return True if a previous item exists */
	return(hlist->current && hlist->current->prev);
}

/*****
* Name:			HistoryVerifyForward
* Return Type: 	Boolean
* Description: 	verify if a forward move in the history yields a valid
*				result.
* In: 
*	w:			widget for which to check the history data.
* Returns:
*	True if a forward move is allowed, False if not.
*****/
Boolean
HistoryVerifyForward(Widget w)
{
	HistoryList *hlist;

	/* sanity */
	if(w == NULL || !initialized)
		return(False);

	/* get history list for this widget */
	if((hlist = historyFind(w)) == NULL)
		return(False);

	/* Return True if a next item exists */
	return(hlist->current && hlist->current->next);
}
