#pragma once

#include "skse64_common/Utilities.h"
#include "skse64/GameTypes.h"
#include "skse64/GameForms.h"
#include "skse64/GameObjects.h"
#include "skse64/GameReferences.h"

class TESObjectWEAP;
class TESNPC;
class AlchemyItem;
class SpellItem;
class ScrollItem;
class IngredientItem;
class EffectSetting;
class TESObjectARMO;
class TESObjectARMA;
class TESObjectCONT;
class TESObjectCELL;
class TESRegionList;
class BGSAddonNode;
class BGSKeyword;
class BGSAction;
class BGSHeadPart;
class TESRace;
class EnchantmentItem;
class TESObjectACTI;
class TESFurniture;
class BGSEncounterZone;
class BGSLocation;
class BGSColorForm;
class BSFile;
class BSFaceGenModelMap;
class BSFaceGenModel;
class NiTexture;

class NiAVObject;
class NiColorA;

extern RelocAddr <UInt32*> g_gameTime;

struct FormRecordData
{
	UInt8		typeID;		// corresponds to kFormType_XXX
	UInt32		typeCode;	// i.e. 'GMST', 'FACT'
	UInt32		unk08;		// only seen zero
};

struct ChunkHeader
{
	UInt32	type : 4;	// i.e. 'XGRD', 'DATA'
	UInt16	size : 2;
};

// 4C8
struct ModInfo		// referred to by game as TESFile
{
	ModInfo();
	~ModInfo();

	// 18 info about currently loading form
	struct FormInfo
	{
		UInt32		recordType;			// 00 i.e. 'FACT', 'GMST'
		UInt32		unk04;				// 04 looks like size of entire record
		UInt32		formFlags;			// 08 copied to TESForm->flags
		UInt32		formID;				// 0C 
		UInt32		unk10;				// 10
		UInt16		unk14;				// 14 always initialized to 0F on SaveForm. 
		UInt16		unk16;
	};

	enum FileFlags
	{
		kFileFlags_Light = (1 << 9)
	};

	UInt32								unk000;				// 000
	UInt8								unk004[0xC];		// 004
	UInt32 /*NiTPointerMap<TESFile*>*/	* pointerMap;		// 010   CHECK
	UInt64								unk018[2];			// 018
	UInt8								unk028;				// 028
	UInt8								unk029;				// 029
	UInt8								pad02A[6];			// 029
	UInt64								unk030;				// 030
	BSFile*								unkFile;			// 038
	UInt64								unk040[3];			// 040
	char								name[0x104];		// 058
	char								filepath[0x104];	// 15C
	void*								unk260;				// 260 
	UInt32								unk268;				// 268 - init'd to ctor arg(0x800) or 0x10000 if arg is 0
	UInt32								unk26C;				// 26C
	UInt32								unk270;				// 270
	UInt32								unk274;				// 274 - pad?
	UInt64								unk278;				// 278
	UInt32								unk280;				// 280
	FormInfo							formInfo;			// 284 
	ChunkHeader							subRecord;			// 29C
	UInt32								unk2A4;				// 2A4
	UInt32								fileOffset;			// 2A8
	UInt32								dataOffset;			// 2AC index into dataBuf
	UInt32								subrecordBytesRead;	// 2B0 generates error on Read if != expected length
	UInt8								unk2B4[0x2D8 - 0x2B4];	// 2B4
	UInt64								unk2D8;				// 2D8
	UInt64								unk2E0;				// 2E0
	UInt8								unk2E8;				// 2E8
	UInt8								bIsBigEndian;		// 2E9
	UInt8								unk2EA;				// 2EA
	UInt8								pad2EB;				// 2EB
	WIN32_FIND_DATA						fileData;			// 2EC
	float								unk42C;				// 42C init'd to 0.94
	UInt32								unk430;				// 430
	UInt32								flags;				// 434 init'd to 0x00000800. 4000 and 40000 do stuff
	UInt32								fileFlags;			// 438
	UInt32								unk43C;				// 43C
	void*								unk440;				// 440
	void*								unk448;				// 448
	void*								unk450;				// 450
	void*								unk458;				// 458
	UInt32								numRefMods;			// 460 related to modindex; see 4472D0
															// formIDs in mod are as saved in GECK, must fix up at runtime
	UInt32								pad464;
	ModInfo								** refModInfo;		// 468 used to look up modInfo based on fixed mod index, double-check 
	UInt32								unk470;				// 470
	UInt32								unk474;				// 474
	UInt8								modIndex;			// 478 init to 0xFF
	UInt8								pad479;				// 479
	UInt16								lightIndex;			// 47A
	UInt8								pad47C[4];
	BSString							author;				// 480
	BSString							description;		// 490
	void								* dataBuf;			// 4A0 
	UInt32								unk4A8;				// 4A8 looks like size of entire record
	UInt32								pad4AC;				// 4AC
	void								* unk4B0;			// 4B0
	UInt32								unk4B8;				// 4B8
	UInt32								pad4BC;				// 4BC
	void								* unk4C0;			// 4C0

	// Checks if a particular formID is part of the mod
	bool IsFormInMod(UInt32 formID) const
	{
		if (!IsLight() && (formID >> 24) == modIndex)
			return true;
		if (IsLight() && ((formID & 0x00FFF000) >> 12) == lightIndex)
			return true;
		return false;
	}

	// Returns either a modIndex or a modIndex|lightIndex pair
	UInt32 GetPartialIndex() const
	{
		return !IsLight() ? modIndex : (0xFE000 | lightIndex);
	}

	// Converts the lower bits of a FormID to a full FormID depending on plugin type
	UInt32 GetFormID(UInt32 formLower) const
	{
		return !IsLight() ? UInt32(modIndex) << 24 | (formLower & 0xFFFFFF) : 0xFE000000 | (UInt32(lightIndex) << 12) | (formLower & 0xFFF);
	}

	bool IsActive() const { return modIndex != 0xFF; }
	bool IsLight() const { return (fileFlags & kFileFlags_Light) == kFileFlags_Light; }
};

STATIC_ASSERT(offsetof(ModInfo, formInfo) == 0x284);
STATIC_ASSERT(offsetof(ModInfo, subRecord) == 0x29C);
STATIC_ASSERT(offsetof(ModInfo, fileOffset) == 0x2A8);
STATIC_ASSERT(offsetof(ModInfo, fileData) == 0x2EC);
STATIC_ASSERT(offsetof(ModInfo, numRefMods) == 0x460);
STATIC_ASSERT(offsetof(ModInfo, author) == 0x480);
STATIC_ASSERT(sizeof(ModInfo) == 0x4C8);

// 40 - updated in 1.5.3
struct ModList
{
	tList<ModInfo>		modInfoList;		// 00 - DataHandler D60
	tArray<ModInfo *>	loadedMods;			// 10 - DataHandler D70
	tArray<ModInfo *>	loadedCCMods;		// 28 - DataHandler D88 - just assuming this is for CC
};
STATIC_ASSERT(sizeof(ModList) == 0x40);

// DC0
class DataHandler
{
public:
	UInt64								unk00;
	UInt64								unk08;
	UnkFormArray						arrNONE;	// Form Type 0
	UnkFormArray						arrTES4;	// Form Type 1
	UnkFormArray						arrGRUP;	// Form Type 2
	UnkFormArray						arrGMST;	// Form Type 3
	tArray<BGSKeyword*>					keywords;	// Form Type 4
	UnkFormArray						arrLCRT;	// Form Type 5
	UnkFormArray						arrAACT;	// Form Type 6
	UnkFormArray						arrTXST;	// Form Type 7
	UnkFormArray						arrMICN;	// Form Type 8
	UnkFormArray						arrGLOB;	// Form Type 9
	UnkFormArray						arrCLAS;	// Form Type 10
	UnkFormArray						arrFACT;	// Form Type 11
	tArray<BGSHeadPart*>				headParts;	// Form Type 12
	UnkFormArray						arrEYES;	// Form Type 13
	tArray<TESRace*>					races;	// Form Type 14
	UnkFormArray						arrSOUN;	// Form Type 15
	UnkFormArray						arrASPC;	// Form Type 16
	UnkFormArray						arrSKIL;	// Form Type 17
	tArray<EffectSetting*>				arrEffectSettings;	// Form Type 18
	UnkFormArray						arrSCPT;	// Form Type 19
	UnkFormArray						arrLTEX;	// Form Type 20
	tArray<EnchantmentItem*>			enchantments;	// Form Type 21
	tArray<SpellItem*>					spells;	// Form Type 22
	tArray<ScrollItem*>					scrolls;	// Form Type 23
	tArray<TESObjectACTI*>				arrACTI;	// Form Type 24
	UnkFormArray						arrTACT;	// Form Type 25
	tArray<TESObjectARMO*>				armors;	// Form Type 26
	tArray<TESObjectBOOK*>				books;	// Form Type 27
	tArray<TESObjectCONT*>				arrCONT;	// Form Type 28
	UnkFormArray						arrDOOR;	// Form Type 29
	tArray<IngredientItem*>				ingredients;	// Form Type 30
	UnkFormArray						arrLIGH;	// Form Type 31
	tArray<TESObjectMISC*>				miscObjects;	// Form Type 32
	UnkFormArray						arrSTAT;	// Form Type 33
	UnkFormArray						arrAPPA;	// Form Type 34
	UnkFormArray						arrSCOL;	// Form Type 35
	UnkFormArray						arrMSTT;	// Form Type 36
	UnkFormArray						arrGRAS;	// Form Type 37
	UnkFormArray						arrTREE;	// Form Type 38
	UnkFormArray						arrFLOR;	// Form Type 39
	tArray<TESFurniture*>				arrFURN;	// Form Type 40
	tArray<TESObjectWEAP*>				weapons;	// Form Type 41
	tArray<TESAmmo*>					ammo;	// Form Type 42
	tArray<TESNPC*>						npcs;	// Form Type 43
	UnkFormArray						arrLVLN;	// Form Type 44
	tArray<TESKey*>						keys;	// Form Type 45
	tArray<AlchemyItem*>				potions;	// Form Type 46
	UnkFormArray						arrIDLM;	// Form Type 47
	UnkFormArray						arrNOTE;	// Form Type 48
	UnkFormArray						arrCOBJ;	// Form Type 49
	UnkFormArray						arrPROJ;	// Form Type 50
	UnkFormArray						arrHAZD;	// Form Type 51
	UnkFormArray						arrSLGM;	// Form Type 52
	UnkFormArray						arrLVLI;	// Form Type 53
	UnkFormArray						arrWTHR;	// Form Type 54
	UnkFormArray						arrCLMT;	// Form Type 55
	UnkFormArray						arrSPGD;	// Form Type 56
	UnkFormArray						arrRFCT;	// Form Type 57
	UnkFormArray						arrREGN;	// Form Type 58
	UnkFormArray						arrNAVI;	// Form Type 59
	UnkFormArray						arrCELL;	// Form Type 60
	UnkFormArray						arrREFR;	// Form Type 61
	UnkFormArray						arrACHR;	// Form Type 62
	UnkFormArray						arrPMIS;	// Form Type 63
	UnkFormArray						arrPARW;	// Form Type 64
	UnkFormArray						arrPGRE;	// Form Type 65
	UnkFormArray						arrPBEA;	// Form Type 66
	UnkFormArray						arrPFLA;	// Form Type 67
	UnkFormArray						arrPCOM;	// Form Type 68
	UnkFormArray						arrPBAR;	// Form Type 69
	UnkFormArray						arrPHZD;	// Form Type 70
	UnkFormArray						arrWRLD;	// Form Type 71
	UnkFormArray						arrLAND;	// Form Type 72
	UnkFormArray						arrNAVM;	// Form Type 73
	UnkFormArray						arrTLOD;	// Form Type 74
	UnkFormArray						arrDIAL;	// Form Type 75
	UnkFormArray						arrINFO;	// Form Type 76
	tArray<TESQuest*>					quests;	// Form Type 77
	UnkFormArray						arrIDLE;	// Form Type 78
	UnkFormArray						arrPACK;	// Form Type 79
	UnkFormArray						arrCSTY;	// Form Type 80
	UnkFormArray						arrLSCR;	// Form Type 81
	UnkFormArray						arrLVSP;	// Form Type 82
	UnkFormArray						arrANIO;	// Form Type 83
	UnkFormArray						arrWATR;	// Form Type 84
	UnkFormArray						arrEFSH;	// Form Type 85
	UnkFormArray						arrTOFT;	// Form Type 86
	UnkFormArray						arrEXPL;	// Form Type 87
	UnkFormArray						arrDEBR;	// Form Type 88
	UnkFormArray						arrIMGS;	// Form Type 89
	UnkFormArray						arrIMAD;	// Form Type 90
	UnkFormArray						arrFLST;	// Form Type 91
	UnkFormArray						arrPERK;	// Form Type 92
	UnkFormArray						arrBPTD;	// Form Type 93
	UnkFormArray						arrADDN;	// Form Type 94
	UnkFormArray						arrAVIF;	// Form Type 95
	UnkFormArray						arrCAMS;	// Form Type 96
	UnkFormArray						arrCPTH;	// Form Type 97
	UnkFormArray						arrVTYP;	// Form Type 98
	UnkFormArray						arrMATT;	// Form Type 99
	UnkFormArray						arrIPCT;	// Form Type 100
	UnkFormArray						arrIPDS;	// Form Type 101
	tArray<TESObjectARMA*>				arrARMA;	// Form Type 102
	tArray<BGSEncounterZone*>			arrECZN;	// Form Type 103
	tArray<BGSLocation*>				arrLCTN;	// Form Type 104
	UnkFormArray						arrMESG;	// Form Type 105
	UnkFormArray						arrRGDL;	// Form Type 106
	UnkFormArray						arrDOBJ;	// Form Type 107
	UnkFormArray						arrLGTM;	// Form Type 108
	UnkFormArray						arrMUSC;	// Form Type 109
	UnkFormArray						arrFSTP;	// Form Type 110
	UnkFormArray						arrFSTS;	// Form Type 111
	UnkFormArray						arrSMBN;	// Form Type 112
	UnkFormArray						arrSMQN;	// Form Type 113
	UnkFormArray						arrSMEN;	// Form Type 114
	UnkFormArray						arrDLBR;	// Form Type 115
	UnkFormArray						arrMUST;	// Form Type 116
	UnkFormArray						arrDLVW;	// Form Type 117
	tArray<TESWordOfPower*>				arrWOOP;	// Form Type 118
	tArray<TESShout*>					arrSHOU;	// Form Type 119
	UnkFormArray						arrEQUP;	// Form Type 120
	UnkFormArray						arrRELA;	// Form Type 121
	UnkFormArray						arrSCEN;	// Form Type 122
	UnkFormArray						arrASTP;	// Form Type 123
	UnkFormArray						arrOTFT;	// Form Type 124
	UnkFormArray						arrARTO;	// Form Type 125
	UnkFormArray						arrMATO;	// Form Type 126
	UnkFormArray						arrMOVT;	// Form Type 127
	UnkFormArray						arrSNDR;	// Form Type 128
	UnkFormArray						arrDUAL;	// Form Type 129
	UnkFormArray						arrSNCT;	// Form Type 130
	UnkFormArray						arrSOPM;	// Form Type 131
	UnkFormArray						arrCOLL;	// Form Type 132
	tArray<BGSColorForm*>				arrCLFM;	// Form Type 133
	UnkFormArray						arrREVB;	// Form Type 134
	UnkFormArray						arrLENS;	// Form Type 135
	UnkFormArray						arrLSPR;	// Form Type 136
	UnkFormArray						arrVOLI;	// Form Type 137

	TESRegionList						* regionList;	// D00
//	NiTArray<TESObjectCELL*>			cellList;		// D08
	UInt64								cellList[0x3];	// D08														
//	NiTArray<BGSAddonNode*>				addonNodes;		// D20
	UInt64								addonNodes[0x3]; // D20

	UInt64								unkD38[3];		// D38
	UInt32								unkD50;			// D50 - init'd to 0x800
	UInt32								padD54;			// D54
	UInt64								unkD58;			// D58
	ModList								modList;		// D60

	UInt8								unkDA0;		// DA0
	UInt8								unkDA1;		// DA1
	UInt8								unkDA2;		// DA2
	UInt8								unkDA3;		// DA3
	UInt8								unkDA4;		// DA4
	UInt8								unkDA5;		// DA5
	UInt8								unkDA6;		// DA6
	UInt8								unkDA7;		// DA7
	UInt8								unkDA8;		// DA8
	UInt8								unkDA9;		// DA9
	UInt8								unkDAA;		// DAA
	UInt8								padDAB[5];	// DAB

	void								* regionDataManager;	// DB0 - TESRegionDataManager*, allocated in ctor
	UInt64								unkDB8;		// DB8

	const ModInfo* LookupModByName(const char* modName);

	static DataHandler * GetSingleton();

	UInt32 LoadScripts_Hook();

	MEMBER_FN_PREFIX(DataHandler);
	DEFINE_MEMBER_FN(LoadScripts, UInt32, 0x001713D0);
};

STATIC_ASSERT(offsetof(DataHandler, regionList) == 0xD00);
STATIC_ASSERT(offsetof(DataHandler, addonNodes) == 0xD20);
STATIC_ASSERT(offsetof(DataHandler, modList) == 0xD60);
STATIC_ASSERT(sizeof(DataHandler) == 0xDC0);

extern RelocPtr <DataHandler*> g_dataHandler;
extern RelocPtr <bool> g_isGameDataReady;

class MiscStatManager
{
public:
	static MiscStatManager *	GetSingleton(void);

	// 20
	struct MiscStat
	{
		const char	* name;		// 00
		const char	* unk04;	// 08
		UInt32		value;		// 10
		UInt32		unk14;		// 14
		UInt8		unk18;		// 18
		UInt8		pad19[7];	// 19
	};

	class Visitor
	{
	public:
		virtual void	Visit(MiscStat * stat, void * stat_unk04, UInt32 stat_unk0C, UInt32 value, UInt32 stat_unk10) = 0;
	};

	MEMBER_FN_PREFIX(MiscStatManager);
	// SE: It looks like another inline victim. It wasn't used by anything else in SKSE.
	//DEFINE_MEMBER_FN(Visit, void, 0x00000000, Visitor ** visitor);

	tArray<MiscStat> m_stats;	// 00

	MiscStat *	Get(const char * name);
};
STATIC_ASSERT(sizeof(MiscStatManager::MiscStat) == 0x20);

class EquipManager
{
public:
	virtual ~EquipManager();

	static EquipManager *   GetSingleton(void);

	MEMBER_FN_PREFIX(EquipManager);
	DEFINE_MEMBER_FN(EquipItem, void, 0x00637A80, Actor * actor, TESForm * item, BaseExtraList * extraData, SInt32 count, BGSEquipSlot * equipSlot, bool withEquipSound, bool preventUnequip, bool showMsg, void * unk);
	DEFINE_MEMBER_FN(UnequipItem, bool, 0x00638190, Actor * actor, TESForm * item, BaseExtraList * extraData, SInt32 count, BGSEquipSlot * equipSlot, bool unkFlag1, bool preventEquip, bool unkFlag2, bool unkFlag3, void * unk);
};


typedef BGSEquipSlot * (*_GetEitherHandSlot)();
extern RelocAddr<_GetEitherHandSlot> GetEitherHandSlot;

typedef BGSEquipSlot * (*_GetRightHandSlot)();
extern RelocAddr<_GetRightHandSlot> GetRightHandSlot;

typedef BGSEquipSlot * (*_GetLeftHandSlot)();
extern RelocAddr<_GetLeftHandSlot> GetLeftHandSlot;

typedef UInt32(*_LookupActorValueByName)(const char * name);
extern RelocAddr<_LookupActorValueByName> LookupActorValueByName;

typedef bool(*_HasLOS)(Actor* source, TESObjectREFR* target, UInt8 * unk1);
extern RelocAddr<_HasLOS> HasLOS;

typedef UInt32(*_GetRelationshipIndex)(TESForm * form1, TESForm * form2);
extern RelocAddr<_GetRelationshipIndex> GetRelationshipIndex;

class RelationshipRanks
{
public:
	static RelationshipRanks * GetSingleton(void);

	enum
	{
		kRelationshipLover = 0,
		kRelationshipAlly,
		kRelationshipConfidant,
		kRelationshipFriend,
		kRelationshipAcquaintance,
		kRelationshipRival,
		kRelationshipFoe,
		kRelationshipEnemy,
		kRelationshipArchnemesis,
		kNumRelationships
	};
	SInt32 value[kNumRelationships];

	static SInt32 GetRelationshipRank(TESForm * form1, TESForm * form2);
};

class ActorValueList
{
public:
	enum {
		kNumActorValues = 164
	};

	static ActorValueList * GetSingleton(void);
	ActorValueInfo * GetActorValue(UInt32 id);

	static UInt32 ResolveActorValueByName(const char * name);

private:
	UInt32	unk00;	// 00
	UInt32	pad04;	// 04
	ActorValueInfo * actorValues[kNumActorValues]; // 08
};

// 68 or 70
class FaceGen
{
public:
	static FaceGen *	GetSingleton(void);

	struct Action {
		BSFixedString name;
		UInt32	unk04;
		float	delta;
	};

	// 40
	class MorphDatabase
	{
	public:
		MEMBER_FN_PREFIX(MorphDatabase);
		DEFINE_MEMBER_FN(GetFaceGenModelMapEntry, bool, 0x003D4F90, const char * meshPath, BSFaceGenModelMap ** entry);
		DEFINE_MEMBER_FN(SetFaceGenModelMapEntry, void, 0x003D4D80, const char * meshPath, BSFaceGenModel * model);

		UInt64	unk00;	// 00
		UInt32	unk08;	// 08
		UInt32	unk0C;	// 0C
		UInt32	unk10;	// 10
		UInt32	unk14;	// 14
		void	*unk18;	// 18
		UInt64	unk20;	// 20
		UInt64	unk28;	// 28

		struct Data30
		{
			UInt32	unk00;
			UInt32	unk04;
		};

		Data30	unk30;		// 30
		UInt8	unk38;		// 38
		UInt8	pad39[7];	// 39
	};

	UInt32			unk00;						// 00
	UInt32			unk04;						// 04
	UInt8			unk08;						// 08
	UInt8			pad09[7];					// 09
	UInt64			unk10;						// 10
	MorphDatabase	morphDatabase;				// 18
	UInt8			isReset;					// 58
	UInt8			pad59[3];					// 59
	UInt32			unk5C;						// 5C
	UInt8			unk60;						// 60
	UInt8			pad61[7];					// 61

	MEMBER_FN_PREFIX(FaceGen);
	DEFINE_MEMBER_FN(RegenerateHead, void, 0x003D2A60, BSFaceGenNiNode * headNode, BGSHeadPart * head, TESNPC * npc);
	DEFINE_MEMBER_FN(ApplyMorph, void, 0x003D2380, BSFaceGenNiNode * faceGenNode, BGSHeadPart * headPart, BSFixedString * morphName, float relative);
};
STATIC_ASSERT(offsetof(FaceGen, isReset) == 0x58);

// Changes one HeadPart to another
typedef void(*_ChangeActorHeadPart)(Actor*, BGSHeadPart* oldPart, BGSHeadPart* newPart);
extern RelocAddr<_ChangeActorHeadPart> ChangeActorHeadPart;


// Regenerates dynamic tints
typedef UInt32(*_UpdatePlayerTints)();
extern RelocAddr<_UpdatePlayerTints> UpdatePlayerTints;

typedef BGSHeadPart ** (*_GetActorBaseOverlays)(TESNPC * npc);
extern RelocAddr<_GetActorBaseOverlays> GetActorBaseOverlays;

typedef UInt32(*_GetNumActorBaseOverlays)(TESNPC * npc);
extern RelocAddr<_GetNumActorBaseOverlays> GetNumActorBaseOverlays;

typedef bool(*_ApplyMasksToRenderTarget)(tArray<TintMask*> * tintMask, NiTexture * renderTarget);
extern RelocAddr<_ApplyMasksToRenderTarget> ApplyMasksToRenderTarget;

typedef UInt32(*_UpdateModelSkin)(NiAVObject*, NiColorA**);
extern RelocAddr<_UpdateModelSkin> UpdateModelSkin;

typedef UInt32(*_UpdateModelHair)(NiAVObject*, NiColorA**);
extern RelocAddr<_UpdateModelHair> UpdateModelHair;

typedef UInt32(*_UpdateModelFace)(NiAVObject*);
extern RelocAddr<_UpdateModelFace> UpdateModelFace;

typedef void(*_UpdateHarvestModel)(TESObjectREFR*, NiAVObject*);
extern RelocAddr<_UpdateHarvestModel> UpdateHarvestModel;

// 40
class MagicFavorites
{
	//	void			** _vtbl;	// 00
	UInt64			unk008;		// 08
	UnkFormArray	spells;		// 10
	UnkFormArray	hotkeys;	// 28

public:
	virtual	~MagicFavorites();

	void		SetHotkey(TESForm * form, SInt8 idx);
	void		ClearHotkey(SInt8 idx);
	TESForm	*	GetSpell(SInt8 idx);
	bool		IsFavorited(TESForm * form);

	static MagicFavorites * GetSingleton(void);
};

// D0
class PersistentFormManager
{
public:
	
	// 10
	struct EnchantData
	{
		EnchantmentItem *	enchantment;	// 00
		volatile SInt32		refCount;		// 08
		UInt32				pad;			// 0C
	};

	UInt64	unk00;	// 00
	tArray<EnchantData>	weaponEnchants;	// 08
	tArray<EnchantData>	armorEnchants;	// 20
	UInt64	unk38;	// 38
	UInt32	unk40;	// 40
	UInt32	unk44;	// 44
	UInt32	unk48;	// 48
	UInt32	unk4C;	// 4C

	// 30
	struct Data
	{
		void*	unk00;	// 00
		UInt64	unk08;	// 08
		void*	unk10;	// 10
		UInt64	unk18;	// 18
		UInt32	unk20;	// 20
		UInt32	unk24;	// 24
		UInt32	unk28;	// 28
		UInt32	unk2C;	// 2C
	};

	Data	unk50;	// 50
	Data	unk80;	// 80
	void*	unkB0;	// B0
	UInt64	unkB8;	// B8
	UInt64	unkC0;	// C0
	UInt64	unkC8;	// C8

	static PersistentFormManager * GetSingleton(void);

	void IncRefEnchantment(EnchantmentItem * enchantment)
	{
		if (enchantment && enchantment->formID >= 0xFF000000) {
			for (UInt32 i = 0; i < weaponEnchants.count; i++) {
				EnchantData foundData;
				weaponEnchants.GetNthItem(i, foundData);
				if (foundData.enchantment == enchantment) {
					InterlockedIncrement(&weaponEnchants[i].refCount);
					break;
				}
			}
			for (UInt32 i = 0; i < armorEnchants.count; i++) {
				EnchantData foundData;
				armorEnchants.GetNthItem(i, foundData);
				if (foundData.enchantment == enchantment) {
					InterlockedIncrement(&armorEnchants[i].refCount);
					break;
				}
			}
		}
	}

	// The game doesn't bother to dec ref or even delete custom enchants
	// when they are no longer used, maybe we can fix this?
	void DecRefEnchantment(EnchantmentItem * enchantment)
	{
		if (enchantment && enchantment->formID >= 0xFF000000) {
			for (UInt32 i = 0; i < weaponEnchants.count; i++) {
				EnchantData foundData;
				weaponEnchants.GetNthItem(i, foundData);
				if (foundData.enchantment == enchantment) {
					if (!InterlockedDecrement(&weaponEnchants[i].refCount))
						CALL_MEMBER_FN(this, ScheduleForDeletion)(enchantment);
					break;
				}
			}
			for (UInt32 i = 0; i < armorEnchants.count; i++) {
				EnchantData foundData;
				armorEnchants.GetNthItem(i, foundData);
				if (foundData.enchantment == enchantment) {
					if (!InterlockedDecrement(&armorEnchants[i].refCount))
						CALL_MEMBER_FN(this, ScheduleForDeletion)(enchantment);
					break;
				}
			}
		}
	}

	MEMBER_FN_PREFIX(PersistentFormManager);
	DEFINE_MEMBER_FN(CreateOffensiveEnchantment, EnchantmentItem *, 0x0059F0F0, tArray<MagicItem::EffectItem> * effectArray);
	DEFINE_MEMBER_FN(CreateDefensiveEnchantment, EnchantmentItem *, 0x0059F190, tArray<MagicItem::EffectItem> * effectArray);
	DEFINE_MEMBER_FN(CreatePoison, void, 0x0059F2E0, tArray<MagicItem::EffectItem> * effectArray, AlchemyItem ** poison);
	DEFINE_MEMBER_FN(CreatePotion, void, 0x0059F230, AlchemyItem ** potion, tArray<MagicItem::EffectItem> * effectArray);
	//DEFINE_MEMBER_FN(AddPersistentForm, void, 0x0068A0F0, TESForm *);
	DEFINE_MEMBER_FN(ScheduleForDeletion, void, 0x0059F6E0, TESForm *);
};
STATIC_ASSERT(sizeof(PersistentFormManager) == 0xD0);

// D8 or E0
class MenuTopicManager
{
public:
	virtual ~MenuTopicManager();
	virtual void Unk_01(void);

	NiPointer<TESObjectREFR> GetDialogueTarget();

	static MenuTopicManager * GetSingleton(void);

	BSTEventSink<void>	playerPositionEvent;	// 08
	UInt64				unk10[6];				// 10
	CRITICAL_SECTION	critSection;			// 40
	UInt32	talkingHandle;						// 68	 - init'd to g_InvalidRefHandler
	UInt32	handle2;							// 6C	 - init'd to g_InvalidRefHandler
	void*	unk70;								// 70
	UInt64	unk78;								// 78
	tArray<void *>	unk80;						// 80
	tArray<void *>	unk98;						// 98
	UInt8	unkB0;
	UInt8	unkB1;
	UInt8	unkB2;
	UInt8	unkB3;
	UInt8	unkB4;
	UInt8	unkB5;
	UInt8	unkB6;
	UInt8	unkB7;
	UInt8	unkB8;
	UInt8	unkB9;
	UInt8	unkBA;
	UInt8	unkBB;
	UInt16	padBC;
	tArray<void *>	unkC0;
};
STATIC_ASSERT(offsetof(MenuTopicManager, talkingHandle) == 0x68);

// 3D0
// Note: this class heavily changed in SE!
class BGSSaveLoadManager
{
public:
	enum
	{
		kEvent_Autosave = 1 << 0,
		kEvent_Save = 1 << 1,
		kEvent_Unk02 = 1 << 2,
		kEvent_Unk03 = 1 << 3,
		kEvent_Unk04 = 1 << 4,

		kEvent_Unk07 = 1 << 7,
	};

	static BGSSaveLoadManager *	GetSingleton(void);

	void	Save(const char * name);
	void	Load(const char * name);

	// used by Hooks_SaveLoad
	void	SaveGame_Hook(UInt64 *unk0);
	bool	LoadGame_Hook(UInt64 *unk0, UInt32 unk1, UInt32 unk2, void *unk3);
	void	ProcessEvents_Hook(void);
	void	DeleteSavegame_Hook(const char * saveName, UInt32 unk1);

	// use these when calling from a papyrus thread
	void	RequestSave(const char * name);
	void	RequestLoad(const char * name);

	MEMBER_FN_PREFIX(BGSSaveLoadManager);

	BSTEventSink<void*>	unk000;				// 000 - .?AV?$BSTEventSink@VBSSaveDataEvent@@@@
	BSTEventSink<void*>	unk008;				// 008 - .?AV?$BSTEventSink@UNameChangedEvent@RaceSexMenuEvent@@@@
	void*				unk010;				// 010

	// 58
	struct Data018
	{
		UnkArray	unk00;		// 00
		UnkArray	unk18;		// 18
		UnkArray	unk30;		// 30
		UInt64		unk48;		// 48
		UInt8		unk50;		// 50
		UInt8		pad51[7];	// 51
	};

	Data018				unk018;				// 018
	UInt32				unk070;				// 070
	UInt32				unk074;				// 074
	UInt32				unk078;				// 078
	UInt32				unk07C;				// 07C
	UInt32				unk080;				// 080
	UInt32				unk084;				// 084
	void*				unk088;				// 088
	UInt32				unk090;				// 090
	UInt32				unk094;				// 094
	void*				unk098;				// 098
	UInt32				unk0A0;				// 0A0
	UInt32				unk0A4;				// 0A4
	UInt32				unk0A8;				// 0A8
	UInt32				unk0AC;				// 0AC
	UInt32				unk0B0;				// 0B0
	UInt32				unk0B4;				// 0B4
	void*				unk0B8;				// 0B8
	UInt32				unk0C0;				// 0C0
	UInt32				unk0C4;				// 0C4
	UInt32				unk0C8;				// 0C8
	UInt32				unk0CC;				// 0CC
	UInt32				unk0D0;				// 0D0
	UInt32				unk0D4;				// 0D4
	UInt32				unk0D8;				// 0D8 - init'd to 0xFFFFFFFF
	UInt32				unk0DC;				// 0DC
	UInt32				unk0E0;				// 0E0
	UInt32				unk0E4;				// 0E4
	UnkArray			unk0E8;				// 0E8 - TODO: it is really a tArray of a structure of 0x48 bytes
	UnkArray			unk100;				// 100
	UInt8				unk118;				// 118
	UInt8				pad119[3];			// 119
	UInt32				unk11C;				// 11C
	UInt16				unk120;				// 120
	UInt16				pad122;				// 122
	UInt32				unk124;				// 124
	UInt64				startTickCount;		// 128 - GetTickCount when constructed, casted to 64 bits
	UInt8				unk130;				// 130 - init'd to 1
	UInt8				pad131[7];			// 131

	// 118
	struct Data138
	{
		char		latestSaveFileName[0x104];	// 000
		UInt32		pad104;						// 104
		Data138*	unk108;						// 108 - Init'd as self ptr. Might has been intended as a ptr to the char[] instead
		UInt8		unk110;						// 110 - init'd to 0
		UInt8		unk111;						// 111 - init'd to 0
		UInt8		unk112;						// 112 - init'd to 4
		UInt8		unk113;						// 113 - init'd to 1
		UInt32		pad114;						// 114

	};

	Data138			unk138;					// 138
	UInt32			unk250;					// 250 - init'd to FFFFFFFF   (unk28 in Skyrim32)
	bool			unk254;					// 254	(unk2C in Skyrim32)
	UInt8			pad255[3];				// 255
	void*			unk258;					// 258  (unk30 in Skyrim32)
	UInt8			unk260;					// 260 - init'd to 1  (unk34 in Skyrim32)
	UInt8			unk261;					// 261  (unk35 in Skyrim32)
	UInt8			pad262[2];				// 262
	UInt32			unk264;					// 264
	UInt32			unk268;					// 268
	UInt32			unk26C;					// 26C
	UInt8			unk270[0x2B0 - 0x270];	// 270

	// C0 .?AVThread@BGSSaveLoadManager@@
	class Thread
	{
	public:

		virtual ~Thread();
		virtual	UInt32	Unk01();
		virtual	void SetEvent();	// Just calls Windows SetEvent on hEvent

		CRITICAL_SECTION	criticalSection;	// 08
		HANDLE				hThread;			// 30
		HANDLE				currentThread;		// 38
		DWORD				threadId;			// 40
		DWORD				currentThreadId;	// 44
		UInt8				unk48;				// 48
		UInt8				pad49[3];			// 49
		UInt8				unk50;				// 50 - init'd to 1
		UInt8				unk51;				// 51
		UInt16				pad52;				// 52
		UInt32				unk54;				// 54 - init'd to 1
		HANDLE				hEvent;				// 58
		UInt8				unk60[0x60];		// 60 - TODO:  .?AV?$BSTCommonStaticMessageQueue@V?$BSTSmartPointer@VRequest@saveload@bgs@@UBSTSmartPointerIntrusiveRefCount@@@@$07@@
	};

	Thread				thread;				// 2B0
	UInt8				unk370[0x60];		// 370 - TODO:  .?AV?$BSTCommonStaticMessageQueue@V?$BSTSmartPointer@VRequest@saveload@bgs@@UBSTSmartPointerIntrusiveRefCount@@@@$07@@
	
	private:
	// SE: Save_Internal signature changed! Normal save: unk1=2, unk2=0
	DEFINE_MEMBER_FN(Save_Internal, bool, 0x00586DE0, int unk1, UInt32 unk2, const char * name);
	DEFINE_MEMBER_FN(Load_Internal, bool, 0x00587350, const char * name, int unk1, UInt32 unk2, UInt32 unk3);

	DEFINE_MEMBER_FN(SaveGame_HookTarget, void, 0x0057CCC0, UInt64 *unk0);
	DEFINE_MEMBER_FN(LoadGame_HookTarget, bool, 0x0057D1A0, UInt64 *unk0, UInt32 unk1, UInt32 unk2, void *unk3);

	DEFINE_MEMBER_FN(ProcessEvents_Internal, void, 0x00589A60);
	DEFINE_MEMBER_FN(DeleteSavegame, void, 0x00586D70, const char * saveName, UInt32 unk1);
};
STATIC_ASSERT(offsetof(BGSSaveLoadManager, thread) == 0x2B0);
STATIC_ASSERT(offsetof(BGSSaveLoadManager::Thread, hThread) == 0x30);
STATIC_ASSERT(sizeof(BGSSaveLoadManager::Thread) == 0xC0);
STATIC_ASSERT(sizeof(BGSSaveLoadManager) == 0x3D0);

class DefaultObjectList
{
public:
	enum {
		kNumDefaultObjects = 0x15B		// SE: there is one more than in Skyrim32 ("Mods Help FormList")
	};

	// 18 - Verified
	struct DefaultObject
	{
		const char	* description;	// 00
		UInt32		unk08;			// 08
		UInt32		key;			// 0C
		UInt32		unk10;			// 10
		UInt32		pad;			// 14
	};

	static DefaultObjectList * GetSingleton(void);

	DefaultObject	objects[kNumDefaultObjects];
};
STATIC_ASSERT(sizeof(DefaultObjectList::DefaultObject) == 0x18);

class FacePresetData
{
public:
	virtual ~FacePresetData();

	UInt32 unk08;	// Always 10?
	const char * gameSettingName;
};

class FacePresetList
{
public:
	enum {
		kNumPresets = 4
	};
	enum {
		kPreset_NoseType,
		kPreset_BrowType,
		kPreset_EyesType,
		kPreset_LipType
	};

	struct Preset
	{
		const char * presetName;
		FacePresetData * data;
	};

	Preset presets[kNumPresets];
};

class FaceMorphList
{
public:
	enum {
		kNumMorphs = 19
	};

	enum {
		kMorph_NoseShortLong = 0,
		kMorph_NoseDownUp,
		kMorph_JawUpDown,
		kMorph_JawNarrowWide,
		kMorph_JawBackForward,
		kMorph_CheeksDownUp,
		kMorph_CheeksInOut,
		kMorph_EyesMoveDownUp,
		kMorph_EyesMoveInOut,
		kMorph_BrowDownUp,
		kMorph_BrowInOut,
		kMorph_BrowBackForward,
		kMorph_LipMoveDownUp,
		kMorph_LipMoveInOut,
		kMorph_ChinThinWide,
		kMorph_ChinMoveUpDown,
		kMorph_OverbiteUnderbite,
		kMorph_EyesBackForward,
		kMorph_Vampire
	};

	struct Morph
	{
		UInt32 type;
		const char * lowerName;
		const char * upperName;
	};

	Morph morphs[kNumMorphs];
};
