/*
 * command.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 <string.h> /* for memcpy  */
#include <unistd.h>
#include "command.h"
#include "log.h"

static char scratch_buf_for_printing[500];
const char *commandTypeAsString(CommandType c);

static const char *cmdAsString(const Command *p);

const char * commandRegisterAsString(const Command *pC);
const char * commandStatusChangeAsString(const Command *pC);
const char * commandServerControlAsString(const Command *pC);
const char * commandQueryAsString(const Command *pC);
const char * commandViaFileAsString(const Command *pC);

/* virtual table for the command*AsString methods
*/
typedef const char * (*AS_STRING)(const Command *pC);

static AS_STRING vt_as_string[eCommandTypeMax];
static int       aSizeof[eCommandTypeMax];  /* a table of sizeof(Command*) */

void commandInitialiseTables()
{
	aSizeof[eCommandTypeRegisterNeed]    = sizeof(CommandRegister);
	aSizeof[eCommandTypeRegisterProvide] = sizeof(CommandRegister);
	aSizeof[eCommandTypeDeregister]      = sizeof(CommandRegister);
	aSizeof[eCommandTypeStatusChange]    = sizeof(CommandStatusChange);
	aSizeof[eCommandTypeServerControl]   = sizeof(CommandServerControl);
	aSizeof[eCommandTypeQuery]           = sizeof(CommandQuery);
	aSizeof[eCommandTypeViaFile]         = sizeof(CommandViaFile);

	vt_as_string[eCommandTypeRegisterNeed]    = commandRegisterAsString;
	vt_as_string[eCommandTypeRegisterProvide] = commandRegisterAsString;
	vt_as_string[eCommandTypeDeregister]      = commandRegisterAsString;
	vt_as_string[eCommandTypeStatusChange]    = commandStatusChangeAsString;
	vt_as_string[eCommandTypeServerControl]   = commandServerControlAsString;
	vt_as_string[eCommandTypeQuery]           = commandQueryAsString;
	vt_as_string[eCommandTypeViaFile]         = commandViaFileAsString;

	{
		/* do some assertions just for the heck of it
		*/
		CommandRegister cr;
		Message msg;
		int i;

		/* assumptions made by the read/write code
		*/
		assert((void *) &(cr.cmd) == (void *) &cr);

		assert((void *) &(msg.header) == (void *) &msg);

		assert(sizeof(Message) < PIPE_BUF);

		for (i = 0; i < eCommandTypeMax; i++)
		{
			assert(aSizeof[i] < PIPE_BUF);
		}
	}
}

int commandToCharStream(const Command *pSrc, char *pDst, int cDst)
{
	int ret;

	if (aSizeof[pSrc->c] > cDst)
	{
		ret = -1;
	}
	else
	{
		memcpy(pDst, pSrc, aSizeof[pSrc->c]);
		ret = aSizeof[pSrc->c];
	}

	return ret;
}

/*
 * pSrc points to an area of memory that contains:
 *  - an int indicating the number of meaningful bytes to follow
 *  - a Command
 */

void commandFromCharStream(Command *pDst, const char *pSrc, int cSrc)
{
	CommandType c = ((Command *) pSrc)->c;

	assert(c < eCommandTypeMax);
	assert(aSizeof[c] >= cSrc);

	/* printf("(sizeof(Command) = %d, cSrc = %d\n", aSizeof[c], cSrc); */

	memcpy(pDst, pSrc, aSizeof[c]);
}

static const char * cmdAsString(const Command *p)
{
	static char buf[100];

	sprintf(buf, "(%d, %s)", p->pidReply, commandTypeAsString(p->c));

	return buf;
}

const char * commandAsString(const Command *p)
{
	return vt_as_string[p->c](p);
}

const char * commandRegisterAsString(const Command *pC)
{
	CommandRegister *p = ((CommandRegister *) pC);

	sprintf(scratch_buf_for_printing, "%s, pid = %d, service name = %s", cmdAsString(pC), p->pid, p->name);

	return scratch_buf_for_printing;
}

const char * commandStatusChangeAsString(const Command *pC)
{
	CommandStatusChange *p = ((CommandStatusChange *) pC);

	sprintf(scratch_buf_for_printing, "%s, pid = %d, exit status == %s", cmdAsString(pC), p->pid, pidStatusAsString(p->status));

	return scratch_buf_for_printing;
}

const char * commandServerControlAsString(const Command *pC)
{
	CommandServerControl *p = ((CommandServerControl *) pC);

	sprintf(scratch_buf_for_printing, "%s, code == %s", cmdAsString(pC), serverControlCodeAsString(p->code));

	return scratch_buf_for_printing;
}

const char * commandQueryAsString(const Command *pC)
{
	CommandQuery *p = ((CommandQuery *) pC);

	sprintf(scratch_buf_for_printing, "%s, query: %s %s %s, channel is %s",
		cmdAsString(pC),
		queryCodeAsString(p->code),
		p->name,
		((strlen(p->fileAdd) > 0) ? p->fileAdd : ""), 
		p->fileChannel);

	return scratch_buf_for_printing;
}

const char * commandViaFileAsString(const Command *pC)
{
	CommandViaFile *p = ((CommandViaFile *) pC);

	sprintf(scratch_buf_for_printing, "%s, via file: code is %s, channel is %s",
		cmdAsString(pC),
		viaFileCodeAsString(p->code),
		p->fileChannel);

	return scratch_buf_for_printing;
}

void commandInitialise(Command *pCommand, CommandType type, Pid pid)
{
	pCommand->c        = type;
	pCommand->pidReply = pid;
}

void messageInitialise(Message *p)
{
	p->header.cBytes = 0;
}

/* Append pCommand to the end of message
*/
void messagePush(Message *pMessage, const Command *pCommand)
{
	int cBytes;
	int indexToWrite = pMessage->header.cBytes;
	cBytes = commandToCharStream(pCommand, &(pMessage->buf[indexToWrite]), sizeof(pMessage->buf) - indexToWrite); 

	assert(cBytes > 0 && cBytes < sizeof(pMessage->buf));

	pMessage->header.cBytes += cBytes;

	printLog(eLogLevelClient, "%d: messagePush(%s)\n", getpid(), commandAsString(pCommand));
}

/*
 * pMessage->buf contains a stack of Command* structures that have
 * been serialised to a character array.
 * This function pops the topmost structure from the stack, first copying 
 * it to a newly allocated Command* structure.  A pointer to the newly
 * allocated Command* structure is returned.
 * The caller of this function is responsible for calling free()
 */
Command * messagePopNew(Message *pMessage)
{
	Command *pCommand = NULL;
	int index = 0;
	CommandType c = eCommandTypeMax;

	/* Find the bit of buf with the command to pop,
	*/
	while (pMessage->header.cBytes > index)
	{
		c = ((Command *) (&pMessage->buf[index]))->c;
		/* printf("c = %d is pointed to by %p\n", c, ((Command *) (&pMessage->buf[index]))); */
		assert(c >= 0 && c < eCommandTypeMax);

		index +=  aSizeof[c];
	}

	/*
	 * malloc some space, copy the relevant bit of pMessage->buf, and return
	 * the newly allocated Command* structure
	 */
	if (index != 0)
	{
		pCommand = (Command *) malloc(aSizeof[c]);
		/* printf("memcpy(dst, pMessage->buf[%d] = %p, %d)\n", index - aSizeof[c], &(pMessage->buf[index  - aSizeof[c]]), aSizeof[c]); */
		memcpy(pCommand, &(pMessage->buf[index  - aSizeof[c]]), aSizeof[c]);

		pMessage->header.cBytes = index - aSizeof[c];
	}
	
	return pCommand;
}

const char *commandTypeAsString(CommandType c)
{
	const char *pReturn;
	static const char *table[eCommandTypeMax];
	static int initialised = FALSE;

	if (!initialised)
	{
		table[eCommandTypeRegisterNeed]    = "need";
		table[eCommandTypeRegisterProvide] = "provide";
		table[eCommandTypeDeregister]      = "unprovide";
		table[eCommandTypeStatusChange]    = "statuschange";
		table[eCommandTypeServerControl]   = "control";
		table[eCommandTypeQuery]           = "query";
		table[eCommandTypeViaFile]         = "viafile";

		initialised = TRUE;
	}

	switch(c)
	{
		case eCommandTypeRegisterNeed:
		case eCommandTypeRegisterProvide:
		case eCommandTypeDeregister:
		case eCommandTypeStatusChange:
		case eCommandTypeServerControl:
		case eCommandTypeQuery:
		case eCommandTypeViaFile:
				pReturn = table[c];
				break;
		default:
				pReturn = "unknown!";
				break;
	};

	return pReturn;
}

RosterQueryCode rosterQueryCodeFromString(const char *psz)
{
	RosterQueryCode ret = eRosterQueryCodeMax;

	assert(psz);

	if (strcmp(psz, "verify") == 0)
	{
		ret = eRosterQueryCodeVerify;
	}
	else if (strcmp(psz, "prior-history") == 0)
	{
		ret = eRosterQueryCodePriorHistory;
	}

	return ret;
}

CommandType commandTypeFromString(const char *psz)
{
	int i;
	CommandType ret = eCommandTypeMax;

	assert(psz);

	for (i = 0; i < eCommandTypeMax; i++)
	{
		if (strcmp(psz, commandTypeAsString(i)) == 0)
		{
			ret = i;
			break;
		}
	}
	
	return ret;
}

const char * pidStatusAsString(PidStatus x)
{
	char *pReturn;

	switch(x)
	{
		case ePidStatusSuccess: pReturn = "Success";  break;
		case ePidStatusFailure: pReturn = "Failure";  break;
		default:                pReturn = "unknown!"; break;
	};

	return pReturn;
}

const char * serverControlCodeAsString(ServerControlCode x)
{
	char *pReturn;

	switch(x)
	{
		case eServerControlCodeStop: pReturn = "stop server";  break;
		default:                     pReturn = "unknown!"; break;
	};

	return pReturn;
}

const char * queryCodeAsString(QueryCode x)
{
	char *pReturn;

	switch(x)
	{
		case eQueryCodeStatus: pReturn = "status";   break;
		case eQueryCodeGraph:  pReturn = "graph";    break;
		default:               pReturn = "unknown!"; break;
	};

	return pReturn;
}

const char * viaFileCodeAsString(ViaFileCode x)
{
	char *pReturn;

	switch(x)
	{
		case eViaFileCodeRosterApplyStarted: pReturn = "apply-started"; break;
		case eViaFileCodeRosterApply:        pReturn = "apply";    break;
		case eViaFileCodeRosterPopulate:     pReturn = "populate"; break;
		case eViaFileCodeRosterQuery:        pReturn = "query";    break;
		default:                             pReturn = "unknown!"; break;
	};

	return pReturn;
}

/*
 * commandDup(const Command *p)
 * Returns
 * A duplicate of the Command* structure
 * Notes
 * The caller is responsible for freeing the memory.
 */
Command * commandDup(const Command *pCommand)
{
	Command *pReturn;
	
	assert(pCommand != NULL);

	pReturn = malloc(sizeof(aSizeof[pCommand->c]));
	memcpy(pReturn, pCommand, sizeof(aSizeof[pCommand->c]));

	return pReturn;
}
