#include <list>
#include <stdarg.h>
#include "EventManager.h"
#include "ArrayVar.h"
#include "PluginAPI.h"
#include "GameAPI.h"
#include "Utilities.h"
#include "ScriptUtils.h"
#include "obse_common/SafeWrite.h"
#include "FunctionScripts.h"

namespace EventManager {

//////////////////////
// Event definitions
/////////////////////

enum {
	kEventMask_OnActivate = 0xFFFFFFFF,		// special case as OnActivate has no event mask
};

struct EventInfo
{
	~EventInfo();

	const char					* name;
	UInt8						* paramTypes;
	UInt8						numParams;
	std::list<EventCallback>	* callbacks;
};

static UInt8 kEventParams_GameEvent[2] =
{
	Script::eVarType_Ref, Script::eVarType_Ref
};

static UInt8 kEventParams_OneRef[1] =
{
	Script::eVarType_Ref,
};

static UInt8 kEventParams_GameMGEFEvent[2] =
{
	// MGEF gets converted to effect code when passed to scripts
	Script::eVarType_Ref, Script::eVarType_Integer
};

static UInt8 kEventParams_OneString[1] =
{
	Script::eVarType_String
};

static UInt8 kEventParams_OneInteger[1] =
{
	Script::eVarType_Integer
};

#define EVENT_INFO(name, params) { name, kEventParams_ ## params, sizeof(kEventParams_ ## params), NULL }

static EventInfo	s_eventInfos[kEventID_MAX] =
{
	EVENT_INFO("onhit", GameEvent),
	EVENT_INFO("onhitwith", GameEvent),
	EVENT_INFO("onmagiceffecthit", GameMGEFEvent),
	EVENT_INFO("onactorequip", GameEvent),
	EVENT_INFO("ondeath", GameEvent),
	EVENT_INFO("onmurder", GameEvent),
	EVENT_INFO("onknockout", OneRef),
	EVENT_INFO("onactorunequip", GameEvent),
	EVENT_INFO("onalarm trespass", GameEvent),
	EVENT_INFO("onalarm steal", GameEvent),
	EVENT_INFO("onalarm attack", GameEvent),
	EVENT_INFO("onalarm pickpocket", GameEvent),
	EVENT_INFO("onalarm murder", GameEvent),
	EVENT_INFO("onpackagechange", GameEvent),
	EVENT_INFO("onpackagestart", GameEvent),
	EVENT_INFO("onpackagedone", GameEvent),
	EVENT_INFO("onstartcombat", GameEvent),
	EVENT_INFO("onactivate", GameEvent),

	EVENT_INFO("loadgame", OneString),
	EVENT_INFO("savegame", OneString),
	{ "exitgame", NULL, 0, NULL },
	{ "mainmenu", NULL, 0, NULL },
	{ "qqq", NULL, 0, NULL },
	EVENT_INFO("postloadgame", OneInteger),
};

#undef EVENT_INFO

///////////////////////////
// internal functions
//////////////////////////

// don't install hook unless at least one handler is registered
static bool s_bHookInstalled = false;
static bool s_bActivateHookInstalled = false;	// hook for TESObjectREFR::Activate()

static const UInt32 kMarkEvent_HookAddr = 0x004FBF90;
static const UInt32 kMarkEvent_RetnAddr = 0x004FBF96;

static const UInt32 kActivate_HookAddr = 0x004DD286;
static const UInt32 kActivate_RetnAddr = 0x004DD28C;

#if OBLIVION_VERSION == OBLIVION_VERSION_1_2_416
static const UInt32 kVtbl_PlayerCharacter = 0x00A73A0C;
static const UInt32 kVtbl_Character = 0x00A6FC9C;
static const UInt32 kVtbl_Creature = 0x00A710F4;
static const UInt32 kVtbl_ArrowProjectile = 0x00A6F08C;
static const UInt32 kVtbl_MagicBallProjectile = 0x00A75944;
static const UInt32 kVtbl_MagicBoltProjectile = 0x00A75BC4;
static const UInt32 kVtbl_MagicFogProjectile = 0x00A75EFC;
static const UInt32 kVtbl_MagicSprayProjectile = 0x00A76594;
static const UInt32 kVtbl_TESObjectREFR = 0x00A46C44;
#else
#error unsupported Oblivion version
#endif


// cheap check to prevent duplicate events being processed in immediate succession
// (e.g. game marks OnHitWith twice per event, this way we only process it once)
static TESObjectREFR* s_lastObj = NULL;
static TESForm* s_lastTarget = NULL;
static UInt32 s_lastEvent = NULL;

void Tick()
{
	s_lastObj = NULL;
	s_lastTarget = NULL;
	s_lastEvent = NULL;
}

static __declspec(naked) void MarkEventHook(void)
{
	static TESObjectREFR* thisObj = NULL;
	static TESForm* target = NULL;
	static UInt32 eventMask = NULL;

	__asm {
		// grab args
		mov	eax, [esp+8]			// ExtraDataList*
		test eax, eax
		jnz	XDataListIsNotNull

		push ebx
		push esi
		mov esi, 0
		jmp [kMarkEvent_RetnAddr]

	XDataListIsNotNull:
		sub eax, 0x44				// TESObjectREFR*
		mov [thisObj], eax
		mov eax, [esp+0xC]			// event mask
		mov [eventMask], eax
		mov eax, [esp+4]			// target
		mov [target], eax

		pushad
	}

	if (thisObj != s_lastObj || target != s_lastTarget || eventMask != s_lastEvent) {
		HandleGameEvent(eventMask, thisObj, target);
		s_lastObj = thisObj;
		s_lastEvent = eventMask;
		s_lastTarget = target;
	}

	__asm {
		popad

		// overwritten code
		push ebx
		push esi
		mov esi, [thisObj]
		add esi, 0x44

		jmp [kMarkEvent_RetnAddr]
	}
}		

void InstallHook()
{
	if (!s_bHookInstalled) {
		WriteRelJump(kMarkEvent_HookAddr, (UInt32)&MarkEventHook);
		s_bHookInstalled = true;
	}
}

static __declspec(naked) void TESObjectREFR_ActivateHook(void)
{
	__asm {
		pushad
		push edi		// activating refr
		push ecx		// this
		mov eax, kEventMask_OnActivate
		push eax
		call HandleGameEvent
		popad

		// overwritten code
		xor bl, bl
		test edi, edi
		mov esi, ecx
		jmp	[kActivate_RetnAddr]
	}
}

void InstallActivateHook()
{
	WriteRelJump(kActivate_HookAddr, (UInt32)&TESObjectREFR_ActivateHook);
	s_bActivateHookInstalled = true;
}

eEventID EventIDForMask(UInt32 eventMask)
{
	switch (eventMask) {
		case kEventMask_OnActivate:
			return kEventID_OnActivate;
		case ScriptEventList::kEvent_OnHit:
			return kEventID_OnHit;
		case ScriptEventList::kEvent_OnHitWith:
			return kEventID_OnHitWith;
		case ScriptEventList::kEvent_OnMagicEffectHit:
			return kEventID_OnMagicEffectHit;
		case ScriptEventList::kEvent_OnActorEquip:
			return kEventID_OnActorEquip;
		case ScriptEventList::kEvent_OnDeath:
			return kEventID_OnDeath;
		case ScriptEventList::kEvent_OnMurder:
			return kEventID_OnMurder;
		case ScriptEventList::kEvent_OnKnockout:
			return kEventID_OnKnockout;
		case ScriptEventList::kEvent_OnActorUnequip:
				return kEventID_OnActorUnequip;
		case ScriptEventList::kEvent_OnAlarm_Trespass:
			return kEventID_OnAlarm_Trespass;
		case ScriptEventList::kEvent_OnAlarm_Steal:
			return kEventID_OnAlarm_Steal;
		case ScriptEventList::kEvent_OnAlarm_Attack:
			return kEventID_OnAlarm_Attack;
		case ScriptEventList::kEvent_OnAlarm_Pickpocket:
			return kEventID_OnAlarm_Pickpocket;
		case ScriptEventList::kEvent_OnAlarm_Murder:
			return kEventID_OnAlarm_Murder;
		case ScriptEventList::kEvent_OnPackageStart:
			return kEventID_OnPackageStart;
		case ScriptEventList::kEvent_OnPackageDone:
			return kEventID_OnPackageDone;
		case ScriptEventList::kEvent_OnPackageChange:
			return kEventID_OnPackageChange;
		case ScriptEventList::kEvent_OnStartCombat:
			return kEventID_OnStartCombat;
		default:
			return kEventID_MAX;
	}
}

eEventID EventIDForMessage(UInt32 msgID)
{
	switch (msgID) {
		case OBSEMessagingInterface::kMessage_LoadGame:
			return kEventID_LoadGame;
		case OBSEMessagingInterface::kMessage_SaveGame:
			return kEventID_SaveGame;
		case OBSEMessagingInterface::kMessage_ExitGame:
			return kEventID_ExitGame;
		case OBSEMessagingInterface::kMessage_ExitGame_Console:
			return kEventID_QQQ;
		case OBSEMessagingInterface::kMessage_ExitToMainMenu:
			return kEventID_ExitToMainMenu;
		case OBSEMessagingInterface::kMessage_PostLoadGame:
			return kEventID_PostLoadGame;
		default:
			return kEventID_MAX;
	}
}

eEventID EventIDForString(const char* eventStr)
{
	std::string name(eventStr);
	MakeLower(name);
	eventStr = name.c_str();
	for (UInt32 i = 0; i < kEventID_MAX; i++) {
		if (!strcmp(eventStr, s_eventInfos[i].name)) {
			return (eEventID)i;
		}
	}

	return kEventID_MAX;
}

typedef std::list<EventCallback>	CallbackList;

bool SetHandler(eEventID id, EventCallback& handler)
{
	if (id < kEventID_MAX) {
		if (id < kEventID_GameEventMAX) {
			// it's a game event handler, so we need to enable the hook
			if (!s_bHookInstalled)
				InstallHook();
		}

		// OnActivate requires its own hook
		if (id == kEventID_OnActivate && !s_bActivateHookInstalled) {
			InstallActivateHook();
		}

		EventInfo* info = &s_eventInfos[id];
		if (!info->callbacks) {
			info->callbacks = new CallbackList();
		}
		else {
			// if an existing handler matches this one exactly, don't duplicate it
			for (CallbackList::iterator iter = info->callbacks->begin(); iter != info->callbacks->end(); ++iter) {
				if (iter->script == handler.script && iter->object == handler.object && iter->source == handler.source) {
					// may be re-adding a previously removed handler, so clear the Removed flag
					iter->SetRemoved(false);
					return false;
				}
			}
		}

		info->callbacks->push_back(handler);
		return true;
	}
	else {
		return false;
	}
}

bool RemoveHandler(eEventID id, EventCallback& handler);
bool RemoveHandler(eEventID id, Script* fnScript);

class EventHandlerCaller : public InternalFunctionCaller
{
public:
	EventHandlerCaller(Script* script, EventInfo* eventInfo, void* arg0, void* arg1)
		: InternalFunctionCaller(script), m_eventInfo(eventInfo)
	{
		UInt8 numArgs = 2;
		if (!arg1)
			numArgs = 1;
		if (!arg0)
			numArgs = 0;

		SetArgs(numArgs, arg0, arg1);
	}

	virtual bool ValidateParam(UserFunctionParam* param, UInt8 paramIndex)
	{
		return param->varType == m_eventInfo->paramTypes[paramIndex];
	}

	virtual bool PopulateArgs(ScriptEventList* eventList, FunctionInfo* info) {
		// make sure we've got the same # of args as expected by event handler
		DynamicParamInfo& dParams = info->ParamInfo();
		if (dParams.NumParams() != m_eventInfo->numParams || dParams.NumParams() > 2) {
			ShowRuntimeError(m_script, "Number of arguments to function script does not match those expected for event");
			return false;
		}

		return InternalFunctionCaller::PopulateArgs(eventList, info);
	}

private:
	EventInfo		* m_eventInfo;
};

// stack of event names pushed when handler invoked, popped when handler returns
// used by GetCurrentEventName

std::stack<const char*> s_eventStack;

void HandleEvent(eEventID id, void * arg0, void * arg1)
{
	EventInfo* eventInfo = &s_eventInfos[id];
	if (eventInfo->callbacks) {
		for (CallbackList::iterator iter = eventInfo->callbacks->begin(); iter != eventInfo->callbacks->end(); ) {
			if (iter->IsRemoved()) {
				if (!iter->IsInUse()) {
					iter = eventInfo->callbacks->erase(iter);
				}
				else {
					++iter;
				}

				continue;
			}

			// Check filters
			if (id < kEventID_GameEventMAX) {
				if (iter->source && !((TESObjectREFR*)arg0 == iter->source)) {
					++iter;
					continue;
				}
				if (iter->object) {
					if (id == kEventID_OnMagicEffectHit) {
						EffectSetting* setting = OBLIVION_CAST(iter->object, TESForm, EffectSetting);
						if (setting && setting->effectCode != (UInt32)arg1) {
							++iter;
							continue;
						}
					}
					else if (!(iter->object == (TESForm*)arg1)) {
						++iter;
						continue;
					}
				}
			}

			iter->SetInUse(true);
			s_eventStack.push(eventInfo->name);
			ScriptToken* result = UserFunctionManager::Call(EventHandlerCaller(iter->script, eventInfo, arg0, arg1));
			s_eventStack.pop();
			iter->SetInUse(false);

			// it's possible the handler decided to remove itself, so take care of that
			if (iter->IsRemoved()) {
				iter = eventInfo->callbacks->erase(iter);
			}
			else {
				++iter;
			}

			// result is unused
			delete result;
		}
	}
}

////////////////
// public API
///////////////

const char* GetCurrentEventName()
{
	return s_eventStack.size() ? s_eventStack.top() : NULL;
}

bool SetHandler(const char* id, EventCallback& handler)
{
	return SetHandler(EventIDForString(id), handler);
}

bool RemoveHandler(const char* id, EventCallback& handler)
{
	eEventID eventType = EventIDForString(id);
	bool bRemovedAtLeastOne = false;
	if (eventType < kEventID_MAX && s_eventInfos[eventType].callbacks) {
		CallbackList* callbacks = s_eventInfos[eventType].callbacks;
		for (CallbackList::iterator iter = callbacks->begin(); iter != callbacks->end(); ) {
			if (iter->script == handler.script) {
				bool bMatches = true;
				if (handler.object && handler.object != iter->object) {
					bMatches = false;
				}
				else if (handler.source && handler.source != iter->source) {
					bMatches = false;
				}

				if (bMatches) {
					if (iter->IsInUse()) {
						// this handler is currently active, flag it for later removal
						iter->SetRemoved(true);
						++iter;
					}
					else {
						iter = callbacks->erase(iter);
					}

					bRemovedAtLeastOne = true;
				}
				else {
					++iter;
				}
			}
		}
	}
	
	return bRemovedAtLeastOne;
}

void __stdcall HandleGameEvent(UInt32 eventMask, TESObjectREFR* source, TESForm* object)
{
	// ### HACK HACK HACK
	// MarkEventList() may have been called for a BaseExtraList not associated with a TESObjectREFR
	bool bIsRefr = false;
	try {
		switch (*((UInt32*)source)) {
			case kVtbl_PlayerCharacter:
			case kVtbl_Character:
			case kVtbl_Creature:
			case kVtbl_ArrowProjectile:
			case kVtbl_MagicBallProjectile:
			case kVtbl_MagicBoltProjectile:
			case kVtbl_MagicFogProjectile:
			case kVtbl_MagicSprayProjectile:
			case kVtbl_TESObjectREFR:
				bIsRefr = true;
		}
	}
	catch (...) {
		//
	}

	if (!bIsRefr)
		return;

	if (!s_bHookInstalled) {
		// we shouldn't ever get here
		InstallHook();
	}

	eEventID eventID = EventIDForMask(eventMask);
	if (eventID < kEventID_MAX) {
		// special-case OnMagicEffectHit
		if (eventID == kEventID_OnMagicEffectHit) {
			EffectSetting* setting = OBLIVION_CAST(object, TESForm, EffectSetting);
			HandleEvent(eventID, source, setting ? (void*)setting->effectCode : 0);
		}
		else
			HandleEvent(eventID, source, object);
	}
}

void HandleOBSEMessage(UInt32 msgID, void* data)
{
	eEventID eventID = EventIDForMessage(msgID);
	if (eventID < kEventID_MAX)
		HandleEvent(eventID, data, NULL);
}

EventInfo::~EventInfo()
{
	if (callbacks) {
		delete callbacks;
		callbacks = NULL;
	}
}

};	// namespace

