/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * Nota bene: later versions of the GNU General Public License do not apply
 * to this program.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
/*
 * CDP API definitions
 * the API is used by both the engine and modules
 */

#include "cdp.h"
#include "port.h"
#include "cdpint.h"
#include "uqmversion.h"

#define MAX_REG_ITFS   255
#define MAX_REG_EVENTS 1023

static cdp_Error cdp_api_error = CDPERR_NONE;

static uint32 cdp_Host_GetApiVersion (void);
static uint32 cdp_Host_GetVersion (void);
static cdp_Error cdp_Host_GetApiError (void);
static cdp_Itf* cdp_Host_GetItf (const char* name);
static bool cdp_Host_GetItfs (cdp_ItfDef* defs);
static cdp_ItfReg* cdp_Host_RegisterItf (const char* name,
			cdp_ApiVersion ver_from, cdp_ApiVersion ver_to,
			cdp_Itf*, cdp_Module*);
static void cdp_Host_UnregisterItf (cdp_ItfReg*);
static bool cdp_Host_RegisterItfs (cdp_ItfDef* defs, cdp_Module*);
static void cdp_Host_UnregisterItfs (cdp_ItfDef* defs);
static cdp_Event cdp_Host_GetEvent (const char* name);
static bool cdp_Host_GetEvents (cdp_EventDef* defs);
static cdp_EventReg* cdp_Host_RegisterEvent (const char* name, cdp_Module*);
static void cdp_Host_UnregisterEvent (cdp_EventReg*);
static bool cdp_Host_RegisterEvents (cdp_EventDef* defs, cdp_Module*);
static void cdp_Host_UnregisterEvents (cdp_EventDef* defs);
static bool cdp_Host_SubscribeEvent (cdp_Event, cdp_EventProc, cdp_Module*);
static void cdp_Host_UnsubscribeEvent (cdp_Event, cdp_EventProc);
static bool cdp_Host_SubscribeEvents (cdp_EventDef* defs, cdp_Module*);
static void cdp_Host_UnsubscribeEvents (cdp_EventDef* defs);
static cdp_EventResult cdp_Host_FireEvent (cdp_EventReg*, uint32, void*);

// Interfaces
cdp_Itf_HostVtbl_v1 cdp_host_itf_v1 = 
{
	cdp_Host_GetApiVersion,
	cdp_Host_GetVersion,
	cdp_Host_GetApiError,
	cdp_Host_GetItf,
	cdp_Host_GetItfs,
	cdp_Host_RegisterItf,
	cdp_Host_UnregisterItf,
	cdp_Host_RegisterItfs,
	cdp_Host_UnregisterItfs,
	cdp_Host_GetEvent,
	cdp_Host_GetEvents,
	cdp_Host_RegisterEvent,
	cdp_Host_UnregisterEvent,
	cdp_Host_RegisterEvents,
	cdp_Host_UnregisterEvents,
	cdp_Host_SubscribeEvent,
	cdp_Host_UnsubscribeEvent,
	cdp_Host_SubscribeEvents,
	cdp_Host_UnsubscribeEvents,
	cdp_Host_FireEvent,
};

cdp_Itf_MemoryVtbl_v1 cdp_memory_itf_v1 =
{
	HMalloc,
	HFree,
	HCalloc,
	HRealloc,
};

cdp_Itf_IoVtbl_v1 cdp_io_itf_v1 = 
{
	uio_fopen,
	uio_fclose,
	uio_fread,
	uio_fwrite,
	uio_fseek,
	uio_ftell,
	uio_fflush,
	uio_feof,
	uio_ferror,
};

cdp_Itf_SoundVtbl_v1 cdp_sound_itf_v1 = 
{
	SoundDecoder_Register,
	SoundDecoder_Unregister,
	SoundDecoder_Lookup,
};

cdp_Itf_VideoVtbl_v1 cdp_video_itf_v1 = 
{
	VideoDecoder_Register,
	VideoDecoder_Unregister,
	VideoDecoder_Lookup,
};

// the actual interface registration struct/handle
struct cdp_ItfReg
{
	bool builtin;
	bool used;
	const char* name;
	cdp_ApiVersion ver_from;
	cdp_ApiVersion ver_to;
	cdp_Itf* itfvtbl;
	cdp_Module* module;
};

#define CDP_DECLARE_ITF(kind,vf,vt,vtbl) \
	{true, true, CDPITF_KIND_##kind, \
	CDPAPI_VERSION_##vf, CDPAPI_VERSION_##vt, vtbl, NULL}

// Built-in interfaces + space for loadable
cdp_ItfReg cdp_itfs[MAX_REG_ITFS + 1] =
{
	CDP_DECLARE_ITF (HOST,   1, 1, &cdp_host_itf_v1),
	CDP_DECLARE_ITF (MEMORY, 1, 1, &cdp_memory_itf_v1),
	CDP_DECLARE_ITF (IO,     1, 1, &cdp_io_itf_v1),
	CDP_DECLARE_ITF (SOUND,  1, 1, &cdp_sound_itf_v1),
	CDP_DECLARE_ITF (VIDEO,  1, 1, &cdp_video_itf_v1),
	// TODO: put newly defined built-in interfaces here

	{false, false, "", 0, 0, NULL} // term
};

// event bind descriptor
typedef struct
{
	cdp_EventProc proc;
	cdp_Module* module;

} cdp_EventBind;

#define EVENT_BIND_GROW  16

// the actual event registration struct/handle
struct cdp_EventReg
{
	bool builtin;
	bool used;
	const char* name;
	cdp_EventBind* binds;
	uint32 bindslots;
	cdp_Module* module;
};

#define CDP_DECLARE_EVENT(name) \
	{true, true, "UQM." #name, NULL, 0, NULL}

// Built-in events + space for loadable
// a cdp_Event handle is an index into this array
cdp_EventReg cdp_evts[MAX_REG_EVENTS + 1] =
{
	// sample - no real events defined yet
	CDP_DECLARE_EVENT (PlanetSide.TouchDown),
	CDP_DECLARE_EVENT (PlanetSide.LiftOff),
	// TODO: put newly defined built-in events here

	{false, false, "", NULL, 0, NULL} // term
};

cdp_Error
cdp_GetApiError (void)
{
	cdp_Error ret = cdp_api_error;
	cdp_api_error = CDPERR_NONE;
	return ret;
}

bool
cdp_InitApi (void)
{
	int i;
	cdp_Module* kernel;
	
	// preprocess built-in itfs
	kernel = cdp_LoadModule (NULL);

	for (i = 0; cdp_itfs[i].builtin; ++i)
	{
		cdp_itfs[i].module = kernel;
	}
	// clear the rest
	//memset (cdp_itfs + i, 0,
	//		sizeof (cdp_itfs) - sizeof (cdp_ItfReg) * i);

	for (i = 0; cdp_evts[i].builtin; ++i)
	{
		cdp_evts[i].module = kernel;
		cdp_evts[i].bindslots = 0;
		cdp_evts[i].binds = NULL;
	}

	return true;
}

void
cdp_UninitApi (void)
{
	cdp_ItfReg* itf;

	// unregister custom interfaces
	for (itf = cdp_itfs; itf->used; ++itf)
	{
		if (!itf->builtin)
		{
			itf->used = false;
			if (itf->name)
				HFree ((void*) itf->name);
			itf->name = NULL;
			itf->itfvtbl = NULL;
			itf->module = NULL;
		}
	}
}

static uint32
cdp_Host_GetApiVersion (void)
{
	return CDPAPI_VERSION;
}

static uint32
cdp_Host_GetVersion (void)
{
	return (UQM_MAJOR_VERSION << 20) | (UQM_MINOR_VERSION << 15) |
			UQM_PATCH_VERSION;
}

static cdp_Error
cdp_Host_GetApiError (void)
{
	return cdp_GetApiError ();
}

static char*
cdp_MakeContextName (const char* ctx, const char* name)
{
	int namelen;
	char* id_name;

	namelen = strlen(ctx) + strlen(name) + 2;
	id_name = HMalloc (namelen);
	strcpy(id_name, ctx);
	strcat(id_name, ".");
	strcat(id_name, name);

	return id_name;
}

/***********************************************************
 *  Interface system                                       *
 ***********************************************************/

cdp_ItfReg*
cdp_GetInterfaceReg (const char* name, cdp_ApiVersion api_ver)
{
	cdp_ItfReg* itf;

	for (itf = cdp_itfs; itf->used &&
			(!itf->name || stricmp(itf->name, name) != 0 ||
			 api_ver < itf->ver_from || api_ver > itf->ver_to);
			itf++)
		;
	if (!itf->name)
	{
		cdp_api_error = CDPERR_NO_ITF;
		return NULL;
	}
	
	return itf;
}

cdp_Itf*
cdp_GetInterface (const char* name, cdp_ApiVersion api_ver)
{
	cdp_ItfReg* reg;

	reg = cdp_GetInterfaceReg (name, api_ver);
	return reg ? reg->itfvtbl : NULL;
}

static cdp_Itf*
cdp_Host_GetItf (const char* name)
{
	return cdp_GetInterface (name, CDPAPI_VERSION_1);
}

static bool
cdp_Host_GetItfs (cdp_ItfDef* defs)
{
	cdp_ItfDef* def;
	cdp_ItfReg* reg;
	int errors = 0;

	for (def = defs; def->name; ++def)
	{
		// registration handle is not returned
		def->reg = NULL;

		reg = cdp_GetInterfaceReg (def->name, CDPAPI_VERSION_1);
		if (reg)
		{
			def->itf = reg->itfvtbl;
			def->name = reg->name; // set to cannonical name
			def->ver_from = reg->ver_from;
			def->ver_to = reg->ver_to;
			def->module = reg->module;
		}
		else
		{
			def->itf = NULL;
			def->module = NULL;
			def->ver_from = 0;
			def->ver_to = 0;
			++errors;
		}
	}

	return !errors;
}

static cdp_ItfReg*
cdp_Host_RegisterItf (const char* name, cdp_ApiVersion ver_from,
		cdp_ApiVersion ver_to, cdp_Itf* itfvtbl,
		cdp_Module* owner)
{
	cdp_ItfReg* itfreg;
	cdp_ItfReg* newslot = NULL;
	char* id_name;
	const char* ctx;

	if (!owner)
	{
		fprintf (stderr, "cdp_Host_RegisterItf(): "
				"No owner info supplied\n");
		//return NULL;
	}
	if (!name || !*name || !itfvtbl)
	{
		fprintf (stderr, "cdp_Host_RegisterItf(): "
				"Null or invalid interface (from %s)\n",
				cdp_GetModuleName (owner, true));
		return NULL;
	}
	ctx = cdp_GetModuleContext (owner, false);
	if (!ctx)
	{
		fprintf (stderr, "cdp_Host_RegisterItf(): "
				"Null or invalid context (from %s)\n",
				cdp_GetModuleName (owner, true));
		return NULL;
	}

	// TODO: review version policy (below)
	// enforce version policy and do not allow obsolete interfaces
	// POLICY: all modules MUST be aware of recent API changes and will not
	// be allowed to expose interfaces that support and/or utilize obsoleted
	// API versions
	if (ver_from < CDPAPI_VERSION_MIN)
		ver_from = CDPAPI_VERSION_MIN;
	if (ver_to < CDPAPI_VERSION_MIN)
	{
		fprintf (stderr, "cdp_Host_RegisterItf(): "
				"Obsolete interface %s (from %s)\n",
				name, cdp_GetModuleName (owner, true));
		return NULL;
	}

	id_name = cdp_MakeContextName (ctx, name);

	// check if interface already registered
	for (itfreg = cdp_itfs; itfreg->used &&
			(!itfreg->name || stricmp(itfreg->name, id_name) != 0 ||
			 ver_from < itfreg->ver_from || ver_to > itfreg->ver_to);
			++itfreg)
	{
		// and pick up an empty slot (where available)
		if (!newslot && !itfreg->name)
			newslot = itfreg;
	}

	if (itfreg >= cdp_itfs + MAX_REG_ITFS)
	{
		fprintf (stderr, "cdp_Host_RegisterItf(): "
				"Interfaces limit reached\n");
		HFree (id_name);
		return NULL;
	}
	else if (itfreg->name)
	{
		fprintf (stderr, "cdp_Host_RegisterItf(): "
				"Interface %s already registered for these versions, "
				"%s denied\n",
				name, cdp_GetModuleName (owner, true));
		HFree (id_name);
		return NULL;
	}
	
	if (!newslot)
	{
		newslot = itfreg;
		newslot->used = true;
		// make next one a term
		itfreg[1].builtin = false;
		itfreg[1].used = false;
		itfreg[1].name = NULL;
		itfreg[1].itfvtbl = NULL;
	}

	newslot->name = id_name;
	newslot->ver_from = ver_from;
	newslot->ver_to = ver_to;
	newslot->itfvtbl = itfvtbl;
	newslot->module = owner;
	
	return newslot;
}

static void
cdp_Host_UnregisterItf (cdp_ItfReg* itfreg)
{
	if (itfreg < cdp_itfs || itfreg >= cdp_itfs + MAX_REG_ITFS ||
			!itfreg->name || !itfreg->itfvtbl)
	{
		fprintf (stderr, "cdp_Host_UnregisterItf(): "
				"Invalid or expired interface passed\n");
		return;
	}

	if (!itfreg->builtin)
	{
		HFree ((void*) itfreg->name);
	}
	itfreg->module = NULL;
	itfreg->name = NULL;
	itfreg->itfvtbl = NULL;
}

static bool
cdp_Host_RegisterItfs (cdp_ItfDef* defs, cdp_Module* owner)
{
	cdp_ItfDef* def;
	int errors = 0;

	for (def = defs; def->name; ++def)
	{
		def->reg = cdp_Host_RegisterItf (def->name, def->ver_from,
				def->ver_to, def->itf, owner);
		if (def->reg)
		{
			def->module = owner;
		}
		else
		{
			def->module = NULL;
			++errors;
		}
	}

	return !errors;
}

static void
cdp_Host_UnregisterItfs (cdp_ItfDef* defs)
{
	cdp_ItfDef* def;

	for (def = defs; def->name; ++def)
	{
		if (def->reg)
			cdp_Host_UnregisterItf (def->reg);
	}
}

/***********************************************************
 *  Event system                                           *
 ***********************************************************/

cdp_EventReg*
cdp_GetEventReg (const char* name)
{
	cdp_EventReg* evt;

	for (evt = cdp_evts; evt->used &&
			(!evt->name || stricmp(evt->name, name) != 0);
			evt++)
		;
	if (!evt->name)
	{
		cdp_api_error = CDPERR_NO_EVENT;
		return NULL;
	}
	
	return evt;
}

// hopefully inlinable
static cdp_Event
cdp_EventFromReg (cdp_EventReg* reg)
{
	return (reg - cdp_evts) / sizeof (cdp_EventReg);
}

// hopefully inlinable
static cdp_EventReg*
cdp_RegFromEvent (cdp_Event event)
{
	return cdp_evts + event;
}

cdp_Event
cdp_GetEvent (const char* name)
{
	cdp_EventReg* reg;

	reg = cdp_GetEventReg (name);
	return reg ? cdp_EventFromReg (reg) : CDP_EVENT_INVALID;
}

static cdp_EventBind*
cdp_AllocEventBinds (cdp_EventBind* binds, uint32 ccur, uint32 cnew)
{
	cdp_EventBind* newbinds;
	uint32 newsize;

	newsize = cnew * sizeof (cdp_EventBind);
	if (binds)
		newbinds = HRealloc (binds, newsize);
	else
		newbinds = HMalloc (newsize);

	if (cnew > ccur)
		memset (newbinds + ccur, 0,
				(cnew - ccur) * sizeof (cdp_EventBind));

	return newbinds;
}

static cdp_Event
cdp_Host_GetEvent (const char* name)
{
	return cdp_GetEvent (name);
}

static bool
cdp_Host_GetEvents (cdp_EventDef* defs)
{
	cdp_EventDef* def;
	cdp_EventReg* reg;
	int errors = 0;

	for (def = defs; def->name; ++def)
	{
		// registration handle is not returned
		def->reg = NULL;

		reg = cdp_GetEventReg (def->name);
		if (reg)
		{
			def->event = cdp_EventFromReg(reg);
			def->name = reg->name; // set to cannonical name
			def->module = reg->module;
		}
		else
		{
			def->event = CDP_EVENT_INVALID;
			def->module = NULL;
			++errors;
		}
	}

	return !errors;
}

static cdp_EventReg*
cdp_Host_RegisterEvent (const char* name, cdp_Module* owner)
{
	cdp_EventReg* evtreg;
	cdp_EventReg* newslot = NULL;
	char* id_name;
	const char* ctx;

	if (!owner)
	{
		fprintf (stderr, "cdp_Host_RegisterEvent(): "
				"No owner info supplied\n");
		//return NULL;
	}
	if (!name || !*name)
	{
		fprintf (stderr, "cdp_Host_RegisterEvent(): "
				"Null or invalid event (from %s)\n",
				cdp_GetModuleName (owner, true));
		return NULL;
	}
	ctx = cdp_GetModuleContext (owner, false);
	if (!ctx)
	{
		fprintf (stderr, "cdp_Host_RegisterEvent(): "
				"Null or invalid context (from %s)\n",
				cdp_GetModuleName (owner, true));
		return NULL;
	}

	id_name = cdp_MakeContextName (ctx, name);

	// check if event already registered
	for (evtreg = cdp_evts; evtreg->used &&
			(!evtreg->name || stricmp(evtreg->name, id_name) != 0);
			++evtreg)
	{
		// and pick up an empty slot (where available)
		if (!newslot && !evtreg->name)
			newslot = evtreg;
	}

	if (evtreg >= cdp_evts + MAX_REG_EVENTS)
	{
		fprintf (stderr, "cdp_Host_RegisterEvent(): "
				"Event limit reached\n");
		HFree (id_name);
		return NULL;
	}
	else if (evtreg->name)
	{
		fprintf (stderr, "cdp_Host_RegisterEvent(): "
				"Event %s already registered, "
				"%s denied\n",
				name, cdp_GetModuleName (owner, true));
		HFree (id_name);
		return NULL;
	}
	
	if (!newslot)
	{
		newslot = evtreg;
		newslot->used = true;
		// make next one a term
		evtreg[1].builtin = false;
		evtreg[1].used = false;
		evtreg[1].name = NULL;
	}

	newslot->name = id_name;
	newslot->module = owner;
	newslot->binds = NULL;
	newslot->bindslots = 0;
	
	return newslot;
}

static void
cdp_Host_UnregisterEvent (cdp_EventReg* evtreg)
{
	if (evtreg < cdp_evts || evtreg >= cdp_evts + MAX_REG_EVENTS ||
			!evtreg->name)
	{
		fprintf (stderr, "cdp_Host_UnregisterEvent(): "
				"Invalid or expired event passed\n");
		return;
	}

	if (!evtreg->builtin)
	{
		HFree ((void*) evtreg->name);
	}
	evtreg->module = NULL;
	evtreg->name = NULL;
	if (evtreg->binds)
		HFree (evtreg->binds);
	evtreg->binds = NULL;
	evtreg->bindslots = 0;
}

static bool
cdp_Host_RegisterEvents (cdp_EventDef* defs, cdp_Module* owner)
{
	cdp_EventDef* def;
	int errors = 0;

	for (def = defs; def->name; ++def)
	{
		def->reg = cdp_Host_RegisterEvent (def->name, owner);
		if (def->reg)
		{
			def->module = owner;
		}
		else
		{
			def->module = NULL;
			++errors;
		}
	}

	return !errors;
}

static void
cdp_Host_UnregisterEvents (cdp_EventDef* defs)
{
	cdp_EventDef* def;

	for (def = defs; def->name; ++def)
	{
		if (def->reg)
			cdp_Host_UnregisterEvent (def->reg);
	}
}

static bool
cdp_Host_SubscribeEvent (cdp_Event event, cdp_EventProc proc, cdp_Module* module)
{
	cdp_EventReg* reg = cdp_RegFromEvent (event);
	cdp_EventBind* bind = NULL;
	uint32 i;

	if (reg < cdp_evts || reg >= cdp_evts + MAX_REG_EVENTS ||
			!reg->name)
	{
		fprintf (stderr, "cdp_Host_SubscribeEvent(): "
				"Invalid or expired event passed\n");
		return false;
	}

	if (reg->binds)
	{
		// check for duplicate or find a new slot
		for (i = 0, bind = reg->binds; i < reg->bindslots &&
				(!bind->proc || bind->proc != proc);
				++i, ++bind)
			;
		if (i >= reg->bindslots)
		{	// full - add more slots
			reg->binds = cdp_AllocEventBinds (reg->binds,
					reg->bindslots, reg->bindslots + EVENT_BIND_GROW);
			bind = reg->binds + reg->bindslots;
			reg->bindslots += EVENT_BIND_GROW;
		}
		else if (bind->proc == proc)
		{	// already bound
			return true;
		}
	}
	else
	{
		reg->binds = cdp_AllocEventBinds (NULL, 0, EVENT_BIND_GROW);
		reg->bindslots = EVENT_BIND_GROW;
		bind = reg->binds;
	}

	bind->proc = proc;
	bind->module = module;

	return true;
}

static void
cdp_Host_UnsubscribeEvent (cdp_Event event, cdp_EventProc proc)
{
	cdp_EventReg* reg = cdp_RegFromEvent (event);
	cdp_EventBind* bind = NULL;
	uint32 i;

	if (reg < cdp_evts || reg >= cdp_evts + MAX_REG_EVENTS ||
			!reg->name)
	{	// event either expired or invalid
		return;
	}

	if (!reg->binds || !reg->bindslots)
		return; // hmm, no bindings

	// check for duplicate or find a new slot
	for (i = 0, bind = reg->binds; i < reg->bindslots &&
			bind->proc != proc;
			++i, ++bind)
		;
	if (i >= reg->bindslots)
		return; // binding not found

	bind->proc = NULL;
	bind->module = NULL;
}

static bool
cdp_Host_SubscribeEvents (cdp_EventDef* defs, cdp_Module* module)
{
	cdp_EventDef* def;
	int errors = 0;

	for (def = defs; def->name; ++def)
	{
		if (def->event != CDP_EVENT_INVALID && def->proc)
			if (!cdp_Host_SubscribeEvent (def->event, def->proc, module))
				++errors;
	}
	return !errors;
}

static void
cdp_Host_UnsubscribeEvents (cdp_EventDef* defs)
{
	cdp_EventDef* def;

	for (def = defs; def->name; ++def)
	{
		if (def->event != CDP_EVENT_INVALID && def->proc)
			cdp_Host_UnsubscribeEvent (def->event, def->proc);
	}
}

static cdp_EventResult
cdp_Host_FireEvent (cdp_EventReg* evtreg, uint32 iparam, void* pparam)
{
	bool bHandled = false;
	cdp_EventResult ret = 0;
	cdp_Event event;
	cdp_EventBind* bind;
	uint32 i;

	if (evtreg < cdp_evts || evtreg >= cdp_evts + MAX_REG_EVENTS ||
			!evtreg->name)
	{
#ifdef DEBUG
		fprintf (stderr, "cdp_Host_FireEvent(): Invalid event\n");
#endif
		return 0;
	}

	if (!evtreg->binds)
		return 0; // no subscribers

	event = cdp_EventFromReg (evtreg);

	// call event procs in opposite order of binding
	for (i = evtreg->bindslots, bind = evtreg->binds + i - 1;
			!bHandled && i > 0;
			--i, --bind)
	{
		if (bind->proc)
			ret = bind->proc (event, iparam, pparam, &bHandled);
	}
	return ret;
}
