/*
 * traverse.c
 * Routines for traversing RDF graph elements.
 *
 * Copyright (C) Leni Mayo 2002
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, version 2, as
 *	published by the Free Software Foundation.
 *
 *	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.
 */

#include <stdio.h>
#include <stdlib.h> /* for malloc/free */
#include <ctype.h>
#include <string.h>
#include <redland.h>

#include "rdfcommon.h"
#include "rdfconsturi.h"
#include "traverse.h"

static int isNodeInList(GSList *pList, librdf_node *pNode);

/*
 * GSList * test_serel_graph_is_acyclic(librdf_model *pModel)
 *
 * Returns: 
 * A list contains the uri strings representing the dft tree
 * that was encountered the cycle.
 * The caller is responsible for freeing the list items and the list itself.
 */

void depth_first_traverse_start(librdf_model *pModel,
	GSList *pRootNodes,
	librdf_node *pGraphFrom,
	librdf_node *pGraphTo,
	DFT_CALLBACK fn_pre,
	DFT_CALLBACK fn_post,
	DftUserData *pDftUserData
)
{
	GSList *pElement;
	int fContinue = TRUE;

	pDftUserData->pStack = NULL;

	/*
	 * Do a depth-first-traversal of the root nodes, marking each
	 * node by addng it to the list passed to the callback function
	 */

	for (pElement = pRootNodes; pElement != NULL && fContinue; pElement = pElement->next)
	{
		assert(pElement->data);

#ifdef TAKENOUT
		printf("Calling depth_first_traverse(%s)\n",
			node_as_uri_string((librdf_node *) pElement->data));
#endif

		fContinue = depth_first_traverse(pModel, pGraphFrom, pGraphTo,
		              (librdf_node *) pElement->data,
			          fn_pre, fn_post, pDftUserData);

	}
}

int depth_first_traverse(librdf_model *pModel,
	librdf_node *pGraphFrom,
	librdf_node *pGraphTo,
	librdf_node *pNode,
	DFT_CALLBACK fn_pre,
	DFT_CALLBACK fn_post,
	DftUserData *pUserData)
{
	librdf_iterator *iterator;
	librdf_node *subject;
	librdf_node *pNext;
	int fContinue;

	assert(pNode);

#ifdef TAKENOUT
	printf("depth_first_traverse(%s), path to this node is: ", node_as_uri_string(pNode));
	print_nodes_in_list(stdout, pUserData->pStack);
	printf("\n");
#endif

	fContinue = fn_pre(pModel, pNode, pUserData);

	if (fContinue)
	{
		iterator = librdf_model_get_sources(pModel, pGraphFrom, pNode);

#ifdef TAKENOUT
		{
			GSList *pList = listFromNodeIterator(iterator);
			printf("and iterating across: ");
			print_nodes_in_list(stdout, pList);
			printf("\n");
			g_slist_free(pList);
		}

		/* reset the iterator after using it...
		*/
		iterator = librdf_model_get_sources(pModel, pGraphFrom, pNode);
#endif

		while ( fContinue && !librdf_iterator_end(iterator))
		{
			subject = (librdf_node *) librdf_iterator_get_next(iterator);

			assert(subject != NULL);

			if (! nodeHasPropertyName(pModel, subject, eSerelMarkDeleted))
			{
				pNext = librdf_model_get_target(pModel, subject, pGraphTo);

				fContinue = depth_first_traverse(pModel, pGraphFrom, pGraphTo, pNext, fn_pre, fn_post, pUserData);
			}

			librdf_free_node(subject);
		}

		librdf_free_iterator(iterator); 
	}

	if (fContinue)
	{
		fContinue = fn_post(pModel, pNode, pUserData);
	}

	return fContinue;
}

/*
 * int stack_push_callback_pre(librdf_model *pModel, librdf_node *pNode, void *pUserData)
 *
 * Returns: TRUE if the traversal should continue, FALSE otherwise.
 */

int stack_push_callback_pre(librdf_model *pModel, librdf_node *pNode, DftUserData *pUserData)
{
	const char *pszUri;
	int found;

	assert(pModel && pNode && pUserData);

	pszUri = node_as_uri_string(pNode);

	found = isNodeInList(pUserData->pStack, pNode);

	pUserData->pStack = g_slist_prepend(pUserData->pStack, pNode);

#ifdef TAKENOUT
	if (found)
	{
		printf("Cycle found during visit to node: %s\n", pszUri);
	}
#endif

	return !found;
}

/*
 * int stack_pop_callback_post(librdf_model *pModel, librdf_node *pNode, void *pUserData)
 *
 * Removes the first element in the list, which is the most recently visited
 * node in the depth first traversal.
 * 
 * Returns: TRUE - the traversal continues.
 */

int stack_pop_callback_post(librdf_model *pModel, librdf_node *pNode, DftUserData *pUserData)
{
	GSList *pListHead = pUserData->pStack;

	assert(pModel && pNode && pUserData && pUserData->pStack);

#ifdef TAKENOUT
	printf("stack_pop_callback_post(%s), cycle list is: ", local_part_of_uri(node_as_uri_string(pNode)));
	print_nodes_in_list(stdout, pUserData->pStack);
#endif

	pUserData->pStack = g_slist_remove_link(pUserData->pStack, pListHead);

	assert(isNodeInList(pListHead, pNode));

	g_slist_free_1(pListHead);

	return TRUE;
}

/*
 * int acyclic_callback_pre(librdf_model *pModel, librdf_node *pNode, void *pUserData)
 *	Marks the node with a "eSerelVisited" property
 *	The caller then checks to make sure that all nodes have been visited.
 *	If, after starting a traversal from root nodes, a node hasn't been visited,
 *	it must be because of the presence of self-contained cycle of nodes,
 *	ie. no root or leaf.
 *
 * Returns: stack_push_callback_pre()
 */

int acyclic_callback_pre(librdf_model *pModel, librdf_node *pNode, DftUserData *pUserData)
{
	int ret;

	assert(pModel && pNode && pUserData);

	ret = stack_push_callback_pre(pModel, pNode, pUserData);

	if (ret)
	{
		addPropertyToNode(pModel, pNode, eSerelVisited, "true");
	}

	return ret;
}

int isNodeInList(GSList *pList, librdf_node *pNode)
{
	GSList *pElement;
	int found = FALSE;

	for (pElement = pList; pElement != NULL && !found; pElement = pElement->next)
	{
		assert(pElement->data);

		found = librdf_node_equals((librdf_node *) pElement->data, pNode) != 0;
	}

#ifdef TAKENOUT
	if (found)
	{
		printf("isNodeInList(%s) finds a match, list is: ",  node_as_uri_string(pNode));

		print_nodes_in_list(stdout, pList);

		printf("\n");
	}
#endif

	return found;
}

GSList * nodes_from_rdf_container(librdf_model *pModel, librdf_node *pContainer, librdf_node *pGraphTo)
{
	librdf_node *pNode;
	GSList *pListReturn = NULL;
	int count = 0;

	while (TRUE)
	{
		count++;

		pNode = nodeAtContainerItem(pModel, pContainer, count);

		if (pNode != NULL)
		{
			if (!librdf_model_has_arc_in(pModel, pNode, pGraphTo))
			{
				pListReturn = g_slist_append(pListReturn, pNode);

				/* printf("nodes_from_rdf_container() adds a node: %s\n", node_as_uri_string(pNode)); */
			}
		}
		else
		{
			count--;
			break;
		}
	}

	if (count > 0 && pListReturn == NULL)
	{
		librdf_node *pNode;

		/*
		 * If there are nodes but no root nodes, it means that
		 * there is a cycle that involves every node in the graph.
		 * So it doesn't matter which one we begin with...
		 */

		pNode = nodeAtContainerItem(pModel, pContainer, 1);

		if (pNode != NULL)
		{
			/*
			 * it'd only be null if there was an out-of-memory error
			 * Oh, to dream of C++ exceptions ... not for the last time...
			 * leni - Thu Mar 21 16:38:24 EST 2002
			 */
			pListReturn = g_slist_prepend(pListReturn, pNode);
		}
	}

	return pListReturn;
}

/*
 *	Examples:
 *	Get all service statements:
 *	listOfNode(pModel, nodeFromCode(eGraphService), NULL)
 *
 *	Get all Root node statements:
 *	listOfNode(pModel, nodeFromCode(eGraphNode), nodeFromCode(eGraphTo))
 *	
 */
GSList * listOfReferencedNode(librdf_model *pModel, librdf_node *pType, librdf_node *pGraphTo)
{
	librdf_node *pNode, *pNodeToAdd;
	librdf_iterator *iterator;
	GSList *pListReturn = NULL;
	int count = 0;

	iterator = librdf_model_get_sources(pModel, nodeFromCode(eRdfType), pType);

	while (!librdf_iterator_end(iterator))
	{
		pNode = (librdf_node *) librdf_iterator_get_next(iterator);

		assert(pNode != NULL);

		count++;

		pNodeToAdd = NULL;

		if (pGraphTo)
		{
			if (!librdf_model_has_arc_in(pModel, pNode, pGraphTo))
			{
				pNodeToAdd = pNode;
			}
		}
		else
		{
			pNodeToAdd = pNode;
		}

		if (pNodeToAdd)
		{
			pListReturn = g_slist_append(pListReturn, pNodeToAdd);
		}
	}

	librdf_free_iterator(iterator); 

	if (count > 0 && pListReturn == NULL)
	{
		librdf_node *pNode;

		/*
		 * If there are nodes but no root nodes, it means that
		 * there is a cycle that involves every node in the graph.
		 * So it doesn't matter which one we begin with...
		 */

		iterator = librdf_model_get_sources(pModel, nodeFromCode(eRdfType), pType);

		if (!librdf_iterator_end(iterator))
		{
			pNode = (librdf_node *) librdf_iterator_get_next(iterator);

			if (pNode != NULL)
			{
				pListReturn = g_slist_prepend(pListReturn, pNode);
			}
		}

		librdf_free_iterator(iterator); 
	}

	return pListReturn;
}

void testAcyclic(librdf_model *pModel, GSList **ppCycle, librdf_node *pGraphNode)
{
	GSList *pRootNodes = listOfReferencedNode(pModel, pGraphNode, nodeFromCode(eGraphTo));
	GSList *pLeafNodes = listOfReferencedNode(pModel, pGraphNode, nodeFromCode(eGraphFrom));
	GSList *pAllNodes  = listOfReferencedNode(pModel, pGraphNode, NULL);
	GSList *pElement;
	DftUserData userData;

	userData.pData = NULL; /* not used */

#ifdef TAKENOUT
	printf("In testAcyclic():\n");
	printf("pRootNodes = ");
	print_nodes_in_list(stdout, pRootNodes);
	printf("\n");
	printf("pLeafNodes = ");
	print_nodes_in_list(stdout, pLeafNodes);
	printf("\n");
#endif

	depth_first_traverse_start(pModel, pRootNodes, nodeFromCode(eGraphFrom), nodeFromCode(eGraphTo), acyclic_callback_pre, stack_pop_callback_post, &userData);

	*ppCycle = userData.pStack;

	/* Check to make sure that all nodes have been visited
	*/
	if (*ppCycle == NULL)
	{
		for (pElement = pAllNodes; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			if (!nodeHasPropertyName(pModel, pElement->data, eSerelVisited))
			{
				*ppCycle = g_slist_append(*ppCycle, pElement->data);
			}
		}
	}

	/* Remove the Visited property
	*/
	for (pElement = pAllNodes; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);

		removeStatement(pModel, pElement->data, nodeFromCode(eSerelVisited), NULL);
	}

	g_slist_free(pRootNodes);
	g_slist_free(pLeafNodes);
	g_slist_free(pAllNodes);
}


