#include "sdk/amxxmodule.h"

#include "hamsandwich.h"

#include "VTableManager.h"
#include "VTableEntries.h"

#include "NEW_Util.h"

VTableManager VTMan;

NATIVEFUNC VTableManager::RegisterNatives[HAM_END_DONT_USE_ME];
NATIVEFUNC VTableManager::RegisterIDNatives[HAM_END_DONT_USE_ME];
const char *VTableManager::RegisterNames[HAM_END_DONT_USE_ME];

void RegisterThisRegister(int index,NATIVEFUNC byname, NATIVEFUNC byid)
{
	VTableManager::RegisterNatives[index]=byname;
	VTableManager::RegisterIDNatives[index]=byid;
}
void RegisterThisRegisterName(int index, const char *name)
{
	VTableManager::RegisterNames[index]=name;
}

static AMX_NATIVE_INFO registernatives[] = {
	{ "ham_register",			VTableManager::Register },
	{ "ham_registerid",			VTableManager::RegisterID },

	{ NULL,						NULL }
};
void RegisterRegisterNatives(void)
{
	MF_AddNatives(registernatives);
}
cell VTableManager::Register(AMX *amx, cell *params)
{
	int id=params[1];

	if (id<0 || id>=HAM_END_DONT_USE_ME || RegisterIDNatives[id]==NULL)
	{
		// this register is not found, fail the plugin
		int fwd=MF_RegisterSPForwardByName(amx,"__fatal_ham_error",FP_STRING,FP_DONE);
		char error[256];

		snprintf(&error[0],sizeof(error)-1,"Requested to ham_registerid a function ID that is not registered in configs/hamdata.ini, cannot continue. (Requested: %d)",id);

		MF_ExecuteForward(fwd,&error[0]);

		MF_UnregisterSPForward(fwd);
		return 0;

	}

	cell tempparams[4];

	// remove one parameter from this param count
	tempparams[0]=(params[0]-(sizeof(cell)));
	tempparams[1]=params[2];
	tempparams[2]=params[3];
	tempparams[3]=params[4];

	return RegisterNatives[id](amx,&tempparams[0]);
}
cell VTableManager::RegisterID(AMX *amx, cell *params)
{
	int id=params[1];

	if (id<0 || id>=HAM_END_DONT_USE_ME || RegisterNatives[id]==NULL)
	{
		// this register is not found, fail the plugin
		int fwd=MF_RegisterSPForwardByName(amx,"__fatal_ham_error",FP_STRING,FP_DONE);

		char error[256];

		snprintf(&error[0],sizeof(error)-1,"Requested to ham_register a function ID that is not registered in configs/hamdata.ini, cannot continue. (Requested: %d)",id);
		
		MF_ExecuteForward(fwd,&error[0]);

		MF_UnregisterSPForward(fwd);
		return 0;

	}

	cell tempparams[4];

	// remove one parameter from this param count
	tempparams[0]=(params[0]-(sizeof(cell)));
	tempparams[1]=params[2];
	tempparams[2]=params[3];
	tempparams[3]=params[4];

	return RegisterIDNatives[id](amx,&tempparams[0]);
}


void *VTableManager::InsertIntoVTable(void **vtable, int index, void *trampoline)
{
	void *func;
#if defined _WIN32
	DWORD OldFlags;
	VirtualProtect(&vtable[index],sizeof(int*),PAGE_READWRITE,&OldFlags);
#elif defined __linux__
	mprotect(&vtable[index],sizeof(int*),PROT_READ|PROT_WRITE);
#endif
	func=vtable[index];
	vtable[index]=trampoline;

	return func;
};


#define CLEAR_ENTRIES(Container)		\
	i=Container.size();					\
	while (i--)							\
	{									\
		Container[i]->Destroy();		\
		delete Container[i];			\
	}									\
	Container.clear()


void VTableManager::Cleanup(void)
{
	int i;

	CLEAR_ENTRIES(UseEntries);
	CLEAR_ENTRIES(TakeDamageEntries);
};



void VTableEntryBase::CreateGenericTrampoline(VTableManager *manager, void **vtable, int vtid, int id, void **outtrampoline, void **origfunc, void *callee, int paramcount, int voidcall, int thiscall)
{
	Trampolines::TrampolineMaker tramp;

	if (voidcall)
	{
		if (thiscall)
		{
			tramp.ThisVoidPrologue();
		}
		else
		{
			tramp.VoidPrologue();
		}
	}
	else
	{
		if (thiscall)
		{
			tramp.ThisReturnPrologue();
		}
		else
		{
			tramp.ReturnPrologue();
		}
	}

	while (paramcount)
	{
		tramp.PushParam(paramcount--);
	}
	if (thiscall)
	{
		tramp.PushThis();
	}
	tramp.PushNum(id);
	tramp.Call(callee);
	tramp.FreeTargetStack();
	if (voidcall)
	{
#if defined _WIN32
		tramp.VoidEpilogueAndFree();
#elif defined __linux__
		tramp.VoidEpilogue();
#endif
	}
	else
	{
#if defined _WIN32
		tramp.ReturnEpilogueAndFree();
#elif defined __linux__
		tramp.ReturnEpilogue();
#endif
	}

	void *trampoline=tramp.Finish(NULL);

	*outtrampoline=trampoline;

	*origfunc=manager->InsertIntoVTable(vtable,vtid,trampoline);
};