#include "smsdk_ext.h"
#include "extension.h"
#include "vdecoder.h"

using namespace SourceMod;
using namespace SourcePawn;

/**
 * For object pointers, the data looks like this instead:
 * 4 bytes: POINTER TO NEXT FOUR BYTES
 * + bytes: Object internal data
 *
 * We use the virtual stack as extra fake stack space and create a temp object.
 * If these objects had destructors, we'd need to fake destroy toom of course.
 * Of course, BinTools only reads the first four bytes and passes the pointer.
 */

size_t ValveParamToBinParam(ValveType type, 
						  PassType pass,
						  unsigned int flags,
						  PassInfo *info)
{
	switch (type)
	{
	case Valve_Vector:
		{
			size_t mySize = sizeof(Vector *);
			if (pass == PassType_Basic)
			{
				if (info->flags & PASSFLAG_BYREF)
				{
					return 0;
				}
				info->type = PassType_Basic;
				info->flags = flags;
				info->size = sizeof(Vector *);
				mySize += sizeof(Vector);
			} else if (pass == PassType_Object) {
				info->type = PassType_Object;
				info->flags = flags | PASSFLAG_OASSIGNOP | PASSFLAG_OCTOR;
				info->size = sizeof(Vector);
			} else {
				return 0;
			}
			return mySize;
		}
	case Valve_QAngle:
		{
			size_t mySize = sizeof(QAngle *);
			if (pass == PassType_Basic)
			{
				if (info->flags & PASSFLAG_BYREF)
				{
					return 0;
				}
				info->type = PassType_Basic;
				info->flags = flags;
				info->size = sizeof(QAngle *);
				mySize += sizeof(QAngle);
			} else if (pass == PassType_Object) {
				info->type = PassType_Object;
				info->flags = flags | PASSFLAG_OASSIGNOP | PASSFLAG_OCTOR;
				info->size = sizeof(QAngle);
			} else {
				return 0;
			}
			return mySize;
		}
	case Valve_CBaseEntity:
	case Valve_CBasePlayer:
	case Valve_Edict:
	case Valve_String:
		{
			if (pass != PassType_Basic || (info->flags & PASSFLAG_BYREF))
			{
				return 0;
			}
			info->type = PassType_Basic;
			info->flags = flags;
			info->size = sizeof(void *);
			return sizeof(void *);
		}
	case Valve_POD:
		{
			info->type = PassType_Basic;
			info->flags = flags;
			if (flags & PASSFLAG_ASPOINTER)
			{
				info->size = sizeof(int *);
				return sizeof(int *) + sizeof(int);
			} else {
				info->size = sizeof(int);
				return sizeof(int);
			}
		}
	case Valve_Bool:
		{
			info->type = PassType_Basic;
			info->flags = flags;
			if (flags & PASSFLAG_ASPOINTER)
			{
				info->size = sizeof(bool *);
				return sizeof(bool *) + sizeof(bool);
			} else {
				info->size = sizeof(bool);
				return sizeof(bool);
			}
		}
	case Valve_Float:
		{
			info->type = PassType_Float;
			info->flags = flags;
			if (flags & PASSFLAG_ASPOINTER)
			{
				info->size = sizeof(float *);
				return sizeof(float *) + sizeof(float);
			} else {
				info->size = sizeof(float);
				return sizeof(float);
			}
		}
	}

	return 0;
}

DataStatus EncodeValveParam(IPluginContext *pContext,
							cell_t param, 
							const ValvePassInfo *data,
							const void *buffer)
{
	switch (data->vtype)
	{
	case Valve_Vector:
		{
			Vector *v = NULL;

			if (data->type == PassType_Basic)
			{
				v = *(Vector **)((unsigned char *)buffer + sizeof(Vector *));
			} else if (data->type == PassType_Object) {
				v = (Vector *)buffer;
			}

			cell_t *addr;
			pContext->LocalToPhysAddr(param, &addr);

			addr[0] = sp_ftoc(v->x);
			addr[1] = sp_ftoc(v->y);
			addr[2] = sp_ftoc(v->z);

			return Data_Okay;
		}
	case Valve_QAngle:
		{
			QAngle *q = NULL;

			if (data->type == PassType_Basic)
			{
				q = *(QAngle **)((unsigned char *)buffer + sizeof(QAngle *));
			} else if (data->type == PassType_Object) {
				q = (QAngle *)buffer;
			}

			cell_t *addr;
			pContext->LocalToPhysAddr(param, &addr);

			addr[0] = sp_ftoc(q->x);
			addr[1] = sp_ftoc(q->y);
			addr[2] = sp_ftoc(q->z);

			return Data_Okay;
		}
	case Valve_CBaseEntity:
	case Valve_CBasePlayer:
		{
			cell_t *addr;
			pContext->LocalToPhysAddr(param, &addr);

			CBaseEntity *pEntity = *(CBaseEntity **)buffer;
			if (pEntity)
			{
				edict_t *pEdict = gameents->BaseEntityToEdict(pEntity);
				*addr = engine->IndexOfEdict(pEdict);
			} else {
				*addr = -1;
			}

			return Data_Okay;
		}
	case Valve_Edict:
		{
			cell_t *addr;
			pContext->LocalToPhysAddr(param, &addr);

			edict_t *pEdict = *(edict_t **)buffer;
			if (pEdict)
			{
				*addr = engine->IndexOfEdict(pEdict);
			} else {
				*addr = -1;
			}

			return Data_Okay;
		}
	case Valve_POD:
	case Valve_Float:
		{
			cell_t *addr;
			pContext->LocalToPhysAddr(param, &addr);

			if (data->flags & PASSFLAG_ASPOINTER)
			{
				buffer = (char *)buffer + sizeof(void *);
			}

			*addr = *(cell_t *)buffer;

			return Data_Okay;
		}
	case Valve_Bool:
		{
			cell_t *addr;
			pContext->LocalToPhysAddr(param, &addr);

			if (data->flags & PASSFLAG_ASPOINTER)
			{
				buffer = (char *)buffer + sizeof(bool *);
			}

			*addr = *(bool *)buffer ? 1 : 0;

			return Data_Okay;
		}
	}

	return Data_Fail;
}

DataStatus DecodeValveParam(IPluginContext *pContext,
					  cell_t param,
					  const ValvePassInfo *data,
					  void *buffer)
{
	switch (data->vtype)
	{
	case Valve_Vector:
		{
			cell_t *addr;
			int err;
			err = pContext->LocalToPhysAddr(param, &addr);

			unsigned char *mem = (unsigned char *)buffer;
			if (data->type == PassType_Basic)
			{
				/* Store the object in the next N bytes, and store
				 * a pointer to that object right beforehand.
				 */
				Vector **realPtr = (Vector **)buffer;

				if (addr == pContext->GetNullRef(SP_NULL_VECTOR))
				{
					if (data->decflags & VDECODE_FLAG_ALLOWNULL)
					{
						*realPtr = NULL;
						return Data_Okay;
					} else {
						pContext->ThrowNativeError("NULL not allowed");
						return Data_Fail;
					}
				} else {
					mem += sizeof(Vector *);
					*realPtr = (Vector *)mem;
				}
			}

			if (err != SP_ERROR_NONE)
			{
				pContext->ThrowNativeErrorEx(err, "Could not read plugin data");
				return Data_Fail;
			}

			/* Use placement new to initialize the object cleanly
			 * This has no destructor so we don't need to do 
			 * DestroyValveParam() or something :]
			 */
			Vector *v = new (mem) Vector(
				sp_ctof(addr[0]),
				sp_ctof(addr[1]),
				sp_ctof(addr[2]));

			return Data_Okay;
		}
	case Valve_QAngle:
		{
			cell_t *addr;
			int err;
			err = pContext->LocalToPhysAddr(param, &addr);

			unsigned char *mem = (unsigned char *)buffer;
			if (data->type == PassType_Basic)
			{
				/* Store the object in the next N bytes, and store
				 * a pointer to that object right beforehand.
				 */
				QAngle **realPtr = (QAngle **)buffer;

				if (addr == pContext->GetNullRef(SP_NULL_VECTOR))
				{
					if (!(data->decflags & VDECODE_FLAG_ALLOWNULL))
					{
						pContext->ThrowNativeError("NULL not allowed");
						return Data_Fail;
					} else {
						*realPtr = NULL;
						return Data_Okay;
					}
				} else {
					mem += sizeof(QAngle *);
					*realPtr = (QAngle *)mem;
				}
			}

			if (err != SP_ERROR_NONE)
			{
				pContext->ThrowNativeErrorEx(err, "Could not read plugin data");
				return Data_Fail;
			}

			/* Use placement new to initialize the object cleanly
			 * This has no destructor so we don't need to do 
			 * DestroyValveParam() or something :]
			 */
			QAngle *v = new (mem) QAngle(
				sp_ctof(addr[0]),
				sp_ctof(addr[1]),
				sp_ctof(addr[2]));

			return Data_Okay;
		}
	case Valve_CBasePlayer:
		{
			edict_t *pEdict;
			if (data->decflags & VDECODE_FLAG_BYREF)
			{
				cell_t *addr;
				pContext->LocalToPhysAddr(param, &addr);
				param = *addr;
			}
			if (param >= 1 && param <= playerhelpers->GetMaxClients())
			{
				IGamePlayer *player = playerhelpers->GetGamePlayer(param);
				if ((data->decflags & VDECODE_FLAG_ALLOWNOTINGAME)
					&& !player->IsConnected())
				{
					pContext->ThrowNativeError("Client %d is not connected", param);
					return Data_Fail;
				} else if (!player->IsInGame()) {
					pContext->ThrowNativeError("Client %d is not in game", param);
					return Data_Fail;
				}
				pEdict = player->GetEdict();
			} else if (param == -1) {
				if (data->decflags & VDECODE_FLAG_ALLOWNULL)
				{
					pEdict = NULL;
				} else {
					pContext->ThrowNativeError("NULL not allowed");
					return Data_Fail;
				}
			} else if (param == 0) {
				if (data->decflags & VDECODE_FLAG_ALLOWWORLD)
				{
					pEdict = engine->PEntityOfEntIndex(0);
				} else {
					pContext->ThrowNativeError("World not allowed");
					return Data_Fail;
				}
			} else {
				pContext->ThrowNativeError("Entity index %d is not a valid client", param);
				return Data_Fail;
			}
			CBaseEntity *pEntity = NULL;
			if (pEdict)
			{
				IServerUnknown *pUnknown = pEdict->GetUnknown();
				if (!pUnknown)
				{
					pContext->ThrowNativeError("Entity %d is a not an IServerUnknown");
					return Data_Fail;
				}
				pEntity = pUnknown->GetBaseEntity();
				if (!pEntity)
				{
					pContext->ThrowNativeError("Entity %d is not a CBaseEntity");
					return Data_Fail;
				}
			}

			CBaseEntity **ebuf = (CBaseEntity **)buffer;
			*ebuf = pEntity;

			return Data_Okay;
		}
	case Valve_CBaseEntity:
		{
			edict_t *pEdict;
			if (data->decflags & VDECODE_FLAG_BYREF)
			{
				cell_t *addr;
				pContext->LocalToPhysAddr(param, &addr);
				param = *addr;
			}
			if (param >= 1 && param <= playerhelpers->GetMaxClients())
			{
				IGamePlayer *player = playerhelpers->GetGamePlayer(param);
				if ((data->decflags & VDECODE_FLAG_ALLOWNOTINGAME)
					&& !player->IsConnected())
				{
					pContext->ThrowNativeError("Client %d is not connected", param);
					return Data_Fail;
				} else if (!player->IsInGame()) {
					pContext->ThrowNativeError("Client %d is not in game", param);
					return Data_Fail;
				}
				pEdict = player->GetEdict();
			} else if (param == -1) {
				if (data->decflags & VDECODE_FLAG_ALLOWNULL)
				{
					pEdict = NULL;
				} else {
					pContext->ThrowNativeError("NULL not allowed");
					return Data_Fail;
				}
			} else if (param == 0) {
				if (data->decflags & VDECODE_FLAG_ALLOWWORLD)
				{
					pEdict = engine->PEntityOfEntIndex(0);
				} else {
					pContext->ThrowNativeError("World not allowed");
					return Data_Fail;
				}
			} else {
				pEdict = engine->PEntityOfEntIndex(param);
				if (!pEdict || pEdict->IsFree())
				{
					pContext->ThrowNativeError("Entity %d is not valid or is freed", param);
					return Data_Fail;
				}
			}
			CBaseEntity *pEntity = NULL;
			if (pEdict)
			{
				IServerUnknown *pUnknown = pEdict->GetUnknown();
				if (!pUnknown)
				{
					pContext->ThrowNativeError("Entity %d is a not an IServerUnknown");
					return Data_Fail;
				}
				pEntity = pUnknown->GetBaseEntity();
				if (!pEntity)
				{
					pContext->ThrowNativeError("Entity %d is not a CBaseEntity");
					return Data_Fail;
				}
			}

			CBaseEntity **ebuf = (CBaseEntity **)buffer;
			*ebuf = pEntity;

			return Data_Okay;
		}
	case Valve_Edict:
		{
			edict_t *pEdict;
			if (data->decflags & VDECODE_FLAG_BYREF)
			{
				cell_t *addr;
				pContext->LocalToPhysAddr(param, &addr);
				param = *addr;
			}
			if (param >= 1 && param <= playerhelpers->GetMaxClients())
			{
				IGamePlayer *player = playerhelpers->GetGamePlayer(param);
				if ((data->decflags & VDECODE_FLAG_ALLOWNOTINGAME)
					&& !player->IsConnected())
				{
					pContext->ThrowNativeError("Client %d is not connected", param);
					return Data_Fail;
				} else if (!player->IsInGame()) {
					pContext->ThrowNativeError("Client %d is not in game", param);
					return Data_Fail;
				}
				pEdict = player->GetEdict();
			} else if (param == -1) {
				if (data->decflags & VDECODE_FLAG_ALLOWNULL)
				{
					pEdict = NULL;
				} else {
					pContext->ThrowNativeError("NULL not allowed");
					return Data_Fail;
				}
			} else if (param == 0) {
				if (data->decflags & VDECODE_FLAG_ALLOWWORLD)
				{
					pEdict = engine->PEntityOfEntIndex(0);
				} else {
					pContext->ThrowNativeError("World not allowed");
					return Data_Fail;
				}
			} else {
				pEdict = engine->PEntityOfEntIndex(param);
				if (!pEdict || pEdict->IsFree())
				{
					pContext->ThrowNativeError("Entity %d is not valid or is freed", param);
					return Data_Fail;
				}
			}

			edict_t **ebuf = (edict_t **)buffer;
			*ebuf = pEdict;

			return Data_Okay;
		}
	case Valve_POD:
	case Valve_Float:
		{
			if (data->decflags & VDECODE_FLAG_BYREF)
			{
				cell_t *addr;
				pContext->LocalToPhysAddr(param, &addr);
				param = *addr;
			}
			if (data->flags & PASSFLAG_ASPOINTER)
			{
				*(void **)buffer = (char *)buffer + sizeof(void *);
				buffer = *(void **)buffer;
			}
			*(cell_t *)buffer = param;
			return Data_Okay;
		}
	case Valve_Bool:
		{
			if (data->decflags & VDECODE_FLAG_BYREF)
			{
				cell_t *addr;
				pContext->LocalToPhysAddr(param, &addr);
				param = *addr;
			}
			if (data->flags & PASSFLAG_ASPOINTER)
			{
				*(bool **)buffer = (bool *)((char *)buffer + sizeof(bool *));
				buffer = *(bool **)buffer;
			}
			*(bool *)buffer = param ? true : false;
			return Data_Okay;
		}
	case Valve_String:
		{
			char *addr;
			pContext->LocalToString(param, &addr);
			*(char **)buffer = addr;
			return Data_Okay;
		}
	}

	return Data_Fail;
}