#pragma once

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

#include "skse64/PapyrusValue.h"
#include "skse64/PapyrusInterfaces.h"

class IFunction;
class VMIdentifier;
class VMValue;
class VMClassRegistry;
class IFunctionArguments;
class DelayFunctor;


class VMClassFunctionVisitor
{
public:
	virtual bool Accept(IFunction * func) = 0;
};

// ??
class VMClassInfo
{
public:
	SInt32				refCount;	// 00
	UInt32				unk04;		// 04
	StringCache::Ref	name;		// 08
	VMClassInfo			* parent;	// 10
	StringCache::Ref	* unk0C;	// 18
	// ... More

	//UInt32				unk10;		// 10
	//UInt32				unk14;		// 14
	//UInt32				numFuncs;	// 18
	//UInt32				funcPtr;	// 1C

	/*IFunction** GetFunctions() const
	{
		return (IFunction**)(funcPtr + 
			4 * (
					((unk14 >> 10) & 0x3FF) * 10 +
					((unk10 >> 8) & 0x3FF) * 2 +
					((unk10 >> 2) & 0x3F) +
					(unk14 & 0x3FF) * 3
				)
			);
	}

	UInt32 GetNumMemberFunctions() const
	{
		return numFuncs & 0x1FF;
	}

	UInt32 GetNumGlobalFunctions() const
	{
		return (unk14 >> 20) & 0x1FF;
	}

	void Visit(VMClassFunctionVisitor & visitor)
	{
		UInt32 globalFns = GetNumGlobalFunctions();
		UInt32 memberFns = GetNumMemberFunctions();

		IFunction ** funcPtr = GetFunctions();
		for(UInt32 i = 0; i < globalFns + memberFns; i++) {
			IFunction * func = funcPtr[i];
			if(func) {
				if(visitor.Accept(func))
					break;
			}	
		}
	}*/

	void	AddRef(void);
	void	Release(void);

	MEMBER_FN_PREFIX(VMClassInfo);
	DEFINE_MEMBER_FN(Destroy, void, 0x01237240);
	DEFINE_MEMBER_FN(GetVariable, SInt32, 0x012385B0, BSFixedString * name);
	// 1673D04F90861710999A3DC0514282C538D25FCA+187
	DEFINE_MEMBER_FN(GetFunction, IFunction*, 0x0123C760, const char * fnName);
};

// This type is not fully decoded or correctly sized, just enough to use the functor
class VMScriptInstance
{
public:
	UInt32				unk00;
	VMClassInfo			* classInfo;
	void				* unk08;
	UInt32				unk0C;
	UInt32				formId;
	UInt32				unk14;
	UInt32				unk18;
};

// 04
class IForEachScriptObjectFunctor
{
public:
	IForEachScriptObjectFunctor(){};
	virtual ~IForEachScriptObjectFunctor() {};

	// return true to continue
	virtual bool	Visit(VMScriptInstance * arg, void * arg2) { return false; };

//	void	** _vtbl;	// 00
};

// 48+
class VMUnlinkedClassList
{
	virtual ~VMUnlinkedClassList()	{ }
	virtual VMUnlinkedClassList *	Create(void);
	virtual void					SetUnk0C(void * arg);
	virtual bool					Link(StringCache::Ref * className);

//	void	** _vtbl;	// 00
	VMClassRegistry	* unk04;	// 04
	void	* unk08;	// 08
	void	* unk0C;	// 0C - loader
	UInt8	unk10;		// 10
	UInt8	pad11[3];	// 11
	UInt32	unk14;		// 14
	UInt32	unk18;		// 18
	UInt32	unk1C;		// 1C
	void	* unk20;	// 20
	UInt32	unk24;		// 24
	UInt32	unk28;		// 28
	UInt32	unk2C;		// 2C
	void	* unk30;	// 30
	UInt32	unk34;		// 34
	UInt32	unk38;		// 38
	UInt32	unk3C;		// 3C
	void	* unk40;	// 40
	UInt32	unk44;		// 44
};


// 044
class VMStackInfo
{
public:
	virtual ~VMStackInfo();
	
	// more virtual functions ...

	//void**	_vtbl;			// 000
	SInt32		refCount;		// 004
	const char*	rootCallName;	// 008 - not sure if BSFixedString
	const char*	scriptName;		// 00C - not sure if BSFixedString
	// ...

};

// ??
class UnkVMStackData1
{
public:
	UInt32			unk000;		// 000
	void*			unk004;		// 004
	UInt32			unk008;		// 008

	VMStackInfo*	stackInfo;	// 00C
	// ...
};

// ??
class UnkVMStackData2
{
public:
	UInt32		unk000;		// 000 - refCount?
	void*		unk004;		// 004
	UInt32		unk008;		// 008
	UInt32		unk00C;		// 00C

	UnkVMStackData1*	unkData;		// 010
	// ...
};

// 00C
class VMStackTableItem
{
public:
	UInt32				stackId;
	UnkVMStackData2*	data;

	operator UInt32() const	{ return stackId; }

	static inline UInt32 GetHash(UInt32* pStackId)
	{
		UInt32 hash;
		CalculateCRC32_32(&hash, *pStackId);
		return hash;
	}
};

// 4B04
// this does more than hold on to class registrations, but for now that's all we care about
class VMClassRegistry
{
public:
	enum
	{
		kFunctionFlag_NoWait = 0x01	// set this only if your function is thread-safe
	};

	enum
	{
		kLogLevel_Info = 0,
		kLogLevel_Warning,
		kLogLevel_Error,
		kLogLevel_Fatal
	};

	typedef tHashSet<VMStackTableItem,UInt32> StackTableT;

	VMClassRegistry();
	virtual ~VMClassRegistry();

	// ### indices are from 1.5.26

	virtual void	Unk_01(void);
	virtual void	PrintToDebugLog(const char* text, UInt32 stackId, UInt32 logLevel);
	virtual void	Unk_03(void);
	virtual void	Unk_04(void);
	virtual void	Unk_05(void);
	virtual void	Unk_06(void);
	virtual void	Unk_07(void);
	virtual void	RegisterForm(UInt32 typeID, const char * papyrusClassName);
	virtual void	Unk_09(void);
	virtual bool	GetFormTypeClass(UInt32 formType, VMClassInfo ** outClass);
	virtual void	Unk_0B(void);
	virtual void	Unk_0C(void);
	virtual bool	Unk_0D(StringCache::Ref * className, UInt32 * unk);
	virtual void	Unk_0E(void);
	virtual void	Unk_0F(void);
	virtual void	Unk_10(void);
	virtual void	Unk_11(void);
	virtual void	Unk_12(void);
	virtual void	Unk_13(void);
	virtual void	Unk_14(void);
	virtual bool	Unk_15(StringCache::Ref * className, VMIdentifier ** identifier);
	virtual bool	CreateArray(VMValue * value, UInt32 size, VMValue::ArrayData ** unk1);
	virtual void	Unk_17(void);
	virtual void	RegisterFunction(IFunction * fn);
	virtual void	SetFunctionFlagsEx(const char * className, UInt32 unk0, const char * fnName, UInt32 flags);
	virtual void	SetFunctionFlags(const char * className, const char * fnName, UInt32 flags);
	virtual void	VisitScripts(UInt64 handle, IForEachScriptObjectFunctor * functor);
	virtual bool	Unk_1C(UInt64 handle, const char * className, VMIdentifier ** identifier);
	virtual void	Unk_1D(void);
	virtual void	Unk_1E(void);
	virtual void	Unk_1F(void);
	virtual void	Unk_20(void);
	virtual void	Unk_21(void);
	virtual void	Unk_22(void);
	virtual bool	ExtractValue(UInt64 handle, StringCache::Ref * className, SInt32 variableIndex, VMValue * out);	// unk probably script context object?
	virtual void	QueueEvent(UInt64 handle, const StringCache::Ref * eventName, IFunctionArguments * args);
	virtual void	Unk_25(void);
	virtual void	Unk_26(void);
	virtual void	Unk_27(void);
	virtual void	Unk_28(void);
	virtual void	Unk_29(void);
	virtual void	Unk_2A(void);
	virtual void	ResumeStack(UInt32 stackId, VMValue* result);
	virtual void	Unk_2C(void);
	virtual IObjectHandlePolicy *	GetHandlePolicy(void);
	virtual void	Unk_2E(void);
	virtual ObjectBindPolicy *		GetObjectBindPolicy(void);
	virtual void	Unk_30(void);
	virtual void	Unk_31(void);
	virtual void	Unk_32(void);
	virtual void	Unk_33(void);
	virtual void	Unk_34(void);
	virtual void	Unk_35(void);

//	void		** _vtbl;		// 0000
	UInt64		unk0008;		// 0008 - refcount?
	void		** vtbl0008;	// 0010
	void		** vtbl000C;	// 0018
	void		** vtbl0010;	// 0020

	UInt64		unk9310[(0x9310 - 0x20) >> 3];	// 9310

	SimpleLock	stackLock;		// 9318
	StackTableT	allStacks;		// 9320
	StackTableT	waitingStacks;	// 9350

	// More...

	VMStackInfo* GetStackInfo(UInt32 stackId);

	void LogError(const char* message, UInt32 stackId)   { PrintToDebugLog(message, stackId, kLogLevel_Error); }
	void LogWarning(const char* message, UInt32 stackId) { PrintToDebugLog(message, stackId, kLogLevel_Warning); }
};
STATIC_ASSERT(offsetof(VMClassRegistry, stackLock) == 0x9318);
STATIC_ASSERT(offsetof(VMClassRegistry, allStacks) == 0x9320);
STATIC_ASSERT(offsetof(VMClassRegistry, waitingStacks) == 0x9350);

class IStackCallbackSaveInterface
{
public:
	virtual ~IStackCallbackSaveInterface();

	virtual void	Unk_01(void);
	virtual void	Unk_02(void);
};

// 45D0
class SkyrimVM
{
public:
	SkyrimVM();
	virtual ~SkyrimVM();

	virtual void	Unk_01(void);

	enum
	{
		kEventSink_Activate = 0,
		kEventSink_ActiveEffectApplyRemove,
		kEventSink_ActorLocationChange,
		kEventSink_BookRead,
		kEventSink_CellAttachDetach,
		kEventSink_CellFullyLoaded,
		kEventSink_Combat,
		kEventSink_ContainerChanged,
		kEventSink_Death,
		kEventSink_DestructionStateChanged,
		kEventSink_EnterBleedout,
		kEventSink_Equip,
		kEventSink_FormDelete,
		kEventSink_Furniture,
		kEventSink_GrabRelease,
		kEventSink_Hit,
		kEventSink_InitScript,
		kEventSink_LoadGame,
		kEventSink_LockChanged,
		kEventSink_MagicEffectApply,
		kEventSink_MagicWardHit,
		kEventSink_MoveAttachDetach,
		kEventSink_ObjectLoaded,
		kEventSink_ObjectREFRTranslation,
		kEventSink_OpenClose,
		kEventSink_Package,
		kEventSink_PerkEntryRun,
		kEventSink_QuestInit,
		kEventSink_QuestStage,
		kEventSink_Reset,
		kEventSink_ResolveNPCTemplates,
		kEventSink_Scene,
		kEventSink_SceneAction,
		kEventSink_ScenePhase,
		kEventSink_Sell,
		kEventSink_SleepStart,
		kEventSink_SleepStop,
		kEventSink_SpellCast,
		kEventSink_TopicInfo,
		kEventSink_TrackedStats,
		kEventSink_TrapHit,
		kEventSink_Trigger,
		kEventSink_TriggerEnter,
		kEventSink_TriggerLeave,
		kEventSink_UniqueIDChange,
		kEventSink_SwitchRaceComplete,
		kEventSink_PlayerBowShot,
		kEventSink_FastTravelEnd,	// added in 1.5.3
		kEventSink_PositionPlayer,
		kEventSink_Stats,
		kEventSink_NumEvents
	};

//	void						** _vtbl;								// 0000
	IStackCallbackSaveInterface	m_callbackSaveInterface;				// 0008
	void						* eventSinks[kEventSink_NumEvents];		// 0010
	UInt64						unk1A0[(0x200 - 0x1A0) >> 3];			// 01A0
	VMClassRegistry				* m_classRegistry;						// 0200
	UInt8						pad200[0x714 - 0x208];					// 0208
	SimpleLock					m_updateLock;							// 0714
	UInt8						pad71C[0x720 - 0x71C];					// 071C
	UpdateRegistrationHolder	m_updateRegHolder;						// 0720
	UpdateRegistrationHolder	m_updateGameTimeRegHolder;				// 0738

	VMClassRegistry	*	GetClassRegistry(void)	{ return m_classRegistry; }

	UInt32				ClearInvalidRegistrations(void);

	MEMBER_FN_PREFIX(SkyrimVM);

	// Used by Hooks_Papyrus
	// 7B34FD8669F3B7848943EE6F26F783A154F6A336+31
	DEFINE_MEMBER_FN(UnregisterFromSleep_Internal, void, 0x009247E0, UInt64 handle);
	DEFINE_MEMBER_FN(RevertGlobalData_Internal, bool, 0x0092DAE0);
	// E79F47684196468F785E6950402DBDAA5A668FAA+110
	DEFINE_MEMBER_FN(SaveRegSleepEventHandles_Internal, bool, 0x0092EB50, void * handleReaderWriter, void * saveStorageWrapper);
	// ECCDB3740458A9BB07FA958498C66F04CE14F94C+1E6
	DEFINE_MEMBER_FN(LoadRegSleepEventHandles_Internal, bool, 0x0092FD80, void * handleReaderWriter, void * loadStorageWrapper);

	// 00011FEFBFDA69F253C84165B45736A4F0AF02B8+204
	DEFINE_MEMBER_FN(QueueDelayFunctor_Internal, bool, 0x009252C0, void** pFunctor);

	bool QueueDelayFunctor(void** pFunctor)
	{
		return CALL_MEMBER_FN(this, QueueDelayFunctor_Internal)(pFunctor);
	}

	void OnFormDelete_Hook(UInt64 handle);
	void RevertGlobalData_Hook(void);
	bool SaveGlobalData_Hook(void * handleReaderWriter, void * saveStorageWrapper);
	bool LoadGlobalData_Hook(void * handleReaderWriter, void * loadStorageWrapper);
};
STATIC_ASSERT(offsetof(SkyrimVM, m_classRegistry) == 0x200);
STATIC_ASSERT(offsetof(SkyrimVM, m_updateLock) == 0x714);
STATIC_ASSERT(offsetof(SkyrimVM, m_updateRegHolder) == 0x720);
STATIC_ASSERT(offsetof(SkyrimVM, m_updateGameTimeRegHolder) == 0x738);

extern RelocPtr<SkyrimVM*> g_skyrimVM;

// ??
class IFunctionArguments
{
public:
	virtual ~IFunctionArguments()	{ }

	// ??
	struct Output
	{
		UInt32	unk00;		// 00
		UInt32	unk04;		// 04
		VMValue	* m_data;	// 08 - confirmed offset
		UInt64	unk08;		// 10
		UInt32	m_size;		// 18 - confirmed offset

		void	Resize(UInt32 len)	{ CALL_MEMBER_FN(this, Resize)(len); }
		VMValue	* Get(UInt32 idx)	{ return (idx < m_size) ? &m_data[idx] : NULL; }

		MEMBER_FN_PREFIX(Output);
		DEFINE_MEMBER_FN(Resize, bool, 0x00920360, UInt32 len);
	};

	virtual bool	Copy(Output * dst) = 0;
};
