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

namespace EventManager {

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

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
};

#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("loadgame", OneString),
	EVENT_INFO("savegame", OneString),
	{ "exitgame", NULL, 0, NULL },
	{ "mainmenu", NULL, 0, NULL },
	{ "qqq", NULL, 0, NULL }	
};

#undef EVENT_INFO

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

// don't install hook unless at least one handler is registered
static bool s_bHookInstalled = false;
static const UInt32 kMarkEvent_HookAddr = 0x004FBF90;
static const UInt32 kMarkEvent_RetnAddr = 0x004FBF96;
#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;
#else
#error unsupported Oblivion version
#endif

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
	}

	HandleGameEvent(eventMask, thisObj, 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;
	}
}

eEventID EventIDForMask(UInt32 eventMask)
{
	switch (eventMask) {
		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;
		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();
		}

		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) {
					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 InternalFunctionCaller : public FunctionCaller
{
public:
	InternalFunctionCaller(Script* script, EventInfo* eventInfo, void* arg0, void* arg1)
		: m_callerVersion(1), m_script(script), m_eventInfo(eventInfo) {
			m_args[0] = arg0;
			m_args[1] = arg1;
	}

	virtual ~InternalFunctionCaller() { }

	virtual UInt8 ReadCallerVersion() {
		return m_callerVersion;
	}

	virtual Script * ReadScript() {
		return m_script;
	}

	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;
		}

		// populate the args in the event list
		for (UInt32 i = 0; i < m_eventInfo->numParams; i++) {
			// make sure arg type defined in func script matches expected type
			UserFunctionParam* param = info->GetParam(i);
			if (param->varType != m_eventInfo->paramTypes[i]) {
				ShowRuntimeError(m_script, "Argument types for function script do not match those expected for event.");
				return false;
			}

			ScriptEventList::Var* var = eventList->GetVariable(param->varIdx);
			if (!var) {
				ShowRuntimeError(m_script, "Could not look up argument variable for event handler script");
				return false;
			}

			switch (param->varType) {
				case Script::eVarType_Integer:
					var->data = (SInt32)m_args[i];
					break;
				case Script::eVarType_Float:
					var->data = *((float*)&m_args[i]);
					break;
				case Script::eVarType_Ref:
					{
						TESForm* form = (TESForm*)m_args[i];
						*((UInt32*)&var->data) = form ? form->refID : 0;
					}
					break;
				case Script::eVarType_String:
					var->data = g_StringMap.Add(info->GetScript()->GetModIndex(), (const char*)m_args[i], true);
					break;
				default:
					ShowRuntimeError(m_script, "Unexpected param type in event handler function call");
					return false;
			}
		}
	
		return true;
	}

	virtual TESObjectREFR* ThisObj() { return NULL; }
	virtual TESObjectREFR* ContainingObj() { return NULL; }
private:
	UInt8		m_callerVersion;
	Script		* m_script;
	EventInfo	* m_eventInfo;
	void		* m_args[2];
}; 

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

			ScriptToken* result = UserFunctionManager::Call(InternalFunctionCaller(iter->script, eventInfo, arg0, arg1));
			// result is unused
			delete result;
		}
	}
}

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

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) {
					iter = callbacks->erase(iter);
					bRemovedAtLeastOne = true;
				}
				else {
					++iter;
				}
			}
		}
	}
	
	return bRemovedAtLeastOne;
}

void HandleGameEvent(UInt32 eventMask, TESObjectREFR* source, TESForm* object)
{
	// ### HACK HACK HACK
	// MarkEventList() may have been called for a BaseExtraList not associated with a TESObjectREFR
	// we are (currently) only interested in Actors
	bool bIsActor = false;
	try {
		switch (*((UInt32*)source)) {
			case kVtbl_PlayerCharacter:
			case kVtbl_Character:
			case kVtbl_Creature:
				bIsActor = true;
		}
	}
	catch (...) {
		//
	}

	if (!bIsActor)
		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

