/*
 * pidmap.c
 *
 * 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  */
#include <glib.h>
#include "log.h"
#include "pidmap.h"

typedef struct
{
	Node *pNode;
	int  fTicking;                /* boolean indicating whether the clock is ticking */
	struct timeval tv_laststart;  /* time last started */
	struct timeval tv_cumulative; /* time last started */
} PidmapValue;

static void asStringCallback(gpointer key, gpointer value, gpointer pUserData);
static long timeFromPidmapValue(const PidmapValue *pPidmapValue);

PidMap * pidmapNew()
{
	GHashTable *pTable;

	pTable = g_hash_table_new(NULL, NULL);

	assert(sizeof(Pid) <= sizeof(gpointer));

	return pTable;
}

void pidmapDelete(PidMap *pTable)
{
	if (pTable)
	{
		g_hash_table_foreach(pTable, callbackFreeValue, NULL);

		g_hash_table_destroy(pTable);
	}
}

int pidmapAdd(PidMap *pTable, Pid pid, Node *pNode)
{
	PidmapValue *pValue;

	assert(pTable);
	assert(pid != 0);
	assert(pNode != NULL);

	/*
	 * leni TODO: the lookup and assert() are just here for sanity testing
	 * and can be removed once the software stablises.
	 */
	pValue = (PidmapValue *) g_hash_table_lookup(pTable, (gpointer) pid);

	assert(pValue == NULL);

	pValue = malloc(sizeof(PidmapValue));

	if (pValue != NULL)
	{
		pValue->pNode = pNode;
		pValue->fTicking = FALSE;
		pValue->tv_cumulative.tv_sec = 0;
		pValue->tv_cumulative.tv_usec = 0;

		g_hash_table_insert(pTable, (gpointer) pid, pValue);
	}

	return pValue != NULL;
}

void pidmapRemove(PidMap *pTable, Pid pid)
{
	PidmapValue *pValue;

	assert(pTable);

	/*
	 * leni TODO: the lookup and assert() are just here for sanity testing
	 * and can be removed once the software stablises.
	 */
	pValue = (PidmapValue *) g_hash_table_lookup(pTable, (gpointer) pid);

	assert(pValue != NULL);

	free(pValue);

	g_hash_table_remove(pTable, (gpointer) pid);
}

/*
 * pidmapToNode(PidMap *pTable, Pid pid)
 *
 * Return:
 *	The Node, or NULL if the pid isn't present
 */
Node * pidmapToNode(PidMap *pTable, Pid pid)
{
	PidmapValue *pValue;

	assert(pTable);

	pValue = (PidmapValue *) g_hash_table_lookup(pTable, (gpointer) pid);

	return pValue == NULL ? NULL : pValue->pNode;
}

const char * pidmapAsString(PidMap *pTable, char *buf, int bufsiz)
{
	SafeBuf x;

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

	g_hash_table_foreach(pTable, asStringCallback, &x);
	
	return buf;
}

void asStringCallback(gpointer key, gpointer value, gpointer pUserData)
{
	PidmapValue *pValue = (PidmapValue *) value;
	Node *pNode;
	SafeBuf *pX = (SafeBuf *) pUserData;

	assert(pValue != NULL);

	pNode = pValue->pNode;

	sprintfTo(pX, "    %d : %s (stopwatch says %4.2f seconds, %s)\n",
	  (Pid) key,
	  (pNode && pNode->id != 0) ? serviceIdAsString(pNode->id) : "unknown",
	  ((float) timeFromPidmapValue(pValue)) / 1000.0,
	  (pValue->fTicking ? "ticking" : "not ticking") );
}

void pidmapStopwatchStart(PidMap *pTable, Pid pid, const StopwatchTime *pTime)
{
	PidmapValue *pValue;

	assert(pTable);

	pValue = (PidmapValue *) g_hash_table_lookup(pTable, (gpointer) pid);

	assert(pValue != NULL);

	/* Don't reset a stopwatch that's ticking
	 */
	if (!pValue->fTicking)
	{
		pValue->tv_laststart = *pTime;

		pValue->fTicking = TRUE;
	}
}

void pidmapStopwatchStop(PidMap *pTable, Pid pid, const StopwatchTime *pTime)
{
	PidmapValue *pValue;

	assert(pTable && pTime);

	pValue = (PidmapValue *) g_hash_table_lookup(pTable, (gpointer) pid);

	assert(pValue != NULL);

	if (pValue->fTicking)
	{
		pValue->tv_cumulative.tv_sec += pTime->tv_sec - pValue->tv_laststart.tv_sec;
		pValue->tv_cumulative.tv_usec += pTime->tv_usec - pValue->tv_laststart.tv_usec;

		pValue->fTicking = FALSE;
	}
}

long pidmapStopwatchTime(PidMap *pTable, Pid pid)
{
	PidmapValue *pValue;

	assert(pTable);

	pValue = (PidmapValue *) g_hash_table_lookup(pTable, (gpointer) pid);

	return timeFromPidmapValue(pValue);
}

/*
 * getStopwatchTime()
 * Returns:
 *	TRUE if the time was obtained from the OS, false if there was an error.
 */

int getStopwatchTime(StopwatchTime *pTime)
{
	int ret;

	assert(pTime != NULL);

	ret = gettimeofday(pTime, NULL);

	return ret == 0;
}

/*
 * timeFromPidmapValue()
 * Returns:
 *   Returns the time in milliseconds ie 1/1000ths of 1 second.
 */
long timeFromPidmapValue(const PidmapValue *pValue)
{
	long ret;

	assert(pValue != NULL);

	ret = (1000 * pValue->tv_cumulative.tv_sec) + (pValue->tv_cumulative.tv_usec / 1000);

	return ret;
}

const char *printTimeDelta(const StopwatchTime *pStart)
{
	StopwatchTime finish;
	static char buf[200];
	long sec, usec;
	float f;

	getStopwatchTime(&finish);

	sec  = finish.tv_sec - pStart->tv_sec;
	usec = finish.tv_usec - pStart->tv_usec;

	f = sec + (((float) usec)/1000000.0);

	snprintf(buf, sizeof(buf), "%4.2f", f);
	/* 
	 * snprintf(buf, sizeof(buf), "%4.2f (finish: %ld+%ld and start: %ld+%ld)", f,
	 *	finish.tv_sec, finish.tv_usec, pStart->tv_sec, pStart->tv_usec );
	 */

	return buf;
}
