/*
 * edge.c
 * Implements Edges and collections of Edges.
 *
 * Copyright (C) Leni Mayo 2001-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 <stdlib.h> /* for malloc and free                      */
#include "log.h"
#include "edge.h"

/*
 * Notes
 * Doubly-linked lists are used here, rather than singly-linked
 * lists so that remove() has worst-case complexity of O(1),
 * rather than O(n)
 *
 * pProvider and pNeeder serve a purpose analogous to the C++ STL multimap
 * class, namely they are containers optimised for lookup of multiple values
 * associated with the one key.
 */
static Edge * edgeNew(ServiceId provider, ServiceId needer);
static void   edgeDelete(Edge *pEdge);

void removeLists(gpointer key, gpointer value, gpointer pUserData);
void listAsString(gpointer key, gpointer value, gpointer pUserData);

Edges * edgesNew(CallbackServiceId pfn)
{
	Edges *pEdges;

	pEdges = malloc(sizeof(Edges));

	pEdges->pList        = NULL;
	pEdges->callback     = pfn;
	pEdges->pProvidersTo = g_hash_table_new(NULL, NULL);
	pEdges->pNeedersOf   = g_hash_table_new(NULL, NULL);

	return pEdges;
}

void edgesDelete(Edges *pEdges)
{
	if (pEdges)
	{
		GList *pElement;
		Edge *pEdge;

		for (pElement = pEdges->pList; pElement != NULL; pElement = pElement->next)
		{
			pEdge = pElement->data;

			edgeDelete(pEdge);
		}

		g_list_free(pEdges->pList);

		g_hash_table_foreach(pEdges->pProvidersTo, removeLists, NULL);
		g_hash_table_foreach(pEdges->pNeedersOf,   removeLists, NULL);

		g_hash_table_destroy(pEdges->pProvidersTo);
		g_hash_table_destroy(pEdges->pNeedersOf);

		free(pEdges);
	}
}

Edge * edgesAdd(Edges *pEdges, ServiceId provider, ServiceId needer)
{
	Edge *pEdge;
	GList *pList;

	assert(provider != 0);
	assert(needer != 0);

	pEdge = edgeNew(provider, needer);

	/* add to pList
	*/
	pEdges->pList = g_list_prepend(pEdges->pList, pEdge);

	/* add to pNeeders
	*/
	pList = (GList *) g_hash_table_lookup(pEdges->pNeedersOf,
	                                      pEdge->id[eEdgeProvider]);

	{
		GList *pElement;

		/*
		 * assert that this edge isn't already there,
		 * leni TODO: remove this test after software stabilises...
		 */
		for (pElement = pList; pElement != NULL; pElement = pElement->next)
		{
			assert(pEdge != pElement->data);
		}
	}

	pList = g_list_prepend(pList, pEdge);
	g_hash_table_insert(pEdges->pNeedersOf, pEdge->id[eEdgeProvider], pList);

	/* add to pProviders
	*/
	pList = (GList *) g_hash_table_lookup(pEdges->pProvidersTo,
	                                      pEdge->id[eEdgeNeeder]);
	{
		GList *pElement;

		/*
		 * assert that this edge isn't already there,
		 * leni TODO: remove this test after software stabilises...
		 */
		for (pElement = pList; pElement != NULL; pElement = pElement->next)
		{
			assert(pEdge != pElement->data);
		}
	}

	pList = g_list_prepend(pList, pEdge);
	g_hash_table_insert(pEdges->pProvidersTo, pEdge->id[eEdgeNeeder], pList);

	return pEdge;
}

/*
 * edgesRemoveNode()
 * Description
 * Removes all edges that originate or terminate on this Node.
 * When the last Edge originating from a Node is deleted,
 * (ie edgesNeedersOf() returns NULL), a callback function
 * is executed and the hash key used by edgesNeedersOf() is deleted.
 */

void edgesRemoveNode(Edges *pEdges, ServiceId id)
{
	GList *pListProvidersTo, *pListNeedersOf;
	GList *pElement;
	Edge  *pEdge;

	pListProvidersTo = (GList *) g_hash_table_lookup(pEdges->pProvidersTo, id);

	/*
	 * Clean up pNeedersOf by removing a list element from each 
	 * of the providers.  
	 */

	for (pElement = pListProvidersTo; pElement != NULL; pElement = pElement->next)
	{
		pEdge = (Edge *) pElement->data;

		pEdges->pList = g_list_remove(pEdges->pList, pEdge);

		pListNeedersOf =  g_hash_table_lookup(pEdges->pNeedersOf,
		                                      pEdge->id[eEdgeProvider]);

		pListNeedersOf = g_list_remove(pListNeedersOf, pEdge);

		if (pListNeedersOf == NULL)
		{
			CallbackReturnCode code = eReturnSuccess;
			g_hash_table_remove(pEdges->pNeedersOf, pEdge->id[eEdgeProvider]);

			pEdges->callback(pEdge->id[eEdgeProvider], &code);
		}
		else
		{
			g_hash_table_insert(pEdges->pNeedersOf,
			                    pEdge->id[eEdgeProvider], pListNeedersOf);
		}

		edgeDelete(pEdge);
	}

	/*
	 * Clean up pProvidersTo by removing the key+value from the hash
	 * and freeing the list
	 */
	g_hash_table_remove(pEdges->pProvidersTo, id);
	g_list_free(pListProvidersTo);
}

GList * edgesGet(Edges *pEdges)
{
	assert(pEdges);

	return pEdges->pList;
}

GList * edgesNeedersOf(Edges *pEdges, ServiceId id)
{
	return (GList *) g_hash_table_lookup(pEdges->pNeedersOf, id);
}

GList * edgesProvidersTo(Edges *pEdges, ServiceId id)
{
	return (GList *) g_hash_table_lookup(pEdges->pProvidersTo, id);
}

Edge * edgeNew(ServiceId provider, ServiceId needer)
{
	Edge *pEdge;

	assert(provider != 0);
	assert(needer != 0);
	
	pEdge = malloc(sizeof(Edge)); /* leni TODO: test return value */


	pEdge->id[eEdgeProvider] = provider;
	pEdge->id[eEdgeNeeder]   = needer;
	pEdge->pUserData         = NULL;

	return pEdge;
}

void edgeDelete(Edge *pEdge)
{
	if (pEdge != NULL)
	{
		if (pEdge->pUserData != NULL)
		{
			free(pEdge->pUserData);
		}

		free(pEdge);
	}
}

void removeLists(gpointer key, gpointer value, gpointer pUserData)
{
	g_list_free((GList *) value);
}

void listAsString(gpointer key, gpointer value, gpointer pUserData)
{
	SafeBuf *pX = (SafeBuf *) pUserData;
	GList *pElement;
	Edge *pEdge;

	sprintfTo(pX, "    %s: (", serviceIdAsString((ServiceId) key));

	for (pElement = (GList *) value; pElement != NULL; pElement = pElement->next)
	{
		pEdge = pElement->data;

		if (pElement != (GList *) value)
		{
			sprintfTo(pX, ", ");
		}

		sprintfTo(pX, "%p", pEdge);
	}

	sprintfTo(pX, ")\n");
}

const char * edgeAsString(Edge *pEdge, char *buf, int bufsiz)
{
	SafeBuf x;

	x.buf    = buf;
	x.bufsiz = bufsiz;

	sprintfTo(&x, "%p: (%s, %s, userdata: %s)\n", pEdge, 
		serviceIdAsString(pEdge->id[eEdgeProvider]),
		serviceIdAsString(pEdge->id[eEdgeNeeder]),
		pEdge->pUserData == NULL ? "no" : "yes");

	return buf;
}

const char * edgesAsString(Edges *pEdges, char *buf, int bufsiz)
{
	SafeBuf x;
	char lbuf[1000];

	x.buf    = buf;
	x.bufsiz = bufsiz;

	if (pEdges)
	{
		GList *pElement;
		Edge *pEdge;

		for (pElement = pEdges->pList; pElement != NULL; pElement = pElement->next)
		{
			pEdge = pElement->data;

			sprintfTo(&x, "    %s", edgeAsString(pEdge, lbuf, sizeof(lbuf)));
		}

		sprintfTo(&x, "  ProvidersTo:\n");
		g_hash_table_foreach(pEdges->pProvidersTo, listAsString, &x);

		sprintfTo(&x, "  NeedersOf:\n");
		g_hash_table_foreach(pEdges->pNeedersOf,   listAsString, &x);
	}

	return buf;
}
