#pragma once

#include <memory>
#include <list>
#include <map>
#include <set>
#include "../MIPSAssembler.h"
#include "../MIPS.h"
#include "../ELF.h"
#include "../OsStructManager.h"
#include "../OsVariableWrapper.h"
#include "Iop_BiosBase.h"
#include "Iop_BiosStructs.h"
#include "Iop_SifMan.h"
#include "Iop_SifCmd.h"
#include "Iop_Ioman.h"
#include "Iop_Cdvdman.h"
#include "Iop_Stdio.h"
#include "Iop_Sysmem.h"
#include "Iop_Modload.h"
#include "Iop_Loadcore.h"
#include "Iop_LibSd.h"
#ifdef _IOP_EMULATE_MODULES
#include "Iop_FileIo.h"
#include "Iop_PadMan.h"
#include "Iop_MtapMan.h"
#include "Iop_Cdvdfsv.h"
#include "Iop_McServ.h"
#include "Iop_Usbd.h"
#include "Iop_PowerOff.h"
#endif

class CIopBios : public Iop::CBiosBase
{
public:
	enum KERNEL_RESULT_CODES
	{
		KERNEL_RESULT_OK = 0,
		KERNEL_RESULT_ERROR = -1,
		KERNEL_RESULT_ERROR_ILLEGAL_CONTEXT = -100,
		KERNEL_RESULT_ERROR_ILLEGAL_INTRCODE = -101,
		KERNEL_RESULT_ERROR_CPUDI = -102,
		KERNEL_RESULT_ERROR_INTRDISABLE = -103,
		KERNEL_RESULT_ERROR_FOUND_HANDLER = -104,
		KERNEL_RESULT_ERROR_NOTFOUND_HANDLER = -105,
		KERNEL_RESULT_ERROR_NO_TIMER = -150,
		KERNEL_RESULT_ERROR_ILLEGAL_TIMERID = -151,
		KERNEL_RESULT_ERROR_UNKNOWN_MODULE = -202,
		KERNEL_RESULT_ERROR_NO_MEMORY = -400,
		KERNEL_RESULT_ERROR_ILLEGAL_ATTR = -401,
		KERNEL_RESULT_ERROR_ILLEGAL_ENTRY = -402,
		KERNEL_RESULT_ERROR_ILLEGAL_PRIORITY = -403,
		KERNEL_RESULT_ERROR_ILLEGAL_THID = -406,
		KERNEL_RESULT_ERROR_UNKNOWN_THID = -407,
		KERNEL_RESULT_ERROR_UNKNOWN_SEMAID = -408,
		KERNEL_RESULT_ERROR_UNKNOWN_EVFID = -409,
		KERNEL_RESULT_ERROR_UNKNOWN_MBXID = -410,
		KERNEL_RESULT_ERROR_UNKNOWN_VPLID = -411,
		KERNEL_RESULT_ERROR_UNKNOWN_FPLID = -412,
		KERNEL_RESULT_ERROR_NOT_DORMANT = -414,
		KERNEL_RESULT_ERROR_NOT_WAIT = -416,
		KERNEL_RESULT_ERROR_RELEASE_WAIT = -418,
		KERNEL_RESULT_ERROR_SEMA_ZERO = -419,
		KERNEL_RESULT_ERROR_SEMA_OVF = -420,
		KERNEL_RESULT_ERROR_EVF_CONDITION = -421,
		KERNEL_RESULT_ERROR_EVF_ILLEGAL_PAT = -423,
		KERNEL_RESULT_ERROR_MBX_NOMSG = -424,
		KERNEL_RESULT_ERROR_WAIT_DELETE = -425,
		KERNEL_RESULT_ERROR_ILLEGAL_MEMBLOCK = -426,
		KERNEL_RESULT_ERROR_ILLEGAL_MEMSIZE = -427,
	};

	enum CONTROL_BLOCK
	{
		CONTROL_BLOCK_START = 0x100,
		CONTROL_BLOCK_END = 0x10000,
	};

	struct THREADCONTEXT
	{
		uint32 gpr[0x20];
		uint32 epc;
		uint32 delayJump;
	};

	struct THREAD
	{
		uint32 isValid;
		uint32 id;
		uint32 initPriority;
		uint32 priority;
		uint32 optionData;
		uint32 attributes;
		uint32 threadProc;
		THREADCONTEXT context;
		uint32 status;
		uint32 waitObjectId;
		uint32 waitEventFlagMode;
		uint32 waitEventFlagMask;
		uint32 waitEventFlagResultPtr;
		uint32 waitMessageBoxResultPtr;
		uint32 wakeupCount;
		uint32 stackBase;
		uint32 stackSize;
		uint32 nextThreadId;
		uint64 nextActivateTime;
	};

	enum THREAD_STATUS
	{
		THREAD_STATUS_DORMANT = 1,
		THREAD_STATUS_RUNNING,
		THREAD_STATUS_SLEEPING,
		THREAD_STATUS_WAITING_SEMAPHORE,
		THREAD_STATUS_WAITING_EVENTFLAG,
		THREAD_STATUS_WAITING_MESSAGEBOX,
		THREAD_STATUS_WAITING_FPL,
		THREAD_STATUS_WAIT_VBLANK_START,
		THREAD_STATUS_WAIT_VBLANK_END,
		THREAD_STATUS_WAIT_CDSYNC,
	};

	struct THREAD_INFO
	{
		uint32 attributes;
		uint32 option;
		uint32 status;
		uint32 entryPoint;
		uint32 stackAddr;
		uint32 stackSize;
		uint32 gp;
		uint32 initPriority;
		uint32 currentPriority;
		uint32 waitType;
		uint32 waitId;
		uint32 wakeupCount;
		uint32 regContextAddr;
		uint32 reserved[4];
	};

	struct MODULE_INFO
	{
		enum
		{
			MAX_NAME_SIZE = 56,
		};

		char name[MAX_NAME_SIZE];
		uint16 version;
		uint16 flags;
		uint32 id;
		uint32 entryPoint;
		uint32 gp;
		uint32 textSectionAddr;
		uint32 textSectionSize;
		uint32 dataSectionSize;
		uint32 bssSectionSize;
		uint32 reserved[2];
	};

	enum class MODULESTARTREQUEST_SOURCE
	{
		LOCAL,
		REMOTE,
	};

	CIopBios(CMIPS&, uint8*, uint8*);
	virtual ~CIopBios();

	int32 LoadModuleFromPath(const char*, uint32 = ~0U, bool = true);
	int32 LoadModuleFromAddress(uint32, uint32 = ~0U, bool = true);
	int32 LoadModuleFromHost(uint8*);
	int32 UnloadModule(uint32);
	int32 StartModule(MODULESTARTREQUEST_SOURCE, uint32, const char*, const char*, uint32);
	int32 StopModule(MODULESTARTREQUEST_SOURCE, uint32);
	bool CanStopModule(uint32) const;
	bool IsModuleHle(uint32) const;
	int32 SearchModuleByName(const char*) const;
	int32 ReferModuleStatus(uint32, uint32);
	void ProcessModuleReset(const std::string&);

	void SetDefaultImageVersion(uint32);
	bool TryGetImageVersionFromPath(const std::string&, unsigned int*);
	bool TryGetImageVersionFromContents(const std::string&, unsigned int*);

	void HandleException() override;
	void HandleInterrupt() override;

	void Reschedule();
	void CheckReschedule();

	void CountTicks(uint32) override;
	uint64 GetCurrentTime() const;
	uint64 MilliSecToClock(uint32);
	uint64 MicroSecToClock(uint32);
	uint64 ClockToMicroSec(uint64);

	void NotifyVBlankStart() override;
	void NotifyVBlankEnd() override;

	void Reset(uint32, const Iop::SifManPtr&);

	void PreLoadState() override;
	void SaveState(Framework::CZipArchiveWriter&) override;
	void LoadState(Framework::CZipArchiveReader&) override;

	bool IsIdle() override;

	Iop::CSysmem* GetSysmem();
	Iop::CIoman* GetIoman();
	Iop::CSifMan* GetSifman();
	Iop::CSifCmd* GetSifcmd();
	Iop::CCdvdman* GetCdvdman();
	Iop::CLoadcore* GetLoadcore();
#ifdef _IOP_EMULATE_MODULES
	Iop::CPadMan* GetPadman();
	Iop::CCdvdfsv* GetCdvdfsv();
	Iop::CMcServ* GetMcServ();
	Iop::CUsbd* GetUsbd();
#endif
	bool RegisterModule(const Iop::ModulePtr&);
	bool ReleaseModule(const std::string&);

	void RegisterHleModuleReplacement(const std::string&, const Iop::ModulePtr&);

	uint32 CreateThread(uint32, uint32, uint32, uint32, uint32);
	int32 DeleteThread(uint32);
	int32 StartThread(uint32, uint32);
	int32 StartThreadArgs(uint32, uint32, uint32);
	void ExitThread();
	uint32 TerminateThread(uint32);
	int32 DelayThread(uint32);
	void DelayThreadTicks(uint32);
	uint32 SetAlarm(uint32, uint32, uint32);
	uint32 CancelAlarm(uint32, uint32, bool);
	THREAD* GetThread(uint32);
	int32 GetCurrentThreadId();
	int32 GetCurrentThreadIdRaw() const;
	int32 ChangeThreadPriority(uint32, uint32);
	uint32 ReferThreadStatus(uint32, uint32, bool);
	int32 SleepThread();
	uint32 WakeupThread(uint32, bool);
	int32 CancelWakeupThread(uint32, bool);
	int32 RotateThreadReadyQueue(uint32);
	int32 ReleaseWaitThread(uint32, bool);

	int32 RegisterVblankHandler(uint32, uint32, uint32, uint32);
	int32 ReleaseVblankHandler(uint32, uint32);
	int32 FindVblankHandlerByLineAndPtr(uint32 startEnd, uint32 handlerPtr);
	void SleepThreadTillVBlankStart();
	void SleepThreadTillVBlankEnd();

	uint32 CreateSemaphore(uint32, uint32, uint32, uint32);
	uint32 DeleteSemaphore(uint32);
	uint32 SignalSemaphore(uint32, bool);
	uint32 WaitSemaphore(uint32);
	uint32 PollSemaphore(uint32);
	uint32 ReferSemaphoreStatus(uint32, uint32);
	bool SemaReleaseSingleThread(uint32, bool);

	uint32 CreateEventFlag(uint32, uint32, uint32);
	uint32 DeleteEventFlag(uint32);
	uint32 SetEventFlag(uint32, uint32, bool);
	uint32 ClearEventFlag(uint32, uint32);
	uint32 WaitEventFlag(uint32, uint32, uint32, uint32);
	uint32 PollEventFlag(uint32, uint32, uint32, uint32);
	uint32 ReferEventFlagStatus(uint32, uint32);
	bool ProcessEventFlag(uint32, uint32&, uint32, uint32*);

	uint32 CreateMessageBox();
	uint32 DeleteMessageBox(uint32);
	uint32 SendMessageBox(uint32, uint32, bool);
	uint32 ReceiveMessageBox(uint32, uint32);
	uint32 PollMessageBox(uint32, uint32);
	uint32 ReferMessageBoxStatus(uint32, uint32);

	uint32 CreateFpl(uint32);
	uint32 DeleteFpl(uint32);
	uint32 AllocateFpl(uint32);
	uint32 pAllocateFpl(uint32);
	uint32 FreeFpl(uint32, uint32);

	uint32 CreateVpl(uint32);
	uint32 DeleteVpl(uint32);
	uint32 AllocateVpl(uint32, uint32);
	uint32 pAllocateVpl(uint32, uint32);
	uint32 FreeVpl(uint32, uint32);
	uint32 ReferVplStatus(uint32, uint32);
	uint32 GetVplFreeSize(uint32);

	void WaitCdSync();
	void ReleaseWaitCdSync();

	int32 RegisterIntrHandler(uint32, uint32, uint32, uint32);
	int32 ReleaseIntrHandler(uint32);
	int32 FindIntrHandler(uint32);

	int32 TriggerCallback(uint32 address, uint32 arg0 = 0, uint32 arg1 = 0, uint32 arg2 = 0, uint32 arg3 = 0);

#ifdef DEBUGGER_INCLUDED
	void LoadDebugTags(Framework::Xml::CNode*) override;
	void SaveDebugTags(Framework::Xml::CNode*) override;

	BiosDebugModuleInfoArray GetModulesDebugInfo() const override;
	BiosDebugObjectInfoMap GetBiosObjectsDebugInfo() const override;
	BiosDebugObjectArray GetBiosObjects(uint32) const override;
#endif

	typedef Framework::CSignal<void(const char*)> ModuleLoadedEvent;
	typedef Framework::CSignal<void(uint32)> ModuleStartedEvent;

	ModuleLoadedEvent OnModuleLoaded;
	ModuleStartedEvent OnModuleStarted;

private:
	enum
	{
		DEFAULT_IMAGE_VERSION = 1000,
	};

	enum DEFAULT_STACKSIZE
	{
		DEFAULT_STACKSIZE = 0x4000,
	};

	enum DEFAULT_PRIORITY
	{
		DEFAULT_PRIORITY = 64,
	};

	enum MODULE_INIT_PRIORITY
	{
		MODULE_INIT_PRIORITY = 8,
	};

	enum class MODULE_STATE : uint32
	{
		STOPPED,
		STARTED,
		HLE,
	};

	enum class MODULE_RESIDENT_STATE : uint32
	{
		RESIDENT_END = 0,
		NO_RESIDENT_END = 1,
		REMOVABLE_RESIDENT_END = 2,
	};

	enum
	{
		MAX_THREAD = 96,
		MAX_MEMORYBLOCK = 1024,
		MAX_SEMAPHORE = 128,
		MAX_EVENTFLAG = 128,
		MAX_INTRHANDLER = 32,
		MAX_VBLANKHANDLER = 8,
		MAX_MESSAGEBOX = 32,
		MAX_FPL = 16,
		MAX_VPL = 16,
		MAX_MODULESTARTREQUEST = 32,
		MAX_LOADEDMODULE = 48,
	};

	enum WEF_FLAGS
	{
		WEF_AND = 0x00,
		WEF_OR = 0x01,
		WEF_CLEAR = 0x10,
	};

	struct SEMAPHORE
	{
		uint32 isValid;
		uint32 id;
		uint32 count;
		uint32 maxCount;
		uint32 waitCount;
		uint32 attrib;
		uint32 option;
	};

	struct SEMAPHORE_STATUS
	{
		uint32 attrib;
		uint32 option;
		uint32 initCount;
		uint32 maxCount;
		uint32 currentCount;
		uint32 numWaitThreads;
	};

	struct EVENTFLAG
	{
		uint32 isValid;
		uint32 id;
		uint32 attributes;
		uint32 options;
		uint32 value;
	};

	struct EVENTFLAGINFO
	{
		uint32 attributes;
		uint32 options;
		uint32 initBits;
		uint32 currBits;
		uint32 numThreads;
	};

	struct INTRHANDLER
	{
		uint32 isValid;
		uint32 line;
		uint32 mode;
		uint32 handler;
		uint32 arg;
	};

	struct VBLANKHANDLER
	{
		uint32 isValid;
		uint32 type;
		uint32 handler;
		uint32 arg;
	};
	static_assert(sizeof(VBLANKHANDLER) == 0x10, "Size of VBLANKHANDLER must be 16 bytes. AssembleVblankHandler relies on this.");

	struct MESSAGEBOX
	{
		uint32 isValid;
		uint32 nextMsgPtr;
		uint32 numMessage;
	};

	struct MESSAGEBOX_STATUS
	{
		uint32 attr;
		uint32 option;
		uint32 numWaitThread;
		uint32 numMessage;
		uint32 messagePtr;
		uint32 unused[2];
	};

	struct MESSAGE_HEADER
	{
		uint32 nextMsgPtr;
		uint8 priority;
		uint8 unused[3];
	};

	enum FPL_ATTR
	{
		FPL_ATTR_THFIFO = 0x000,
		FPL_ATTR_THPRI = 0x001,
		FPL_ATTR_THMODE_MASK = 0x001,
		FPL_ATTR_MEMBTM = 0x200,
		FPL_ATTR_MEMMODE_MASK = 0x200,
		FPL_ATTR_VALID_MASK = (FPL_ATTR_THMODE_MASK | FPL_ATTR_MEMMODE_MASK)
	};

	struct FPL_PARAM
	{
		uint32 attr;
		uint32 option;
		uint32 blockSize;
		uint32 blockCount;
	};

	struct FPL
	{
		uint32 isValid;
		uint32 attr;
		uint32 option;
		uint32 poolPtr;
		uint32 blockSize;
		uint32 blockCount;
		uint32 waitCount;
	};

	struct VPL
	{
		uint32 isValid;
		uint32 attr;
		uint32 option;
		uint32 poolPtr;
		uint32 size;
		uint32 headBlockId;
	};

	enum VPL_ATTR
	{
		VPL_ATTR_THFIFO = 0x000,
		VPL_ATTR_THPRI = 0x001,
		VPL_ATTR_THMODE_MASK = 0x001,
		VPL_ATTR_MEMBTM = 0x200,
		VPL_ATTR_MEMMODE_MASK = 0x200,
		VPL_ATTR_VALID_MASK = (VPL_ATTR_THMODE_MASK | VPL_ATTR_MEMMODE_MASK)
	};

	struct VPL_PARAM
	{
		uint32 attr;
		uint32 option;
		uint32 size;
	};

	struct VPL_STATUS
	{
		uint32 attr;
		uint32 option;
		uint32 size;
		uint32 freeSize;
		uint32 numWaitThreads;
		uint32 unused[3];
	};

	struct MODULESTARTREQUEST
	{
		enum
		{
			INVALID_PTR = -1,
		};

		enum
		{
			MAX_PATH_SIZE = 256,
			MAX_ARGS_SIZE = 256
		};

		uint32 nextPtr;
		uint32 moduleId;
		uint32 stopRequest;
		int32 requesterThreadId;
		char path[MAX_PATH_SIZE];
		uint32 argsLength;
		char args[MAX_ARGS_SIZE];
	};

	struct LOADEDMODULE
	{
		enum
		{
			MAX_NAME_SIZE = 0x100,
		};

		uint32 isValid;
		char name[MAX_NAME_SIZE];
		uint32 version;
		uint32 start;
		uint32 end;
		uint32 entryPoint;
		uint32 gp;
		uint32 ownsMemory;
		MODULE_STATE state;
		MODULE_RESIDENT_STATE residentState;
	};

	struct IOPMOD
	{
		uint32 module;
		uint32 startAddress;
		uint32 gp;
		uint32 textSectionSize;
		uint32 dataSectionSize;
		uint32 bssSectionSize;
		uint16 moduleVersion;
		char moduleName[256];
	};

	enum
	{
		IOPMOD_SECTION_ID = 0x70000080,
	};

	enum
	{
		ET_SCE_IOPRELEXEC = 0xFF80,
		ET_SCE_IOPRELEXEC2 = 0xFF81
	};

	enum
	{
		R_MIPSSCE_MHI16 = 0xFA,
		R_MIPSSCE_ADDEND = 0xFB
	};

	struct SYSTEM_INTRHANDLER
	{
		uint32 handler;
		uint32 paramPtr;
	};
	static_assert(sizeof(SYSTEM_INTRHANDLER) == 0x8, "Size of SYSTEM_INTRHANDLER must be 8 bytes. Fixed PS2 structure, and we use it for array magic iteration.");

	typedef COsStructManager<THREAD> ThreadList;
	typedef COsStructManager<Iop::MEMORYBLOCK> MemoryBlockList;
	typedef COsStructManager<SEMAPHORE> SemaphoreList;
	typedef COsStructManager<EVENTFLAG> EventFlagList;
	typedef COsStructManager<INTRHANDLER> IntrHandlerList;
	typedef COsStructManager<VBLANKHANDLER> VblankHandlerList;
	typedef COsStructManager<MESSAGEBOX> MessageBoxList;
	typedef COsStructManager<FPL> FplList;
	typedef COsStructManager<VPL> VplList;
	typedef COsStructManager<LOADEDMODULE> LoadedModuleList;
	typedef std::map<std::string, Iop::ModulePtr, std::less<>> IopModuleMapType;
	typedef std::set<Iop::CModule*> ModuleSet;
	typedef std::pair<uint32, uint32> ExecutableRange;

	void LoadThreadContext(uint32);
	void SaveThreadContext(uint32);
	uint32 GetNextReadyThread();
	void ReturnFromException();

	void LinkThread(uint32);
	void UnlinkThread(uint32);

	uint32& ThreadLinkHead() const;
	uint64& CurrentTime() const;
	uint32& ModuleStartRequestHead() const;
	uint32& ModuleStartRequestFree() const;

	int32 LoadModule(CELF32&, const char*, uint32, bool);
	uint32 LoadExecutable(CELF32&, ExecutableRange&, uint32);
	unsigned int GetElfProgramToLoad(CELF32&);
	void RelocateElf(CELF32&, uint32, uint32);
	std::string_view ReadModuleName(const uint8*);
	std::string_view ReadModuleName(uint32);
	void DeleteModules();

	void UnloadUserComponents();

	int32 LoadHleModule(const Iop::ModulePtr&);
	void RegisterHleModule(const Iop::ModulePtr&);

	ModuleSet GetBuiltInModules() const;

	uint32 AssembleThreadFinish(CMIPSAssembler&);
	uint32 AssembleReturnFromException(CMIPSAssembler&);
	uint32 AssembleIdleFunction(CMIPSAssembler&);
	uint32 AssembleModuleStarterProc(CMIPSAssembler&);
	uint32 AssembleAlarmThreadProc(CMIPSAssembler&);
	uint32 AssembleVblankHandler(CMIPSAssembler&);

	void InitializeModuleStarter();
	void ProcessModuleStart();
	void FinishModuleStart();
	void RequestModuleStart(MODULESTARTREQUEST_SOURCE, bool, uint32, const char*, const char*, unsigned int);

	void PopulateSystemIntcHandlers();

#ifdef DEBUGGER_INCLUDED
	void PrepareModuleDebugInfo(CELF32&, const ExecutableRange&, const std::string&, const std::string&);
	BiosDebugModuleInfoIterator FindModuleDebugInfo(const std::string&);
	BiosDebugModuleInfoIterator FindModuleDebugInfo(uint32, uint32);
#endif

	CMIPS& m_cpu;
	uint8* m_ram = nullptr;
	uint32 m_ramSize = 0;
	uint8* m_spr = nullptr;
	uint32 m_threadFinishAddress;
	uint32 m_returnFromExceptionAddress;
	uint32 m_idleFunctionAddress;
	uint32 m_moduleStarterProcAddress;
	uint32 m_alarmThreadProcAddress;
	uint32 m_vblankHandlerAddress;

	uint32 m_defaultImageVersion = DEFAULT_IMAGE_VERSION;

	bool m_rescheduleNeeded = false;
	ThreadList m_threads;
	MemoryBlockList m_memoryBlocks;
	SemaphoreList m_semaphores;
	EventFlagList m_eventFlags;
	IntrHandlerList m_intrHandlers;
	VblankHandlerList m_vblankHandlers;
	MessageBoxList m_messageBoxes;
	FplList m_fpls;
	VplList m_vpls;
	LoadedModuleList m_loadedModules;

	MODULESTARTREQUEST m_moduleStartRequests[MAX_MODULESTARTREQUEST] = {};

	IopModuleMapType m_modules;

	OsVariableWrapper<uint32> m_currentThreadId;

#ifdef DEBUGGER_INCLUDED
	BiosDebugModuleInfoArray m_moduleTags;
#endif
	Iop::SifManPtr m_sifMan;
	Iop::SifCmdPtr m_sifCmd;
	Iop::StdioPtr m_stdio;
	Iop::IomanPtr m_ioman;
	Iop::CdvdmanPtr m_cdvdman;
	Iop::SysmemPtr m_sysmem;
	Iop::ModloadPtr m_modload;
	Iop::LoadcorePtr m_loadcore;
	Iop::ModulePtr m_libsd;
#ifdef _IOP_EMULATE_MODULES
	Iop::FileIoPtr m_fileIo;
	Iop::PadManPtr m_padman;
	Iop::MtapManPtr m_mtapman;
	Iop::McServPtr m_mcserv;
	Iop::CdvdfsvPtr m_cdvdfsv;
	Iop::PowerOffPtr m_powerOff;
	Iop::UsbdPtr m_usbd;

	std::map<std::string, Iop::ModulePtr> m_hleModules;
#endif
};

typedef std::shared_ptr<CIopBios> IopBiosPtr;
