// vi: set ts=4 sw=4 :
// vim: set tw=75 :

// mreg.cpp - functions for registered items (classes MRegCmd, MRegCmdList)

/*
 * Copyright (c) 2001-2003 Will Day <willday@hpgx.net>
 *
 *    This file is part of Metamod.
 *
 *    Metamod is free software; you can redistribute it and/or modify it
 *    under the terms of the GNU General Public License as published by the
 *    Free Software Foundation; either version 2 of the License, or (at
 *    your option) any later version.
 *
 *    Metamod 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 Metamod; if not, write to the Free Software Foundation,
 *    Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    In addition, as a special exception, the author gives permission to
 *    link the code of this program with the Half-Life Game Engine ("HL
 *    Engine") and Modified Game Libraries ("MODs") developed by Valve,
 *    L.L.C ("Valve").  You must obey the GNU General Public License in all
 *    respects for all of the code used other than the HL Engine and MODs
 *    from Valve.  If you modify this file, you may extend this exception
 *    to your version of the file, but you are not obligated to do so.  If
 *    you do not wish to do so, delete this exception statement from your
 *    version.
 *
 */

#ifdef linux
// enable extra routines in system header files, like strsignal
#define _GNU_SOURCE
#endif /* linux */

#include <string.h>			// strsignal, strdup, etc
#include <errno.h>			// strerror, etc

#include <extdll.h>			// always

#include "mreg.h"			// me
#include "metamod.h"		// Plugins, etc
#include "mlist.h"			// class MPluginList
#include "mplugin.h"		// class MPlugin
#include "types_meta.h"		// mBOOL
#include "log_meta.h"		// META_LOG, etc
#include "osdep.h"			// os_safe_call, etc


///// class MRegCmd:

// Init values.  It would probably be more "proper" to use containers and
// constructors, rather than arrays and init-functions.
void MRegCmd::init(int idx)
{
	index = idx;
	name = NULL;
	pfnCmd = NULL;
	plugid = 0;
	status = RG_INVALID;
}

// Try to call the function.  Relies on OS-specific routine to attempt
// calling the function without generating a segfault from an unloaded
// plugin DLL.
// meta_errno values:
//  - ME_BADREQ		function disabled/invalid
//  - ME_ARGUMENT	function pointer is null
mBOOL MRegCmd::call(void) {
	mBOOL ret;

	// can we expect to call this function?
	if(status != RG_VALID)
		RETURN_ERRNO(mFALSE, ME_BADREQ);
	if(!pfnCmd)
		RETURN_ERRNO(mFALSE, ME_ARGUMENT);

	// try to call this function
	ret=os_safe_call(pfnCmd);
	if(!ret) {
		META_DEBUG(4, ("Plugin reg_cmd '%s' called after unloaded; removed from list", name));
		status=RG_INVALID;
		pfnCmd=NULL;
		// NOTE: we can't free the malloc'd space for the name, as that
		// would just re-introduce the segfault problem..
	}
	// meta_errno (if failed) is set already in os_safe_call()
	return(ret);
}


///// class MRegCmdList:

// Constructor
MRegCmdList::MRegCmdList(void)
	: mlist(0), size(REG_CMD_GROWSIZE), endlist(0)
{
	int i;
	mlist = (MRegCmd *) malloc(size * sizeof(MRegCmd));
	// initialize array
	for(i=0; i< size; i++)
		mlist[i].init(i+1);		// 1-based index
	endlist=0;
}

// Try to find a registered function with the given name.
// meta_errno values:
//  - ME_NOTFOUND	couldn't find a matching function
MRegCmd *MRegCmdList::find(const char *findname) {
	int i;
	for(i=0; i < endlist; i++) {
		if(!strcasecmp(mlist[i].name, findname))
				return(&mlist[i]);
	}
	RETURN_ERRNO(NULL, ME_NOTFOUND);
}

// Add the given name to the list and return the instance.  This only
// writes the "name" to the new cmd; other fields are writtin by caller
// (meta_AddServerCommand).
// meta_errno values:
//  - ME_NOMEM			couldn't realloc or malloc for various parts
MRegCmd *MRegCmdList::add(const char *addname) {
	MRegCmd *icmd;

	if(endlist==size) {
		// grow array
		MRegCmd *temp;
		int i, newsize;
		newsize=size+REG_CMD_GROWSIZE;
		META_DEBUG(6, ("Growing reg cmd list from %d to %d", size, newsize));
		temp = (MRegCmd *) realloc(mlist, newsize*sizeof(MRegCmd));
		if(!temp) {
			META_ERROR("Couldn't grow registered command list to %d for '%s': %s", newsize, addname, strerror(errno));
			RETURN_ERRNO(NULL, ME_NOMEM);
		}
		mlist=temp;
		size=newsize;
		// initialize new (unused) entries
		for(i=endlist; i<size; i++)
			mlist[i].init(i+1);		// 1-based
	}
	icmd = &mlist[endlist];

	// Malloc space separately for the command name, because:
	//  - Can't point to memory loc in plugin (another segv waiting to 
	//    happen).
	//  - Can't point to memory in mlist which might get moved later by 
	//    realloc (again, segv).
	icmd->name=strdup(addname);
	if(!icmd->name) {
		META_ERROR("Couldn't strdup for adding reg cmd name '%s': %s", 
				addname, strerror(errno));
		RETURN_ERRNO(NULL, ME_NOMEM);
	}
	endlist++;
	
	return(icmd);
}

// Disable any functions belonging to the given plugin (by index id).
void MRegCmdList::disable(int plugin_id) {
	int i;
	for(i=0; i< size; i++) {
		if(mlist[i].plugid == plugin_id)
			mlist[i].status = RG_INVALID;
	}
}

// List all the registered commands.
void MRegCmdList::show(void) {
	int i, n=0, a=0;
	MRegCmd *icmd;
	MPlugin *iplug;
	char bplug[18+1];	// +1 for term null

	META_CONS("Registered plugin commands:");
	META_CONS("  %*s  %-*s  %-s", 
			WIDTH_MAX_REG, "",
			sizeof(bplug)-1, "plugin", "command");
	for(i=0; i < endlist; i++) {
		icmd = &mlist[i];
		if(icmd->status==RG_VALID) {
			iplug=Plugins->find(icmd->plugid);
			if(iplug)
				STRNCPY(bplug, iplug->desc, sizeof(bplug));
			else
				STRNCPY(bplug, "(unknown)", sizeof(bplug));
		}
		else
			STRNCPY(bplug, "(unloaded)", sizeof(bplug));
		META_CONS(" [%*d] %-*s  %-s", 
				WIDTH_MAX_REG, icmd->index, 
				sizeof(bplug)-1, bplug,
				icmd->name);
		if(icmd->status==RG_VALID) a++;
		n++;
	}
	META_CONS("%d commands, %d available (%d allocated)", n, a, size);
}

// List all the registered commands for the given plugin id.
void MRegCmdList::show(int plugin_id) {
	int i, n=0;
	MRegCmd *icmd;

	// If OS doesn't support DLFNAME, then we can't know what the plugin's
	// registered cvars are.
	DLFNAME(NULL);
	if(meta_errno==ME_OSNOTSUP) {
		META_CONS("Registered commands: unknown (can't get info under this OS)");
		return;
	}
	META_CONS("Registered commands:");
	for(i=0; i < endlist; i++) {
		icmd = &mlist[i];
		if(icmd->plugid != plugin_id)
			continue;
		META_CONS("   %s", icmd->name);
		n++;
	}
	META_CONS("%d commands", n);
}


///// class MRegCvar:

// Init values.  It would probably be more "proper" to use containers and
// constructors, rather than arrays and init-functions.
void MRegCvar::init(int idx)
{
	index = idx;
	data = NULL;
	plugid = 0;
	status = RG_INVALID;
}

// Set the cvar, copying values from given cvar.
// meta_errno values:
//  - ME_ARGUMENT	given cvar doesn't match this cvar
mBOOL MRegCvar::set(cvar_t *src) {
	if(strcasecmp(src->name, data->name)) {
		META_ERROR("Tried to set cvar with mismatched name; src=%s dst=%s",
				src->name, data->name);
		RETURN_ERRNO(mFALSE, ME_ARGUMENT);
	}
	// Would like to free() existing string, but can't tell where it was
	// allocated...
	data->string = strdup(src->string);
	data->flags = src->flags;
	data->value = src->value;
	data->next = src->next;
	return(mTRUE);
}


///// class MRegCvarList:

// Constructor
MRegCvarList::MRegCvarList(void)
	: vlist(0), size(REG_CVAR_GROWSIZE), endlist(0)
{
	int i;
	vlist = (MRegCvar *) malloc(size * sizeof(MRegCvar));
	// initialize array
	for(i=0; i< size; i++)
		vlist[i].init(i+1);		// 1-based
	endlist=0;
}

// Add the given cvar name to the list and return the instance.  This only
// writes the "name" to the new cvar; other fields are written with
// cvar::set().
// meta_errno values:
//  - ME_NOMEM			couldn't alloc or realloc for various parts
MRegCvar *MRegCvarList::add(const char *addname) {
	MRegCvar *icvar;

	if(endlist==size) {
		// grow array
		MRegCvar *temp;
		int i, newsize;
		newsize=size+REG_CVAR_GROWSIZE;
		META_DEBUG(6, ("Growing reg cvar list from %d to %d", size, newsize));
		temp = (MRegCvar *) realloc(vlist, newsize*sizeof(MRegCvar));
		if(!temp) {
			META_ERROR("Couldn't grow registered cvar list to %d for '%s'; %s", newsize, addname, strerror(errno));
			RETURN_ERRNO(NULL, ME_NOMEM);
		}
		vlist=temp;
		size=newsize;
		// initialize new (unused) entries
		for(i=endlist; i<size; i++)
			vlist[i].init(i+1);		// 1-based
	}

	icvar = &vlist[endlist];

	// Malloc space for the cvar and cvar name, for two reasons:
	//  - Can't point to memory loc in plugin (another segv waiting to 
	//    happen).
	//  - Can't point to memory in vlist which might get moved later by 
	//    realloc (again, segv).
	icvar->data = (cvar_t *) malloc(sizeof(cvar_t));
	if(!icvar->data) {
		META_ERROR("Couldn't malloc cvar for adding reg cvar name '%s': %s", 
				addname, strerror(errno));
		RETURN_ERRNO(NULL, ME_NOMEM);
	}
	icvar->data->name=strdup(addname);
	if(!icvar->data->name) {
		META_ERROR("Couldn't strdup for adding reg cvar name '%s': %s", 
				addname, strerror(errno));
		RETURN_ERRNO(NULL, ME_NOMEM);
	}
	endlist++;
	
	return(icvar);
}

// Try to find a registered cvar with the given name.
// meta_errno values:
//  - ME_NOTFOUND	couldn't find a matching cvar
MRegCvar *MRegCvarList::find(const char *findname) {
	int i;
	for(i=0; i < endlist; i++) {
		if(!strcasecmp(vlist[i].data->name, findname))
				return(&vlist[i]);
	}
	RETURN_ERRNO(NULL, ME_NOTFOUND);
}

// Disable any cvars belonging to the given plugin (by index id).
void MRegCvarList::disable(int plugin_id) {
	int i;
	MRegCvar *icvar;
	for(i=0; i< size; i++) {
		icvar=&vlist[i];
		if(icvar->plugid == plugin_id) {
			icvar->status = RG_INVALID;
			icvar->plugid = 0;
			// Decided not to do this, in order to keep pre-existing values
			// after a plugin reload.
			// CVAR_SET_STRING(icvar->data->name, "[metamod: cvar invalid; plugin unloaded]");
		}
	}
}

// List all the registered cvars.
void MRegCvarList::show(void) {
	int i, n=0, a=0;
	MRegCvar *icvar;
	MPlugin *iplug;
	char bplug[13+1], bname[20+1], bval[15+1];	// +1 for term null

	META_CONS("Registered plugin cvars:");
	META_CONS("  %*s  %-*s  %-*s  %*s  %s", 
			WIDTH_MAX_REG, "",
			sizeof(bplug)-1, "plugin",
			sizeof(bname)-1, "cvar",
			sizeof(bval)-1, "float value",
			"string value");
	for(i=0; i < endlist; i++) {
		icvar = &vlist[i];
		if(icvar->status==RG_VALID) {
			iplug=Plugins->find(icvar->plugid);
			if(iplug)
				STRNCPY(bplug, iplug->desc, sizeof(bplug));
			else
				STRNCPY(bplug, "(unknown)", sizeof(bplug));
		}
		else
			STRNCPY(bplug, "(unloaded)", sizeof(bplug));
		STRNCPY(bname, icvar->data->name, sizeof(bname));
		snprintf(bval, sizeof(bval), "%f", icvar->data->value);
		META_CONS(" [%*d] %-*s  %-*s  %*s  %s", 
				WIDTH_MAX_REG, icvar->index, 
				sizeof(bplug)-1, bplug,
				sizeof(bname)-1, bname,
				sizeof(bval)-1, bval,
				icvar->data->string);
		if(icvar->status==RG_VALID) a++;
		n++;
	}
	META_CONS("%d cvars, %d available (%d allocated)", n, a, size);
}

// List the registered cvars for the given plugin id.
void MRegCvarList::show(int plugin_id) {
	int i, n=0;
	MRegCvar *icvar;
	char bname[30+1], bval[15+1];	// +1 for term null

	// If OS doesn't support DLFNAME, then we can't know what the plugin's
	// registered cvars are.
	DLFNAME(NULL);
	if(meta_errno==ME_OSNOTSUP) {
		META_CONS("Registered cvars: unknown (can't get info under this OS)");
		return;
	}
	META_CONS("%-*s     %*s  %s", 
			sizeof(bname)-1, "Registered cvars:",
			sizeof(bval)-1, "float value",
			"string value");
	for(i=0; i < endlist; i++) {
		icvar = &vlist[i];
		if(icvar->plugid != plugin_id)
			continue;
		STRNCPY(bname, icvar->data->name, sizeof(bname));
		snprintf(bval, sizeof(bval), "%f", icvar->data->value);
		META_CONS("   %-*s  %*s  %s", 
				sizeof(bname)-1, bname,
				sizeof(bval)-1, bval,
				icvar->data->string);
		n++;
	}
	META_CONS("%d cvars", n); 
}


///// class MRegMsgList:

// Constructor
MRegMsgList::MRegMsgList(void)
	: size(MAX_REG_MSGS), endlist(0)
{
	int i;
	// initialize array
	memset(mlist, 0, sizeof(mlist));
	for(i=0; i< size; i++) {
		mlist[i].index=i+1;		// 1-based
	}
	endlist=0;
}

// Add the given user msg the list and return the instance.
// meta_errno values:
//  - ME_MAXREACHED		reached max number of msgs allowed
MRegMsg *MRegMsgList::add(const char *addname, int addmsgid, int addsize) {
	MRegMsg *imsg;

	if(endlist==size) {
		// all slots used
		META_ERROR("Couldn't add registered msg '%s' to list; reached max msgs (%d)",
				addname, size);
		RETURN_ERRNO(NULL, ME_MAXREACHED);
	}

	imsg = &mlist[endlist];
	endlist++;

	// Copy msg data into empty slot.
	// Note: 'addname' assumed to be a constant string allocated in the
	// gamedll.
	imsg->name=addname;
	imsg->msgid=addmsgid;
	imsg->size=addsize;

	return(imsg);
}

// Try to find a registered msg with the given name.
// meta_errno values:
//  - ME_NOTFOUND	couldn't find a matching cvar
MRegMsg *MRegMsgList::find(const char *findname) {
	int i;
	for(i=0; i < endlist; i++) {
		if(!strcmp(mlist[i].name, findname))
			return(&mlist[i]);
	}
	RETURN_ERRNO(NULL, ME_NOTFOUND);
}

// Try to find a registered msg with the given msgid.
// meta_errno values:
//  - ME_NOTFOUND	couldn't find a matching cvar
MRegMsg *MRegMsgList::find(int findmsgid) {
	int i;
	for(i=0; i < endlist; i++) {
		if(mlist[i].msgid == findmsgid)
			return(&mlist[i]);
	}
	RETURN_ERRNO(NULL, ME_NOTFOUND);
}

// List the registered usermsgs for the gamedll.
void MRegMsgList::show(void) {
	int i, n=0;
	MRegMsg *imsg;
	char bname[25+1];	// +1 for term null

	META_CONS("%-*s    %5s  %5s", 
			sizeof(bname)-1, "Game registered user msgs:", "msgid", "size");
	for(i=0; i < endlist; i++) {
		imsg = &mlist[i];
		STRNCPY(bname, imsg->name, sizeof(bname));
		META_CONS("   %-*s   %3d    %3d", 
				sizeof(bname)-1, bname,
				imsg->msgid,
				imsg->size);
		n++;
	}
	META_CONS("%d game user msgs", n); 
}

