#include <cstdio>
#include <exception>
#include <memory>
#include <climits>
#include <fenv.h>
#include "FpUtils.h"
#include "make_unique.h"
#include "string_format.h"
#include "PS2VM.h"
#include "PS2VM_Preferences.h"
#include "ee/PS2OS.h"
#include "ee/EeExecutor.h"
#include "Ps2Const.h"
#include "iop/Iop_SifManPs2.h"
#include "iop/UsbBuzzerDevice.h"
#include "StdStream.h"
#include "StdStreamUtils.h"
#include "states/MemoryStateFile.h"
#include "zip/ZipArchiveWriter.h"
#include "zip/ZipArchiveReader.h"
#include "xml/Node.h"
#include "xml/Writer.h"
#include "xml/Parser.h"
#include "AppConfig.h"
#include "PathUtils.h"
#include "ThreadUtils.h"
#include "iop/IopBios.h"
#include "iop/ioman/HardDiskDevice.h"
#include "iop/ioman/OpticalMediaDevice.h"
#include "iop/ioman/PreferenceDirectoryDevice.h"
#include "Log.h"
#include "DiskUtils.h"
#include "GameConfig.h"
#ifdef __ANDROID__
#include "android/JavaVM.h"
#endif

#define LOG_NAME ("ps2vm")

#define THREAD_NAME ("PS2VM Thread")

#define STATE_VM_TIMING_XML ("vm_timing.xml")
#define STATE_VM_TIMING_VBLANK_TICKS ("vblankTicks")
#define STATE_VM_TIMING_IN_VBLANK ("inVblank")
#define STATE_VM_TIMING_EE_EXECUTION_TICKS ("eeExecutionTicks")
#define STATE_VM_TIMING_IOP_EXECUTION_TICKS ("iopExecutionTicks")
#define STATE_VM_TIMING_SPU_UPDATE_TICKS ("spuUpdateTicks")

#define PREF_PS2_ROM0_DIRECTORY_DEFAULT ("vfs/rom0")
#define PREF_PS2_HOST_DIRECTORY_DEFAULT ("vfs/host")
#define PREF_PS2_MC0_DIRECTORY_DEFAULT ("vfs/mc0")
#define PREF_PS2_MC1_DIRECTORY_DEFAULT ("vfs/mc1")
#define PREF_PS2_HDD_DIRECTORY_DEFAULT ("vfs/hdd")
#define PREF_PS2_ARCADEROMS_DIRECTORY_DEFAULT ("arcaderoms")

CPS2VM::CPS2VM()
    : m_eeProfilerZone(CProfiler::GetInstance().RegisterZone("EE"))
    , m_iopProfilerZone(CProfiler::GetInstance().RegisterZone("IOP"))
    , m_spuProfilerZone(CProfiler::GetInstance().RegisterZone("SPU"))
    , m_gsSyncProfilerZone(CProfiler::GetInstance().RegisterZone("GSSYNC"))
    , m_otherProfilerZone(CProfiler::GetInstance().RegisterZone("OTHER"))
{
	// clang-format off
	static const std::pair<const char*, const char*> basicDirectorySettings[] =
	{
		std::make_pair(PREF_PS2_ROM0_DIRECTORY, PREF_PS2_ROM0_DIRECTORY_DEFAULT),
		std::make_pair(PREF_PS2_HOST_DIRECTORY, PREF_PS2_HOST_DIRECTORY_DEFAULT),
		std::make_pair(PREF_PS2_MC0_DIRECTORY, PREF_PS2_MC0_DIRECTORY_DEFAULT),
		std::make_pair(PREF_PS2_MC1_DIRECTORY, PREF_PS2_MC1_DIRECTORY_DEFAULT),
		std::make_pair(PREF_PS2_HDD_DIRECTORY, PREF_PS2_HDD_DIRECTORY_DEFAULT),
		std::make_pair(PREF_PS2_ARCADEROMS_DIRECTORY, PREF_PS2_ARCADEROMS_DIRECTORY_DEFAULT),
	};
	// clang-format on

	for(const auto& basicDirectorySetting : basicDirectorySettings)
	{
		auto setting = basicDirectorySetting.first;
		auto path = basicDirectorySetting.second;

		auto absolutePath = CAppConfig::GetInstance().GetBasePath() / path;
		Framework::PathUtils::EnsurePathExists(absolutePath);
		CAppConfig::GetInstance().RegisterPreferencePath(setting, absolutePath);

		auto currentPath = CAppConfig::GetInstance().GetPreferencePath(setting);
		if(!fs::exists(currentPath))
		{
			CAppConfig::GetInstance().SetPreferencePath(setting, absolutePath);
		}
	}

	CAppConfig::GetInstance().RegisterPreferencePath(PREF_PS2_CDROM0_PATH, "");

	Framework::PathUtils::EnsurePathExists(GetStateDirectoryPath());

	CAppConfig::GetInstance().RegisterPreferenceBoolean(PREF_PS2_LIMIT_FRAMERATE, true);
	ReloadFrameRateLimit();

	CAppConfig::GetInstance().RegisterPreferenceInteger(PREF_AUDIO_SPUBLOCKCOUNT, 100);
	ReloadSpuBlockCountImpl();

	CAppConfig::GetInstance().RegisterPreferenceBoolean(PREF_PS2_ARCADE_IO_SERVER_ENABLED, false);
	CAppConfig::GetInstance().RegisterPreferenceInteger(PREF_PS2_ARCADE_IO_SERVER_PORT, 9876);
}

//////////////////////////////////////////////////
//Various Message Functions
//////////////////////////////////////////////////

void CPS2VM::CreateGSHandler(const CGSHandler::FactoryFunction& factoryFunction)
{
	m_mailBox.SendCall([this, factoryFunction]() { CreateGsHandlerImpl(factoryFunction); }, true);
}

CGSHandler* CPS2VM::GetGSHandler()
{
	return m_ee->m_gs;
}

void CPS2VM::DestroyGSHandler()
{
	if(m_ee->m_gs == nullptr) return;
	m_mailBox.SendCall([this]() { DestroyGsHandlerImpl(); }, true);
}

void CPS2VM::CreatePadHandler(const CPadHandler::FactoryFunction& factoryFunction)
{
	if(m_pad != nullptr) return;
	m_mailBox.SendCall([this, factoryFunction]() { CreatePadHandlerImpl(factoryFunction); }, true);
}

CPadHandler* CPS2VM::GetPadHandler()
{
	return m_pad;
}

bool CPS2VM::HasGunListener() const
{
	return m_gunListener != nullptr;
}

void CPS2VM::SetGunListener(CScreenPositionListener* listener)
{
	m_gunListener = listener;
}

void CPS2VM::ReportGunPosition(float x, float y)
{
	if(m_gunListener)
	{
		m_gunListener->SetScreenPosition(x, y);
	}
}

bool CPS2VM::HasTouchListener() const
{
	return m_touchListener != nullptr;
}

void CPS2VM::SetTouchListener(CScreenPositionListener* listener)
{
	m_touchListener = listener;
}

void CPS2VM::ReportTouchPosition(float x, float y)
{
	if(m_touchListener)
	{
		m_touchListener->SetScreenPosition(x, y);
	}
}

void CPS2VM::ReleaseScreenPosition()
{
	if(m_touchListener)
	{
		m_touchListener->ReleaseScreenPosition();
	}
}

void CPS2VM::DestroyPadHandler()
{
	if(m_pad == nullptr) return;
	m_mailBox.SendCall([this]() { DestroyPadHandlerImpl(); }, true);
}

void CPS2VM::CreateSoundHandler(const CSoundHandler::FactoryFunction& factoryFunction)
{
	if(m_soundHandler) return;
	std::exception_ptr exception;
	m_mailBox.SendCall([this, factoryFunction, &exception]() {
		try
		{
			CreateSoundHandlerImpl(factoryFunction);
		}
		catch(...)
		{
			exception = std::current_exception();
		}
	},
	                   true);
	if(exception)
	{
		std::rethrow_exception(exception);
	}
}

CSoundHandler* CPS2VM::GetSoundHandler()
{
	return m_soundHandler;
}

void CPS2VM::ReloadSpuBlockCount()
{
	m_mailBox.SendCall([this]() { ReloadSpuBlockCountImpl(); });
}

void CPS2VM::DestroySoundHandler()
{
	if(m_soundHandler == nullptr) return;
	m_mailBox.SendCall([this]() { DestroySoundHandlerImpl(); }, true);
}

void CPS2VM::SetEeFrequencyScale(uint32 numerator, uint32 denominator)
{
	m_eeFreqScaleNumerator = numerator;
	m_eeFreqScaleDenominator = denominator;
	ReloadFrameRateLimit();
}

void CPS2VM::ReloadFrameRateLimit()
{
	uint32 hRefreshRate = PS2::GS_NTSC_HSYNC_FREQ;
	uint32 vRefreshRate = 60;
	if(m_ee && m_ee->m_gs)
	{
		hRefreshRate = m_ee->m_gs->GetCrtHSyncFrequency();
		vRefreshRate = m_ee->m_gs->GetCrtFrameRate();
	}
	bool limitFrameRate = CAppConfig::GetInstance().GetPreferenceBoolean(PREF_PS2_LIMIT_FRAMERATE);
	m_frameLimiter.SetFrameRate(limitFrameRate ? vRefreshRate : 0);

	//At 1x scale, IOP runs 8 times slower than EE
	uint32 eeFreqScaled = PS2::EE_CLOCK_FREQ * m_eeFreqScaleNumerator / m_eeFreqScaleDenominator;
	m_iopTickStep = (m_eeTickStep / 8) * m_eeFreqScaleDenominator / m_eeFreqScaleNumerator;

	m_hblankTicksTotal = eeFreqScaled / hRefreshRate;

	uint32 frameTicks = eeFreqScaled / vRefreshRate;
	m_onScreenTicksTotal = frameTicks * 9 / 10;
	m_vblankTicksTotal = frameTicks / 10;

	m_spuUpdateTicksTotal = (static_cast<int64>(eeFreqScaled) << SPU_UPDATE_TICKS_PRECISION) / (static_cast<int64>(DST_SAMPLE_RATE));
	m_spuUpdateTicksTotal *= static_cast<int64>(SAMPLES_PER_UPDATE);
}

CVirtualMachine::STATUS CPS2VM::GetStatus() const
{
	return m_nStatus;
}

void CPS2VM::StepEe()
{
	if(GetStatus() == RUNNING) return;
	m_singleStepEe = true;
	m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true);
}

void CPS2VM::StepIop()
{
	if(GetStatus() == RUNNING) return;
	m_singleStepIop = true;
	m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true);
}

void CPS2VM::StepVu0()
{
	if(GetStatus() == RUNNING) return;
	m_singleStepVu0 = true;
	m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true);
}

void CPS2VM::StepVu1()
{
	if(GetStatus() == RUNNING) return;
	m_singleStepVu1 = true;
	m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true);
}

void CPS2VM::Resume()
{
	if(m_nStatus == RUNNING) return;
	m_mailBox.SendCall(std::bind(&CPS2VM::ResumeImpl, this), true);
	OnRunningStateChange();
}

void CPS2VM::Pause()
{
	if(m_nStatus == PAUSED) return;
	m_mailBox.SendCall(std::bind(&CPS2VM::PauseImpl, this), true);
	OnMachineStateChange();
	OnRunningStateChange();
}

void CPS2VM::PauseAsync()
{
	if(m_nStatus == PAUSED) return;
	m_mailBox.SendCall([this]() {
		PauseImpl();
		OnMachineStateChange();
		OnRunningStateChange();
	});
}

void CPS2VM::Reset(uint32 eeRamSize, uint32 iopRamSize)
{
	assert(m_nStatus == PAUSED);
	BeforeExecutableReloaded = ExecutableReloadedHandler();
	AfterExecutableReloaded = ExecutableReloadedHandler();
	m_eeRamSize = eeRamSize;
	m_iopRamSize = iopRamSize;
	ResetVM();
}

void CPS2VM::Initialize()
{
	m_nEnd = false;
	m_thread = std::thread([&]() { EmuThread(); });
	Framework::ThreadUtils::SetThreadName(m_thread, THREAD_NAME);
}

void CPS2VM::Destroy()
{
	m_mailBox.SendCall(std::bind(&CPS2VM::DestroyImpl, this));
	m_thread.join();
	DestroyVM();
}

fs::path CPS2VM::GetStateDirectoryPath()
{
	return CAppConfig::GetInstance().GetBasePath() / fs::path("states/");
}

fs::path CPS2VM::GenerateStatePath(unsigned int slot) const
{
	auto stateFileName = string_format("%s.st%d.zip", m_ee->m_os->GetExecutableName(), slot);
	return GetStateDirectoryPath() / fs::path(stateFileName);
}

std::future<bool> CPS2VM::SaveState(const fs::path& statePath)
{
	auto promise = std::make_shared<std::promise<bool>>();
	auto future = promise->get_future();
	m_mailBox.SendCall(
	    [this, promise, statePath]() {
		    auto result = SaveVMState(statePath);
		    promise->set_value(result);
	    });
	return future;
}

std::future<bool> CPS2VM::LoadState(const fs::path& statePath)
{
	auto promise = std::make_shared<std::promise<bool>>();
	auto future = promise->get_future();
	m_mailBox.SendCall(
	    [this, promise, statePath]() {
		    auto result = LoadVMState(statePath);
		    promise->set_value(result);
	    });
	return future;
}

CPS2VM::CPU_UTILISATION_INFO CPS2VM::GetCpuUtilisationInfo() const
{
	return m_cpuUtilisation;
}

#ifdef DEBUGGER_INCLUDED

#define TAGS_SECTION_TAGS ("tags")
#define TAGS_SECTION_EE_FUNCTIONS ("ee_functions")
#define TAGS_SECTION_EE_COMMENTS ("ee_comments")
#define TAGS_SECTION_EE_VARIABLES ("ee_variables")
#define TAGS_SECTION_VU1_FUNCTIONS ("vu1_functions")
#define TAGS_SECTION_VU1_COMMENTS ("vu1_comments")
#define TAGS_SECTION_IOP ("iop")
#define TAGS_SECTION_IOP_FUNCTIONS ("functions")
#define TAGS_SECTION_IOP_COMMENTS ("comments")
#define TAGS_SECTION_IOP_VARIABLES ("variables")

#define TAGS_PATH ("tags/")

fs::path CPS2VM::MakeDebugTagsPackagePath(const char* packageName)
{
	auto tagsPath = CAppConfig::GetInstance().GetBasePath() / fs::path(TAGS_PATH);
	Framework::PathUtils::EnsurePathExists(tagsPath);
	auto tagsPackagePath = tagsPath / (std::string(packageName) + std::string(".tags.xml"));
	return tagsPackagePath;
}

void CPS2VM::LoadDebugTags(const char* packageName)
{
	try
	{
		auto packagePath = MakeDebugTagsPackagePath(packageName);
		auto stream = Framework::CreateInputStdStream(packagePath.native());
		auto document = Framework::Xml::CParser::ParseDocument(stream);
		auto tagsNode = document->Select(TAGS_SECTION_TAGS);
		if(!tagsNode) return;
		m_ee->m_EE.m_Functions.Unserialize(tagsNode, TAGS_SECTION_EE_FUNCTIONS);
		m_ee->m_EE.m_Comments.Unserialize(tagsNode, TAGS_SECTION_EE_COMMENTS);
		m_ee->m_EE.m_Variables.Unserialize(tagsNode, TAGS_SECTION_EE_VARIABLES);
		m_ee->m_VU1.m_Functions.Unserialize(tagsNode, TAGS_SECTION_VU1_FUNCTIONS);
		m_ee->m_VU1.m_Comments.Unserialize(tagsNode, TAGS_SECTION_VU1_COMMENTS);
		{
			auto sectionNode = tagsNode->Select(TAGS_SECTION_IOP);
			if(sectionNode)
			{
				m_iop->m_cpu.m_Functions.Unserialize(sectionNode, TAGS_SECTION_IOP_FUNCTIONS);
				m_iop->m_cpu.m_Comments.Unserialize(sectionNode, TAGS_SECTION_IOP_COMMENTS);
				m_iop->m_cpu.m_Variables.Unserialize(sectionNode, TAGS_SECTION_IOP_VARIABLES);
				m_iop->m_bios->LoadDebugTags(sectionNode);
			}
		}
	}
	catch(...)
	{
	}
}

void CPS2VM::SaveDebugTags(const char* packageName)
{
	try
	{
		auto packagePath = MakeDebugTagsPackagePath(packageName);
		auto stream = Framework::CreateOutputStdStream(packagePath.native());
		auto document = std::make_unique<Framework::Xml::CNode>(TAGS_SECTION_TAGS, true);
		m_ee->m_EE.m_Functions.Serialize(document.get(), TAGS_SECTION_EE_FUNCTIONS);
		m_ee->m_EE.m_Comments.Serialize(document.get(), TAGS_SECTION_EE_COMMENTS);
		m_ee->m_EE.m_Variables.Serialize(document.get(), TAGS_SECTION_EE_VARIABLES);
		m_ee->m_VU1.m_Functions.Serialize(document.get(), TAGS_SECTION_VU1_FUNCTIONS);
		m_ee->m_VU1.m_Comments.Serialize(document.get(), TAGS_SECTION_VU1_COMMENTS);
		{
			auto iopNode = std::make_unique<Framework::Xml::CNode>(TAGS_SECTION_IOP, true);
			m_iop->m_cpu.m_Functions.Serialize(iopNode.get(), TAGS_SECTION_IOP_FUNCTIONS);
			m_iop->m_cpu.m_Comments.Serialize(iopNode.get(), TAGS_SECTION_IOP_COMMENTS);
			m_iop->m_cpu.m_Variables.Serialize(iopNode.get(), TAGS_SECTION_IOP_VARIABLES);
			m_iop->m_bios->SaveDebugTags(iopNode.get());
			document->InsertNode(std::move(iopNode));
		}
		Framework::Xml::CWriter::WriteDocument(stream, document.get());
	}
	catch(...)
	{
	}
}

#endif

//////////////////////////////////////////////////
//Non extern callable methods
//////////////////////////////////////////////////

void CPS2VM::ValidateThreadContext()
{
	FRAMEWORK_MAYBE_UNUSED auto currThreadId = std::this_thread::get_id();
	FRAMEWORK_MAYBE_UNUSED auto vmThreadId = m_thread.get_id();
	assert(vmThreadId == std::thread::id() || currThreadId == vmThreadId);
}

void CPS2VM::CreateVM()
{
	m_iop = std::make_unique<Iop::CSubSystem>(true);
	auto iopOs = dynamic_cast<CIopBios*>(m_iop->m_bios.get());

	m_ee = std::make_unique<Ee::CSubSystem>(m_iop->m_ram, *iopOs);
	m_OnRequestLoadExecutableConnection = m_ee->m_os->OnRequestLoadExecutable.Connect(std::bind(&CPS2VM::ReloadExecutable, this, std::placeholders::_1, std::placeholders::_2));
	m_OnExecutableChangeConnection = m_ee->m_os->OnExecutableChange.Connect(std::bind(&CPS2VM::OnExecutableChange, this));
	m_OnCrtModeChangeConnection = m_ee->m_os->OnCrtModeChange.Connect(std::bind(&CPS2VM::OnCrtModeChange, this));

	ResetVM();
}

void CPS2VM::ResetVM()
{
	assert(m_eeRamSize != 0);
	assert(m_iopRamSize != 0);

	assert(m_eeRamSize <= PS2::EE_RAM_SIZE);
	assert(m_iopRamSize <= PS2::IOP_RAM_SIZE);

	m_ee->Reset(m_eeRamSize);
	m_iop->Reset();

	if(m_ee->m_gs != NULL)
	{
		m_ee->m_gs->Reset();
	}

	{
		auto iopOs = dynamic_cast<CIopBios*>(m_iop->m_bios.get());
		assert(iopOs);

		iopOs->Reset(m_iopRamSize, std::make_shared<Iop::CSifManPs2>(m_ee->m_sif, m_ee->m_ram, m_iop->m_ram));

		iopOs->GetIoman()->RegisterDevice("rom0", std::make_shared<Iop::Ioman::CPreferenceDirectoryDevice>(PREF_PS2_ROM0_DIRECTORY));
		iopOs->GetIoman()->RegisterDevice("host", std::make_shared<Iop::Ioman::CPreferenceDirectoryDevice>(PREF_PS2_HOST_DIRECTORY));
		iopOs->GetIoman()->RegisterDevice("host0", std::make_shared<Iop::Ioman::CPreferenceDirectoryDevice>(PREF_PS2_HOST_DIRECTORY));
		iopOs->GetIoman()->RegisterDevice("mc0", std::make_shared<Iop::Ioman::CPreferenceDirectoryDevice>(PREF_PS2_MC0_DIRECTORY));
		iopOs->GetIoman()->RegisterDevice("mc1", std::make_shared<Iop::Ioman::CPreferenceDirectoryDevice>(PREF_PS2_MC1_DIRECTORY));
		iopOs->GetIoman()->RegisterDevice("cdrom", Iop::Ioman::DevicePtr(new Iop::Ioman::COpticalMediaDevice(m_cdrom0)));
		iopOs->GetIoman()->RegisterDevice("cdrom0", Iop::Ioman::DevicePtr(new Iop::Ioman::COpticalMediaDevice(m_cdrom0)));
		iopOs->GetIoman()->RegisterDevice("cdrom1", Iop::Ioman::DevicePtr(new Iop::Ioman::COpticalMediaDevice(m_cdrom0)));
		iopOs->GetIoman()->RegisterDevice("hdd0", std::make_shared<Iop::Ioman::CHardDiskDevice>());

		iopOs->GetLoadcore()->SetLoadExecutableHandler(std::bind(&CPS2OS::LoadExecutable, m_ee->m_os, std::placeholders::_1, std::placeholders::_2));
	}

	CDROM0_SyncPath();

	SetEeFrequencyScale(1, 1);

	m_hblankTicks = m_hblankTicksTotal;
	m_vblankTicks = m_onScreenTicksTotal;
	m_spuUpdateTicks = m_spuUpdateTicksTotal;
	m_inVblank = false;

	m_eeExecutionTicks = 0;
	m_iopExecutionTicks = 0;

	m_currentSpuBlock = 0;
	m_iop->m_spuCore0.SetDestinationSamplingRate(DST_SAMPLE_RATE);
	m_iop->m_spuCore1.SetDestinationSamplingRate(DST_SAMPLE_RATE);

	RegisterModulesInPadHandler();
	m_gunListener = nullptr;
	m_touchListener = nullptr;
}

void CPS2VM::DestroyVM()
{
	CDROM0_Reset();
}

bool CPS2VM::SaveVMState(const fs::path& statePath)
{
	if(m_ee->m_gs == NULL)
	{
		printf("PS2VM: GS Handler was not instancied. Cannot save state.\r\n");
		return false;
	}

	try
	{
		auto stateStream = Framework::CreateOutputStdStream(statePath.native());
		Framework::CZipArchiveWriter archive;

		m_ee->SaveState(archive);
		m_iop->SaveState(archive);
		m_ee->m_gs->SaveState(archive);
		SaveVmTimingState(archive);

		archive.Write(stateStream);
	}
	catch(...)
	{
		return false;
	}

	return true;
}

bool CPS2VM::LoadVMState(const fs::path& statePath)
{
	if(m_ee->m_gs == NULL)
	{
		printf("PS2VM: GS Handler was not instancied. Cannot load state.\r\n");
		return false;
	}

	try
	{
		auto stateStream = Framework::CreateInputStdStream(statePath.native());
		Framework::CZipArchiveReader archive(stateStream);

		try
		{
			m_ee->LoadState(archive);
			m_iop->LoadState(archive);
			m_ee->m_gs->LoadState(archive);
			LoadVmTimingState(archive);

			ReloadFrameRateLimit();
		}
		catch(...)
		{
			//Any error that occurs in the previous block is critical
			PauseImpl();
			throw;
		}
	}
	catch(...)
	{
		return false;
	}

	OnMachineStateChange();

	return true;
}

void CPS2VM::SaveVmTimingState(Framework::CZipArchiveWriter& archive)
{
	auto registerFile = std::make_unique<CRegisterStateFile>(STATE_VM_TIMING_XML);
	registerFile->SetRegister32(STATE_VM_TIMING_VBLANK_TICKS, m_vblankTicks);
	registerFile->SetRegister32(STATE_VM_TIMING_IN_VBLANK, m_inVblank);
	registerFile->SetRegister32(STATE_VM_TIMING_EE_EXECUTION_TICKS, m_eeExecutionTicks);
	registerFile->SetRegister32(STATE_VM_TIMING_IOP_EXECUTION_TICKS, m_iopExecutionTicks);
	registerFile->SetRegister64(STATE_VM_TIMING_SPU_UPDATE_TICKS, m_spuUpdateTicks);
	archive.InsertFile(std::move(registerFile));
}

void CPS2VM::LoadVmTimingState(Framework::CZipArchiveReader& archive)
{
	CRegisterStateFile registerFile(*archive.BeginReadFile(STATE_VM_TIMING_XML));
	m_vblankTicks = registerFile.GetRegister32(STATE_VM_TIMING_VBLANK_TICKS);
	m_inVblank = registerFile.GetRegister32(STATE_VM_TIMING_IN_VBLANK) != 0;
	m_eeExecutionTicks = registerFile.GetRegister32(STATE_VM_TIMING_EE_EXECUTION_TICKS);
	m_iopExecutionTicks = registerFile.GetRegister32(STATE_VM_TIMING_IOP_EXECUTION_TICKS);
	m_spuUpdateTicks = registerFile.GetRegister64(STATE_VM_TIMING_SPU_UPDATE_TICKS);
}

void CPS2VM::PauseImpl()
{
	m_nStatus = PAUSED;
}

void CPS2VM::ResumeImpl()
{
#ifdef DEBUGGER_INCLUDED
	m_ee->m_EE.m_executor->DisableBreakpointsOnce();
	m_iop->m_cpu.m_executor->DisableBreakpointsOnce();
	m_ee->m_VU1.m_executor->DisableBreakpointsOnce();
#endif
	m_nStatus = RUNNING;
}

void CPS2VM::DestroyImpl()
{
	DestroyGsHandlerImpl();
	DestroyPadHandlerImpl();
	DestroySoundHandlerImpl();
	m_nEnd = true;
}

void CPS2VM::CreateGsHandlerImpl(const CGSHandler::FactoryFunction& factoryFunction)
{
	auto gs = m_ee->m_gs;
	m_ee->m_gs = factoryFunction();
	m_ee->m_gs->SetIntc(&m_ee->m_intc);
	m_ee->m_gs->Initialize();
	m_ee->m_gs->SendGSCall([this]() {
		static_cast<CEeExecutor*>(m_ee->m_EE.m_executor.get())->AttachExceptionHandlerToThread();
	});
	if(gs)
	{
		m_ee->m_gs->Copy(gs);
		gs->Release();
		delete gs;
	}
}

void CPS2VM::DestroyGsHandlerImpl()
{
	if(m_ee->m_gs == nullptr) return;
	m_ee->m_gs->Release();
	delete m_ee->m_gs;
	m_ee->m_gs = nullptr;
}

void CPS2VM::CreatePadHandlerImpl(const CPadHandler::FactoryFunction& factoryFunction)
{
	m_pad = factoryFunction();
	RegisterModulesInPadHandler();
}

void CPS2VM::DestroyPadHandlerImpl()
{
	if(m_pad == nullptr) return;
	delete m_pad;
	m_pad = nullptr;
}

void CPS2VM::CreateSoundHandlerImpl(const CSoundHandler::FactoryFunction& factoryFunction)
{
	m_soundHandler = factoryFunction();
}

void CPS2VM::ReloadSpuBlockCountImpl()
{
	ValidateThreadContext();
	m_currentSpuBlock = 0;
	auto spuBlockCount = CAppConfig::GetInstance().GetPreferenceInteger(PREF_AUDIO_SPUBLOCKCOUNT);
	assert(spuBlockCount <= MAX_BLOCK_COUNT);
	spuBlockCount = std::min<int>(spuBlockCount, MAX_BLOCK_COUNT);
	m_spuBlockCount = spuBlockCount;
}

void CPS2VM::DestroySoundHandlerImpl()
{
	if(m_soundHandler == nullptr) return;
	delete m_soundHandler;
	m_soundHandler = nullptr;
}

void CPS2VM::UpdateEe()
{
#ifdef PROFILE
	CProfilerZone profilerZone(m_eeProfilerZone);
#endif

	while(m_eeExecutionTicks > 0)
	{
		int executed = m_ee->ExecuteCpu(m_singleStepEe ? 1 : m_eeExecutionTicks);
		if(m_ee->IsCpuIdle())
		{
			m_cpuUtilisation.eeIdleTicks += (m_eeExecutionTicks - executed);
			executed = m_eeExecutionTicks;
		}
		m_cpuUtilisation.eeTotalTicks += executed;

		m_ee->m_vpu0->Execute(m_singleStepVu0 ? 1 : executed);
		m_ee->m_vpu1->Execute(m_singleStepVu1 ? 1 : executed);

		m_eeExecutionTicks -= executed;
		m_spuUpdateTicks -= (static_cast<int64>(executed) << SPU_UPDATE_TICKS_PRECISION);
		m_ee->CountTicks(executed);
		m_hblankTicks -= executed;
		m_vblankTicks -= executed;

#ifdef DEBUGGER_INCLUDED
		if(m_singleStepEe || m_singleStepVu0 || m_singleStepVu1) break;
		if(m_ee->m_EE.m_executor->MustBreak()) break;
#endif
	}
}

void CPS2VM::UpdateIop()
{
#ifdef PROFILE
	CProfilerZone profilerZone(m_iopProfilerZone);
#endif

	while(m_iopExecutionTicks > 0)
	{
		int executed = m_iop->ExecuteCpu(m_singleStepIop ? 1 : m_iopExecutionTicks);
		if(m_iop->IsCpuIdle())
		{
			m_cpuUtilisation.iopIdleTicks += (m_iopExecutionTicks - executed);
			executed = m_iopExecutionTicks;
		}
		m_cpuUtilisation.iopTotalTicks += executed;

		m_iopExecutionTicks -= executed;
		m_iop->CountTicks(executed);

#ifdef DEBUGGER_INCLUDED
		if(m_singleStepIop) break;
		if(m_iop->m_cpu.m_executor->MustBreak()) break;
#endif
	}
}

void CPS2VM::UpdateSpu()
{
#ifdef PROFILE
	CProfilerZone profilerZone(m_spuProfilerZone);
#endif

	unsigned int blockOffset = (BLOCK_SIZE * m_currentSpuBlock);
	int16* samplesSpu0 = m_samples + blockOffset;

	m_iop->m_spuCore0.Render(samplesSpu0, BLOCK_SIZE);

	if(m_iop->m_spuCore1.IsEnabled())
	{
		int16 samplesSpu1[BLOCK_SIZE];
		m_iop->m_spuCore1.Render(samplesSpu1, BLOCK_SIZE);

		for(unsigned int i = 0; i < BLOCK_SIZE; i++)
		{
			// Core0 output should be mixed with Core1 at volume
			// corresponding to Core1's AVOL register values
			int32 volume = (i % 2) ? m_iop->m_spuCore1.m_extInputVolR : m_iop->m_spuCore1.m_extInputVolL;
			Iop::CSpuBase::MixSamples(samplesSpu0[i], volume, &samplesSpu1[i]);
			samplesSpu0[i] = samplesSpu1[i];
		}
	}

	m_currentSpuBlock++;
	if(m_currentSpuBlock == m_spuBlockCount)
	{
		if(m_soundHandler)
		{
			m_soundHandler->RecycleBuffers();
			m_soundHandler->Write(m_samples, BLOCK_SIZE * m_spuBlockCount, DST_SAMPLE_RATE);
		}
		m_currentSpuBlock = 0;
	}
}

void CPS2VM::CDROM0_SyncPath()
{
	//TODO: Check if there's an m_cdrom0 already
	//TODO: Check if files are linked to this m_cdrom0 too and do something with them

	CDROM0_Reset();

	auto path = CAppConfig::GetInstance().GetPreferencePath(PREF_PS2_CDROM0_PATH);
	if(!path.empty())
	{
		try
		{
			m_cdrom0 = DiskUtils::CreateOpticalMediaFromPath(path);
			SetIopOpticalMedia(m_cdrom0.get());
		}
		catch(const std::exception& Exception)
		{
			printf("PS2VM: Error mounting cdrom0 device: %s\r\n", Exception.what());
		}
	}
}

void CPS2VM::CDROM0_Reset()
{
	SetIopOpticalMedia(nullptr);
	m_cdrom0.reset();
}

void CPS2VM::SetIopOpticalMedia(COpticalMedia* opticalMedia)
{
	auto iopOs = dynamic_cast<CIopBios*>(m_iop->m_bios.get());
	assert(iopOs);

	iopOs->GetCdvdfsv()->SetOpticalMedia(opticalMedia);
	iopOs->GetCdvdman()->SetOpticalMedia(opticalMedia);
}

void CPS2VM::RegisterModulesInPadHandler()
{
	if(m_pad == nullptr) return;

	auto iopOs = dynamic_cast<CIopBios*>(m_iop->m_bios.get());
	assert(iopOs);

	m_pad->RemoveAllListeners();
	m_pad->InsertListener(iopOs->GetPadman());
	m_pad->InsertListener(&m_iop->m_sio2);

	{
		auto device = iopOs->GetUsbd()->GetDevice<Iop::CBuzzerUsbDevice>();
		device->SetPadHandler(m_pad);
	}
}

void CPS2VM::ReloadExecutable(const char* executablePath, const CPS2OS::ArgumentList& arguments)
{
	{
		//SPU RAM is not cleared by a LoadExecPS2 operation, we must keep its contents
		//Deus Ex uses SPU RAM to keep game state in between executable reloads
		auto savedSpuRam = std::vector<uint8>(PS2::SPU_RAM_SIZE);
		memcpy(savedSpuRam.data(), m_iop->m_spuRam, PS2::SPU_RAM_SIZE);
		ResetVM();
		memcpy(m_iop->m_spuRam, savedSpuRam.data(), PS2::SPU_RAM_SIZE);
	}
	if(BeforeExecutableReloaded)
	{
		BeforeExecutableReloaded(this);
	}
	m_ee->m_os->BootFromVirtualPath(executablePath, arguments);
	if(AfterExecutableReloaded)
	{
		AfterExecutableReloaded(this);
	}
}

void CPS2VM::OnExecutableChange()
{
	CGameConfig::ApplyGameConfig(*this);
}

void CPS2VM::OnCrtModeChange()
{
	ReloadFrameRateLimit();
}

void CPS2VM::EmuThread()
{
	CreateVM();
	fesetround(FE_TOWARDZERO);
	FpUtils::SetDenormalHandlingMode();
	CProfiler::GetInstance().SetWorkThread();
#ifdef __ANDROID__
	JNIEnv* env = nullptr;
	Framework::CJavaVM::AttachCurrentThread(&env, THREAD_NAME);
#endif
#ifdef PROFILE
	CProfilerZone profilerZone(m_otherProfilerZone);
#endif
	static_cast<CEeExecutor*>(m_ee->m_EE.m_executor.get())->AddExceptionHandler();
	m_frameLimiter.BeginFrame();
	while(1)
	{
		while(m_mailBox.IsPending())
		{
			m_mailBox.ReceiveCall();
		}
		if(m_nEnd) break;
		if(m_nStatus == PAUSED)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
		}
		if(m_nStatus == RUNNING)
		{
			if(m_spuUpdateTicks <= 0)
			{
				UpdateSpu();
				m_spuUpdateTicks += m_spuUpdateTicksTotal;
			}

			{
				if(m_hblankTicks <= 0)
				{
					m_hblankTicks += m_hblankTicksTotal;
					if(m_ee->m_gs)
					{
						m_ee->m_gs->SetHBlank();
					}
				}

				//Check vblank stuff
				if(m_vblankTicks <= 0)
				{
					m_inVblank = !m_inVblank;
					if(m_inVblank)
					{
						m_vblankTicks += m_vblankTicksTotal;
						m_ee->NotifyVBlankStart();
						m_iop->NotifyVBlankStart();

						if(m_ee->m_gs != NULL)
						{
#ifdef PROFILE
							CProfilerZone profilerZone(m_gsSyncProfilerZone);
#endif
							m_ee->m_gs->SetVBlank();
						}

						if(m_pad != NULL)
						{
							m_pad->Update(m_ee->m_ram);
						}
#ifdef PROFILE
						//Finish up profile
						CProfiler::GetInstance().CountCurrentZone();
#endif
						OnNewFrame();
#ifdef PROFILE
						CProfiler::GetInstance().Reset();
#endif
						m_cpuUtilisation = CPU_UTILISATION_INFO();
					}
					else
					{
						m_vblankTicks += m_onScreenTicksTotal;
						m_ee->NotifyVBlankEnd();
						m_iop->NotifyVBlankEnd();
						if(m_ee->m_gs != NULL)
						{
							m_ee->m_gs->ResetVBlank();
						}
						m_frameLimiter.EndFrame();
						m_frameLimiter.BeginFrame();
					}
				}

				m_eeExecutionTicks += m_eeTickStep;
				m_iopExecutionTicks += m_iopTickStep;

				UpdateEe();
				UpdateIop();
			}
#ifdef DEBUGGER_INCLUDED
			if(
			    m_ee->m_EE.m_executor->MustBreak() ||
			    m_iop->m_cpu.m_executor->MustBreak() ||
			    m_ee->m_VU1.m_executor->MustBreak() ||
			    m_singleStepEe || m_singleStepIop || m_singleStepVu0 || m_singleStepVu1)
			{
				m_nStatus = PAUSED;
				m_singleStepEe = false;
				m_singleStepIop = false;
				m_singleStepVu0 = false;
				m_singleStepVu1 = false;
				OnRunningStateChange();
				OnMachineStateChange();
			}
#endif
		}
	}
	static_cast<CEeExecutor*>(m_ee->m_EE.m_executor.get())->RemoveExceptionHandler();
#ifdef __ANDROID__
	Framework::CJavaVM::DetachCurrentThread();
#endif
}
