#pragma once

#include <memory>
#include <list>
#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"
#endif

class CMipsExecutor;

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_FOUND_HANDLER    = -104,
		KERNEL_RESULT_ERROR_NOTFOUND_HANDLER = -105,
		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_EVFID    = -409,
		KERNEL_RESULT_ERROR_UNKNOWN_MBXID    = -410,
		KERNEL_RESULT_ERROR_UNKNOWN_VPLID    = -411,
		KERNEL_RESULT_ERROR_NOT_DORMANT      = -414,
		KERNEL_RESULT_ERROR_NOT_WAIT         = -416,
		KERNEL_RESULT_ERROR_SEMA_ZERO        = -419,
		KERNEL_RESULT_ERROR_EVF_CONDITION    = -421,
		KERNEL_RESULT_ERROR_EVF_ILLEGAL_PAT  = -423,
		KERNEL_RESULT_ERROR_MBX_NOMSG        = -424,
		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			waitSemaphore;
		uint32			waitEventFlag;
		uint32			waitEventFlagMode;
		uint32			waitEventFlagMask;
		uint32			waitEventFlagResultPtr;
		uint32			waitMessageBox;
		uint32			waitMessageBoxResultPtr;
		uint32			wakeupCount;
		uint32			stackBase;
		uint32			stackSize;
		uint32			nextThreadId;
		uint64			nextActivateTime;
	};

	enum THREAD_STATUS
	{
		THREAD_STATUS_DORMANT				= 1,
		THREAD_STATUS_RUNNING				= 2,
		THREAD_STATUS_SLEEPING				= 3,
		THREAD_STATUS_WAITING_SEMAPHORE		= 4,
		THREAD_STATUS_WAITING_EVENTFLAG		= 5,
		THREAD_STATUS_WAITING_MESSAGEBOX	= 6,
		THREAD_STATUS_WAIT_VBLANK_START		= 7,
		THREAD_STATUS_WAIT_VBLANK_END		= 8,
	};

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

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

	int32						LoadModule(const char*);
	int32						LoadModule(uint32);
	int32						LoadModuleFromHost(uint8*);
	int32						UnloadModule(uint32);
	int32						StartModule(uint32, const char*, const char*, uint32);
	int32						StopModule(uint32);
	bool						IsModuleHle(uint32) const;
	int32						SearchModuleByName(const char*) const;
	void						ProcessModuleReset(const std::string&);

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

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

	void						Reschedule();

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

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

	void						Reset(const Iop::SifManPtr&);

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

	bool						IsIdle() override;

	Iop::CIoman*				GetIoman();
	Iop::CCdvdman*				GetCdvdman();
	Iop::CLoadcore*				GetLoadcore();
#ifdef _IOP_EMULATE_MODULES
	Iop::CPadMan*				GetPadman();
	Iop::CCdvdfsv*				GetCdvdfsv();
#endif
	bool						RegisterModule(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);
	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						ReleaseWaitThread(uint32, bool);

	void						SleepThreadTillVBlankStart();
	void						SleepThreadTillVBlankEnd();

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

	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						CreateVpl(uint32);
	uint32						DeleteVpl(uint32);
	uint32						pAllocateVpl(uint32, uint32);
	uint32						FreeVpl(uint32, uint32);
	uint32						ReferVplStatus(uint32, uint32);
	uint32						GetVplFreeSize(uint32);

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

	void						TriggerCallback(uint32 address, uint32 arg0, uint32 arg1);

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

	BiosDebugModuleInfoArray	GetModulesDebugInfo() const override;
	BiosDebugThreadInfoArray	GetThreadsDebugInfo() const override;
#endif

	typedef boost::signals2::signal<void (uint32)> ModuleStartedEvent;

	ModuleStartedEvent			OnModuleStarted;

private:
	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				= 128,
		MAX_MEMORYBLOCK			= 256,
		MAX_SEMAPHORE			= 128,
		MAX_EVENTFLAG			= 64,
		MAX_INTRHANDLER			= 32,
		MAX_MESSAGEBOX			= 32,
		MAX_VPL					= 16,
		MAX_MODULESTARTREQUEST	= 32,
		MAX_LOADEDMODULE		= 32,
	};

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

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

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

	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 
		{
			MAX_PATH_SIZE = 256,
			MAX_ARGS_SIZE = 256
		};

		uint32			nextPtr;
		uint32			moduleId;
		uint32			stopRequest;
		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					start;
		uint32					end;
		uint32					entryPoint;
		uint32					gp;
		MODULE_STATE			state;
		MODULE_RESIDENT_STATE	residentState;
	};

	struct IOPMOD
	{
		uint32			module;
		uint32			startAddress;
		uint32			gp;
		uint32			textSectionSize;
		uint32			dataSectionSize;
		uint32			bssSectionSize;
		uint16			moduleStructAttr;
		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
	};

	typedef COsStructManager<THREAD> ThreadList;
	typedef COsStructManager<Iop::MEMORYBLOCK> MemoryBlockList;
	typedef COsStructManager<SEMAPHORE> SemaphoreList;
	typedef COsStructManager<EVENTFLAG> EventFlagList;
	typedef COsStructManager<INTRHANDLER> IntrHandlerList;
	typedef COsStructManager<MESSAGEBOX> MessageBoxList;
	typedef COsStructManager<VPL> VplList;
	typedef COsStructManager<LOADEDMODULE> LoadedModuleList;
	typedef std::map<std::string, Iop::ModulePtr> IopModuleMapType;
	typedef std::pair<uint32, uint32> ExecutableRange;

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

	uint32							FindIntrHandler(uint32);

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

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

	int32							LoadModule(CELF&, const char*);
	uint32							LoadExecutable(CELF&, ExecutableRange&);
	unsigned int					GetElfProgramToLoad(CELF&);
	void							RelocateElf(CELF&, uint32);
	std::string						ReadModuleName(uint32);
	void							DeleteModules();

	int32							LoadHleModule(const Iop::ModulePtr&);

	uint32							AssembleThreadFinish(CMIPSAssembler&);
	uint32							AssembleReturnFromException(CMIPSAssembler&);
	uint32							AssembleIdleFunction(CMIPSAssembler&);
	uint32							AssembleModuleStarterThreadProc(CMIPSAssembler&);
	uint32							AssembleAlarmThreadProc(CMIPSAssembler&);

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

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

	CMIPS&							m_cpu;
	CMipsExecutor&					m_cpuExecutor;
	uint8*							m_ram = nullptr;
	uint32							m_ramSize;
	uint8*							m_spr = nullptr;
	uint32							m_threadFinishAddress;
	uint32							m_returnFromExceptionAddress;
	uint32							m_idleFunctionAddress;
	uint32							m_moduleStarterThreadProcAddress;
	uint32							m_alarmThreadProcAddress;

	uint32							m_moduleStarterThreadId;

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

	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::CdvdfsvPtr					m_cdvdfsv;
#endif
};

typedef std::shared_ptr<CIopBios> IopBiosPtr;
