#include "Iop_Loadcore.h"
#include "Iop_Dynamic.h"
#include "IopBios.h"
#include "../Log.h"
#include "../RegisterStateFile.h"

using namespace Iop;

#define LOG_NAME "iop_loadcore"

#define STATE_VERSION_XML           ("iop_loadcore/version.xml")
#define STATE_VERSION_MODULEVERSION ("moduleVersion")

#define FUNCTION_FLUSHDCACHE					"FlushDcache"
#define FUNCTION_REGISTERLIBRARYENTRIES			"RegisterLibraryEntries"
#define FUNCTION_QUERYBOOTMODE					"QueryBootMode"
#define FUNCTION_SETREBOOTTIMELIBHANDLINGMODE	"SetRebootTimeLibraryHandlingMode"

#define PATH_MAX_SIZE 252
#define ARGS_MAX_SIZE 252

CLoadcore::CLoadcore(CIopBios& bios, uint8* ram, CSifMan& sifMan)
: m_bios(bios)
, m_ram(ram)
{
	sifMan.RegisterModule(MODULE_ID, this);
}

void CLoadcore::SetModuleVersion(unsigned int moduleVersion)
{
	m_moduleVersion = moduleVersion;
}

std::string CLoadcore::GetId() const
{
	return "loadcore";
}

std::string CLoadcore::GetFunctionName(unsigned int functionId) const
{
	switch(functionId)
	{
	case 5:
		return FUNCTION_FLUSHDCACHE;
		break;
	case 6:
		return FUNCTION_REGISTERLIBRARYENTRIES;
		break;
	case 12:
		return FUNCTION_QUERYBOOTMODE;
		break;
	case 27:
		return FUNCTION_SETREBOOTTIMELIBHANDLINGMODE;
		break;
	default:
		return "unknown";
		break;
	}
}

void CLoadcore::Invoke(CMIPS& context, unsigned int functionId)
{
	switch(functionId)
	{
	case 5:
		//FlushDCache
		break;
	case 6:
		context.m_State.nGPR[CMIPS::V0].nD0 = static_cast<int32>(RegisterLibraryEntries(
			context.m_State.nGPR[CMIPS::A0].nV0
			));
		break;
	case 12:
		context.m_State.nGPR[CMIPS::V0].nD0 = static_cast<int32>(QueryBootMode(
			context.m_State.nGPR[CMIPS::A0].nV0
			));
		break;
	case 27:
		context.m_State.nGPR[CMIPS::V0].nD0 = static_cast<int32>(SetRebootTimeLibraryHandlingMode(
			context.m_State.nGPR[CMIPS::A0].nV0,
			context.m_State.nGPR[CMIPS::A1].nV0
			));
		break;
	default:
		CLog::GetInstance().Print(LOG_NAME, "Unknown function (%d) called (PC: 0x%0.8X).\r\n", 
			functionId, context.m_State.nPC);
		break;
	}
}

bool CLoadcore::Invoke(uint32 method, uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
{
	switch(method)
	{
	case 0x00:
		return LoadModule(args, argsSize, ret, retSize);
		break;
	case 0x01:
		LoadExecutable(args, argsSize, ret, retSize);
		break;
	case 0x06:
		LoadModuleFromMemory(args, argsSize, ret, retSize);
		return false;	//Block EE till module is loaded
		break;
	case 0x07:
		return StopModule(args, argsSize, ret, retSize);
		break;
	case 0x08:
		UnloadModule(args, argsSize, ret, retSize);
		break;
	case 0x09:
		SearchModuleByName(args, argsSize, ret, retSize);
		break;
	case 0xFF:
		//This is sometimes called after binding this server with a client
		Initialize(args, argsSize, ret, retSize);
		break;
	default:
		CLog::GetInstance().Print(LOG_NAME, "Invoking unknown function %d.\r\n", method);
		break;
	}
	return true;
}

void CLoadcore::LoadState(Framework::CZipArchiveReader& archive)
{
	auto registerFile = CRegisterStateFile(*archive.BeginReadFile(STATE_VERSION_XML));
	m_moduleVersion = registerFile.GetRegister32(STATE_VERSION_MODULEVERSION);
}

void CLoadcore::SaveState(Framework::CZipArchiveWriter& archive)
{
	auto registerFile = new CRegisterStateFile(STATE_VERSION_XML);
	registerFile->SetRegister32(STATE_VERSION_MODULEVERSION, m_moduleVersion);
	archive.InsertFile(registerFile);
}

void CLoadcore::SetLoadExecutableHandler(const LoadExecutableHandler& loadExecutableHandler)
{
	m_loadExecutableHandler = loadExecutableHandler;
}

uint32 CLoadcore::RegisterLibraryEntries(uint32 exportTablePtr)
{
	CLog::GetInstance().Print(LOG_NAME, FUNCTION_REGISTERLIBRARYENTRIES "(exportTable = 0x%0.8X);\r\n", exportTablePtr);
	uint32* exportTable = reinterpret_cast<uint32*>(&m_ram[exportTablePtr]);
	auto module = std::make_shared<CDynamic>(exportTable);
	bool registered = m_bios.RegisterModule(module);
	assert(registered);
	return 0;
}

uint32 CLoadcore::QueryBootMode(uint32 param)
{
	CLog::GetInstance().Print(LOG_NAME, FUNCTION_QUERYBOOTMODE "(param = %d);\r\n", param);
	return 0;
}

uint32 CLoadcore::SetRebootTimeLibraryHandlingMode(uint32 libAddr, uint32 mode)
{
	CLog::GetInstance().Print(LOG_NAME, FUNCTION_SETREBOOTTIMELIBHANDLINGMODE "(libAddr = 0x%0.8X, mode = 0x%0.8X);\r\n",
		libAddr, mode);
	return 0;
}

bool CLoadcore::LoadModule(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	char moduleName[PATH_MAX_SIZE];
	char moduleArgs[ARGS_MAX_SIZE];

	assert(argsSize == 512);

	//Sometimes called with 4, sometimes 8
	assert(retSize >= 4);

	uint32 moduleArgsSize = args[0];

	memcpy(moduleName, reinterpret_cast<const char*>(args) + 8, PATH_MAX_SIZE);
	memcpy(moduleArgs, reinterpret_cast<const char*>(args) + 8 + PATH_MAX_SIZE, ARGS_MAX_SIZE);

	//Load the module
	CLog::GetInstance().Print(LOG_NAME, "Request to load module '%s' received with %d bytes arguments payload.\r\n", moduleName, moduleArgsSize);

	auto moduleId = m_bios.LoadModule(moduleName);
	if(moduleId >= 0)
	{
		moduleId = m_bios.StartModule(moduleId, moduleName, moduleArgs, moduleArgsSize);
	}

	//This function returns something negative upon failure
	ret[0] = moduleId;

	if((moduleId >= 0) && !m_bios.IsModuleHle(moduleId))
	{
		//Block EE till the IOP has completed the operation and sends its reply to the EE
		return false;
	}
	else
	{
		//Loading module failed or is module is HLE, reply can be sent over immediately
		return true;
	}
}

void CLoadcore::LoadExecutable(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	char moduleName[PATH_MAX_SIZE];
	char sectionName[ARGS_MAX_SIZE];

	assert(argsSize == 512);
	assert(retSize >= 8);

	memcpy(moduleName, reinterpret_cast<const char*>(args) + 8, PATH_MAX_SIZE);
	memcpy(sectionName, reinterpret_cast<const char*>(args) + 8 + PATH_MAX_SIZE, ARGS_MAX_SIZE);

	CLog::GetInstance().Print(LOG_NAME, "Request to load section '%s' from executable '%s' received.\r\n", sectionName, moduleName);

	uint32 result = 0;

	//Load executable in EE memory
	if(m_loadExecutableHandler)
	{
		result = m_loadExecutableHandler(moduleName, sectionName);
	}

	//This function returns something negative upon failure
	ret[0] = result;		//epc or result (if negative)
	ret[1] = 0x00000000;	//gp
}

void CLoadcore::LoadModuleFromMemory(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	const char* moduleArgs = reinterpret_cast<const char*>(args) + 8 + PATH_MAX_SIZE;
	uint32 moduleArgsSize = args[1];
	CLog::GetInstance().Print(LOG_NAME, "Request to load module at 0x%0.8X received with %d bytes arguments payload.\r\n", args[0], moduleArgsSize);
	auto moduleId = m_bios.LoadModule(args[0]);
	if(moduleId >= 0)
	{
		moduleId = m_bios.StartModule(moduleId, "", moduleArgs, moduleArgsSize);
	}
	ret[0] = moduleId;
}

bool CLoadcore::StopModule(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	char moduleArgs[ARGS_MAX_SIZE];

	assert(argsSize == 512);
	assert(retSize >= 4);

	uint32 moduleId = args[0];
	uint32 moduleArgsSize = args[1];

	memcpy(moduleArgs, reinterpret_cast<const char*>(args) + 8 + PATH_MAX_SIZE, ARGS_MAX_SIZE);

	CLog::GetInstance().Print(LOG_NAME, "StopModule(moduleId = %d, args, argsSize = 0x%0.8X);\r\n", 
		moduleId, moduleArgsSize);

	auto result = m_bios.StopModule(moduleId);
	ret[0] = result;

	if(result >= 0)
	{
		return false;
	}
	else
	{
		return true;
	}
}

void CLoadcore::UnloadModule(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	assert(argsSize == 4);
	assert(retSize >= 4);

	uint32 moduleId = args[0];

	CLog::GetInstance().Print(LOG_NAME, "UnloadModule(moduleId = %d);\r\n", moduleId);

	auto result = m_bios.UnloadModule(moduleId);
	ret[0] = result;
}

void CLoadcore::SearchModuleByName(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	assert(argsSize >= 0x200);
	assert(retSize >= 4);

	const char* moduleName = reinterpret_cast<const char*>(args) + 8;
	CLog::GetInstance().Print(LOG_NAME, "SearchModuleByName('%s');\r\n", moduleName);
	auto moduleId = m_bios.SearchModuleByName(moduleName);
	ret[0] = moduleId;
}

void CLoadcore::Initialize(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize)
{
	assert(argsSize == 0);
	assert(retSize == 4);

	if(m_moduleVersion == 2020)
	{
		//Return '2020'
		//This is needed by Super Bust-A-Move
		ret[0] = 0x30323032;
	}
	else
	{
		//Return '....'
		ret[0] = 0x2E2E2E2E;
	}
}
