#pragma once

#include "skse64_common/Utilities.h"
#include "GameTypes.h"

template <typename T> class BSTEventSink;

enum EventResult
{
	kEvent_Continue = 0,
	kEvent_Abort
};

// 058 
template <typename EventT, typename EventArgT = EventT>
class EventDispatcher
{
	typedef BSTEventSink<EventT> SinkT;

	tArray<SinkT*>		eventSinks;			// 000
	tArray<SinkT*>		addBuffer;			// 018 - schedule for add
	tArray<SinkT*>		removeBuffer;		// 030 - schedule for remove
	SimpleLock			lock;				// 048
	bool				stateFlag;			// 050 - some internal state changed while sending
	char				pad[7];				// 051

	// Note: in SE there are multiple identical copies of all these functions 
	MEMBER_FN_PREFIX(EventDispatcher);
	// 66B1C7AC473D5EA48E4FD620BBFE0A06392C5885+66
	DEFINE_MEMBER_FN(AddEventSink_Internal, void, 0x0056A0A0, SinkT * eventSink);
	// ??_7BGSProcedureShoutExecState@@6B@ | +43
	DEFINE_MEMBER_FN(RemoveEventSink_Internal, void, 0x00423D20, SinkT * eventSink);
	// D6BA7CEC95B2C2B9C593A9AEE7F0ADFFB2C10E11+456
	DEFINE_MEMBER_FN(SendEvent_Internal, void, 0x00177EC0, EventArgT * evn);

public:

	EventDispatcher() : stateFlag(false) {}

	void AddEventSink(SinkT * eventSink)		{ CALL_MEMBER_FN(this,AddEventSink_Internal)(eventSink); }
	void RemoveEventSink(SinkT * eventSink)		{ CALL_MEMBER_FN(this,RemoveEventSink_Internal)(eventSink); }
	void SendEvent(EventArgT * evn)				{ CALL_MEMBER_FN(this,SendEvent_Internal)(evn); }
};
STATIC_ASSERT(sizeof(EventDispatcher<void*>) == 0x58);

// 08 
template <typename T>
class BSTEventSink
{
public:
	virtual ~BSTEventSink();
	virtual	EventResult	ReceiveEvent(T * evn, EventDispatcher<T> * dispatcher); // pure
//	void	** _vtbl;	// 00
};

// 58 
template <typename T>
class BSTEventSource
{
public:
	
	tArray<void*> unk00; // 08
	UnkArray	unk18;	// 18
	UnkArray	unk30;	// 30
	UInt32		unk48;	// 48
	UInt32		unk4C;	// 4C
	UInt8		unk50;	// 50
	UInt8		pad51[7];	// 51
};
STATIC_ASSERT(sizeof(BSTEventSource<void*>) == 0x58);

// 08 
struct TESSleepStartEvent
{
	float startTime;	// 00
	float endTime;		// 04
};

// 10 
struct MenuOpenCloseEvent
{
	BSFixedString	menuName;	// 00
	bool			opening;	// 08
	char			pad[7];
};

struct TESFurnitureEvent
{
};

// Todo
struct MenuModeChangeEvent
{
};

class TESObjectREFR;
class TESForm;
class ActiveEffect;

struct TESActiveEffectApplyRemoveEvent
{
	TESObjectREFR	* caster;	// 0
	TESObjectREFR	* target;	// 8
	UInt16			unk10;		// 10
	UInt8			unk12;		// 12	
};

struct TESQuestStageEvent
{
	void			* finishedCallback;	// 0
	UInt32			formId;	// 8
	UInt16			stage;	// C
	UInt8			unk0E;	// E
	UInt8			pad;	// F
};

// This isn't necessarily correct, just there to receive events
struct TESHarvestEvent
{
	struct ItemHarvested
	{
		// Unknown
	};
};

struct LevelIncrease
{
	struct Event
	{
		TESForm		* character;
		UInt32		level;
	};
};

struct SkillIncrease
{
	struct Event
	{
		// Unknown
	};
};
struct WordLearned
{
	struct Event
	{
		// Unknown
	};
};
struct WordUnlocked
{
	struct Event
	{
		// Unknown
	};
};
struct Inventory
{
	struct Event
	{
		// Unknown
	};
};
struct Bounty
{
	struct Event
	{
		// Unknown
	};
};
struct QuestStatus
{
	struct Event
	{
		// Unknown
	};
};
struct ObjectiveState
{
	struct Event
	{
		// Unknown
	};
};
struct Trespass
{
	struct Event
	{
		// Unknown
	};
};
struct FinePaid
{
	struct Event
	{
		// Unknown
	};
};
struct HoursPassed
{
	struct Event
	{
		// Unknown
	};
};
struct DaysPassed
{
	struct Event
	{
		// Unknown
	};
};
struct DaysJailed
{
	struct Event
	{
		// Unknown
	};
};
struct CriticalHitEvent
{
	struct Event
	{
		// Unknown
	};
};
struct DisarmedEvent
{
	struct Event
	{
		// Unknown
	};
};
struct ItemsPickpocketed
{
	struct Event
	{
		// Unknown
	};
};
struct ItemSteal
{
	struct Event
	{
		// Unknown
	};
};
struct ItemCrafted
{
	struct Event
	{
		// Unknown
	};
};
struct LocationDiscovery
{
	struct Event
	{
		// Unknown
	};
};
struct Jailing
{
	struct Event
	{
		// Unknown
	};
};
struct ChestsLooted
{
	struct Event
	{
		// Unknown
	};
};
struct TimesTrained
{
	struct Event
	{
		// Unknown
	};
};
struct TimesBartered
{
	struct Event
	{
		// Unknown
	};
};
struct ContractedDisease
{
	struct Event
	{
		// Unknown
	};
};
struct SpellsLearned
{
	struct Event
	{
		// Unknown
	};
};
struct DragonSoulGained
{
	struct Event
	{
		// Unknown
	};
};
struct SoulGemsUsed
{
	struct Event
	{
		// Unknown
	};
};
struct SoulsTrapped
{
	struct Event
	{
		// Unknown
	};
};
struct PoisonedWeapon
{
	struct Event
	{
		// Unknown
	};
};
struct ShoutAttack
{
	struct Event
	{
		// Unknown
	};
};
struct JailEscape
{
	struct Event
	{
		// Unknown
	};
};
struct GrandTheftHorse
{
	struct Event
	{
		// Unknown
	};
};
struct AssaultCrime
{
	struct Event
	{
		// Unknown
	};
};
struct MurderCrime
{
	struct Event
	{
		// Unknown
	};
};
struct LocksPicked
{
	struct Event
	{
		// Unknown
	};
};
struct LocationCleared
{
	struct Event
	{
		// Unknown
	};
};
struct ShoutMastered
{
	struct Event
	{
		// Unknown
	};
};

struct TESCombatEvent 
{
	TESObjectREFR	* source;	// 00
	TESObjectREFR	* target;	// 08
	UInt32			state;		// 10
};

struct TESDeathEvent
{
	TESObjectREFR	* source;	// 00
	TESObjectREFR	* killer;	// 08
	UInt8			state;		// 10 -  0 - dying; 1 - death
};

struct TESHitEvent
{
	TESObjectREFR	* target;			// 00
	TESObjectREFR	* caster;			// 08
	UInt32			sourceFormID;		// 10
	UInt32			projectileFormID;	// 14

	enum
	{
		kFlag_PowerAttack = (1 << 0),
		kFlag_SneakAttack = (1 << 1),
		kFlag_Bash		  = (1 << 2),
		kFlag_Blocked	  = (1 << 3)
	};

	UInt32			flags;				// 18 ??	
};

struct TESUniqueIDChangeEvent
{
	UInt32	oldOwnerFormId;	// 0
	UInt32	newOwnerFormId;	// 4
	UInt32	formId;			// 8
	UInt16	oldUniqueId;	// C
	UInt16	newUniqueId;	// E
};

struct TESContainerChangedEvent
{
	UInt32	fromFormId;		// 0
	UInt32	toFormId;		// 4
	UInt32	itemFormId;		// 8
	UInt32	count;			// C
	UInt32	toReference;	// 10
	UInt16	unk14;			// 14
};

struct TESObjectLoadedEvent
{
	UInt32	formId;	// 00
	UInt8	loaded; // 04	- 01 - loaded, 00 - unloaded
};

struct TESCellAttachDetachEvent
{
	TESObjectREFR * reference;	// 00
	UInt8			attached;	// 08	- 01 - attached, 00 - detached
};

struct TESMoveAttachDetachEvent
{
	TESObjectREFR * reference;	// 00
	UInt8			attached;	// 08	- 01 - attached, 00 - detached
};

struct TESInitScriptEvent
{
	TESObjectREFR * reference;
};

struct BGSFootstepEvent
{
	UInt32	actorHandle;
};

template <>
class BSTEventSink <TESCombatEvent>
{
public:
	virtual ~BSTEventSink() {}; // todo?
	virtual	EventResult ReceiveEvent(TESCombatEvent * evn, EventDispatcher<TESCombatEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESDeathEvent>
{
public:
	virtual ~BSTEventSink() {}; // todo?
	virtual	EventResult ReceiveEvent(TESDeathEvent * evn, EventDispatcher<TESDeathEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESSleepStartEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESSleepStartEvent * evn, EventDispatcher<TESSleepStartEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <MenuOpenCloseEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(MenuOpenCloseEvent * evn, EventDispatcher<MenuOpenCloseEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESQuestStageEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESQuestStageEvent * evn, EventDispatcher<TESQuestStageEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <LevelIncrease::Event>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(LevelIncrease::Event * evn, EventDispatcher<LevelIncrease::Event> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESHarvestEvent::ItemHarvested>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESHarvestEvent::ItemHarvested * evn, EventDispatcher<TESHarvestEvent::ItemHarvested> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESHitEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESHitEvent * evn, EventDispatcher<TESHitEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESUniqueIDChangeEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESUniqueIDChangeEvent * evn, EventDispatcher<TESUniqueIDChangeEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESContainerChangedEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESContainerChangedEvent * evn, EventDispatcher<TESContainerChangedEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESObjectLoadedEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESObjectLoadedEvent * evn, EventDispatcher<TESObjectLoadedEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESInitScriptEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESInitScriptEvent * evn, EventDispatcher<TESInitScriptEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESCellAttachDetachEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESCellAttachDetachEvent * evn, EventDispatcher<TESCellAttachDetachEvent> * dispatcher) = 0;
};

template <>
class BSTEventSink <TESMoveAttachDetachEvent>
{
public:
	virtual ~BSTEventSink() {}	// todo?
	virtual	EventResult ReceiveEvent(TESMoveAttachDetachEvent * evn, EventDispatcher<TESMoveAttachDetachEvent> * dispatcher) = 0;
};

// For testing
//extern EventDispatcher<TESSleepStartEvent> * g_sleepStartEventDispatcher;
//extern EventDispatcher<TESCombatEvent> * g_combatEventDispatcher;
//extern EventDispatcher<TESDeathEvent> * g_deathEventDispatcher;
//extern EventDispatcher<BGSFootstepEvent> * g_footstepEventDispatcher;
//extern EventDispatcher<TESQuestStageEvent> * g_questStageEventDispatcher;
//extern EventDispatcher<TESHarvestEvent::ItemHarvested> * g_harvestEventDispatcher;
//extern EventDispatcher<LevelIncrease::Event> * g_levelIncreaseEventDispatcher;
//extern EventDispatcher<TESHitEvent> * g_hitEventDispatcher;
//extern EventDispatcher<TESContainerChangedEvent> * g_containerChangedEventDispatcher;
//extern EventDispatcher<TESUniqueIDChangeEvent> * g_changeUniqueIDEventDispatcher;
//extern EventDispatcher<TESObjectLoadedEvent> * g_objectLoadedEventDispatcher;
//extern EventDispatcher<TESInitScriptEvent> * g_initScriptEventDispatcher;
//extern EventDispatcher<TESCellAttachDetachEvent> * g_cellAttachDetachEventDispatcher;
//extern EventDispatcher<TESMoveAttachDetachEvent> * g_moveAttachDetachEventDispatcher;
