#include "Vpu.h"
#include "make_unique.h"
#include "string_format.h"
#include "Log.h"
#include "../states/RegisterStateFile.h"
#include "../Ps2Const.h"
#include "../FrameDump.h"
#include "Vif.h"
#include "Vif1.h"
#include "GIF.h"

#define LOG_NAME ("ee_vpu")

#define STATE_PATH_REGS_FORMAT ("vpu/vpu_%d.xml")

#define STATE_REGS_VUSTATE ("vuState")
#define STATE_REGS_FBRST ("fbrst")

CVpu::CVpu(unsigned int number, const VPUINIT& vpuInit, CGIF& gif, CINTC& intc, uint8* ram, uint8* spr)
    : m_number(number)
    , m_vif((number == 0) ? std::make_unique<CVif>(0, *this, intc, ram, spr) : std::make_unique<CVif1>(1, *this, gif, intc, ram, spr))
    , m_microMem(vpuInit.microMem)
    , m_microMemSize((number == 0) ? PS2::MICROMEM0SIZE : PS2::MICROMEM1SIZE)
    , m_vuMem(vpuInit.vuMem)
    , m_vuMemSize((number == 0) ? PS2::VUMEM0SIZE : PS2::VUMEM1SIZE)
    , m_ctx(vpuInit.context)
    , m_gif(gif)
    , m_vuProfilerZone(CProfiler::GetInstance().RegisterZone("VU"))
#ifdef DEBUGGER_INCLUDED
    , m_microMemMiniState(new uint8[(number == 0) ? PS2::MICROMEM0SIZE : PS2::MICROMEM1SIZE])
    , m_vuMemMiniState(new uint8[(number == 0) ? PS2::VUMEM0SIZE : PS2::VUMEM1SIZE])
    , m_topMiniState(0)
    , m_itopMiniState(0)
#endif
{
}

CVpu::~CVpu()
{
#ifdef DEBUGGER_INCLUDED
	delete[] m_microMemMiniState;
	delete[] m_vuMemMiniState;
#endif
}

void CVpu::Execute(int32 quota)
{
	if(m_vuState != VU_STATE_RUNNING) return;

#ifdef PROFILE
	CProfilerZone profilerZone(m_vuProfilerZone);
#endif

	m_ctx->m_executor->Execute(quota);
	switch(m_ctx->m_State.nHasException)
	{
	case MIPS_EXCEPTION_VU_EBIT:
		//E bit encountered
		m_vuState = VU_STATE_READY;
		VuStateChanged(m_vuState);
		break;
	case MIPS_EXCEPTION_VU_TBIT:
	case MIPS_EXCEPTION_VU_DBIT:
		//T/D bit encountered
		{
			bool mustBreak = false;
			mustBreak |= (m_ctx->m_State.nHasException == MIPS_EXCEPTION_VU_TBIT) && (m_fbrst & FBRST_TE);
			mustBreak |= (m_ctx->m_State.nHasException == MIPS_EXCEPTION_VU_DBIT) && (m_fbrst & FBRST_DE);
			if(mustBreak)
			{
				m_vuState = VU_STATE_STOPPED;
				VuStateChanged(m_vuState);
				VuInterruptTriggered();
			}
			else
			{
				m_ctx->m_State.nHasException = 0;
			}
		}
		break;
	default:
		break;
	}
}

#ifdef DEBUGGER_INCLUDED

void CVpu::SaveMiniState()
{
	memcpy(m_microMemMiniState, m_microMem, (m_number == 0) ? PS2::MICROMEM0SIZE : PS2::MICROMEM1SIZE);
	memcpy(m_vuMemMiniState, m_vuMem, (m_number == 0) ? PS2::VUMEM0SIZE : PS2::VUMEM1SIZE);
	memcpy(&m_vuMiniState, &m_ctx->m_State, sizeof(MIPSSTATE));
	m_topMiniState = (m_number == 0) ? 0 : m_vif->GetTOP();
	m_itopMiniState = m_vif->GetITOP();
}

const MIPSSTATE& CVpu::GetVuMiniState() const
{
	return m_vuMiniState;
}

uint8* CVpu::GetVuMemoryMiniState() const
{
	return m_vuMemMiniState;
}

uint8* CVpu::GetMicroMemoryMiniState() const
{
	return m_microMemMiniState;
}

uint32 CVpu::GetVuTopMiniState() const
{
	return m_topMiniState;
}

uint32 CVpu::GetVuItopMiniState() const
{
	return m_itopMiniState;
}

#endif

void CVpu::Reset()
{
	m_vuState = VU_STATE_READY;
	m_ctx->m_executor->Reset();
	m_vif->Reset();
}

void CVpu::SaveState(Framework::CZipArchiveWriter& archive)
{
	{
		auto path = string_format(STATE_PATH_REGS_FORMAT, m_number);
		auto registerFile = std::make_unique<CRegisterStateFile>(path.c_str());
		registerFile->SetRegister32(STATE_REGS_VUSTATE, m_vuState);
		registerFile->SetRegister32(STATE_REGS_FBRST, m_fbrst);
		archive.InsertFile(std::move(registerFile));
	}

	m_vif->SaveState(archive);
}

void CVpu::LoadState(Framework::CZipArchiveReader& archive)
{
	{
		auto path = string_format(STATE_PATH_REGS_FORMAT, m_number);
		CRegisterStateFile registerFile(*archive.BeginReadFile(path.c_str()));
		m_vuState = static_cast<VU_STATE>(registerFile.GetRegister32(STATE_REGS_VUSTATE));
		m_fbrst = registerFile.GetRegister32(STATE_REGS_FBRST);
	}

	m_vif->LoadState(archive);
}

CMIPS& CVpu::GetContext() const
{
	return *m_ctx;
}

uint8* CVpu::GetMicroMemory() const
{
	return m_microMem;
}

uint32 CVpu::GetMicroMemorySize() const
{
	return m_microMemSize;
}

uint8* CVpu::GetVuMemory() const
{
	return m_vuMem;
}

uint32 CVpu::GetVuMemorySize() const
{
	return m_vuMemSize;
}

CVif& CVpu::GetVif()
{
	return *m_vif.get();
}

void CVpu::SetFbrst(uint32 fbrst)
{
	//Only keep DE and TE bits
	m_fbrst = (fbrst & (FBRST_DE | FBRST_TE));
}

void CVpu::ExecuteMicroProgram(uint32 nAddress)
{
	CLog::GetInstance().Print(LOG_NAME, "Starting microprogram execution at 0x%08X.\r\n", nAddress);

	m_ctx->m_State.nPC = nAddress;
	m_ctx->m_State.pipeTime = 0;
	m_ctx->m_State.pipeFmacWrite[0] = {};
	m_ctx->m_State.pipeFmacWrite[1] = {};
	m_ctx->m_State.pipeFmacWrite[2] = {};
	m_ctx->m_State.savedNextBlockIntRegIdx = 0;
	m_ctx->m_State.nHasException = 0;

#ifdef DEBUGGER_INCLUDED
	SaveMiniState();
#endif

	assert(m_vuState != VU_STATE_RUNNING);
	m_vuState = VU_STATE_RUNNING;
	VuStateChanged(m_vuState);
	for(unsigned int i = 0; i < 100; i++)
	{
		Execute(5000);
		if(m_vuState != VU_STATE_RUNNING) break;
	}
}

void CVpu::InvalidateMicroProgram()
{
	m_ctx->m_executor->ClearActiveBlocksInRange(0, (m_number == 0) ? PS2::MICROMEM0SIZE : PS2::MICROMEM1SIZE, false);
}

void CVpu::InvalidateMicroProgram(uint32 start, uint32 end)
{
	m_ctx->m_executor->ClearActiveBlocksInRange(start, end, false);
}

void CVpu::ProcessXgKick(uint32 address)
{
	address &= 0x3FF;
	address *= 0x10;

	CGsPacketMetadata metadata;
	metadata.pathIndex = 1;
#ifdef DEBUGGER_INCLUDED
	metadata.vuMemPacketAddress = address;
	metadata.vpu1Top = GetVuTopMiniState();
	metadata.vpu1Itop = GetVuItopMiniState();
	memcpy(&metadata.vu1State, &GetVuMiniState(), sizeof(MIPSSTATE));
	memcpy(metadata.vuMem1, GetVuMemoryMiniState(), PS2::VUMEM1SIZE);
	memcpy(metadata.microMem1, GetMicroMemoryMiniState(), PS2::MICROMEM1SIZE);
#endif

	address += m_gif.ProcessSinglePacket(GetVuMemory(), PS2::VUMEM1SIZE, address, PS2::VUMEM1SIZE, metadata);
	if((address == PS2::VUMEM1SIZE) && (m_gif.GetActivePath() == 1))
	{
		address = 0;
		address += m_gif.ProcessSinglePacket(GetVuMemory(), PS2::VUMEM1SIZE, address, PS2::VUMEM1SIZE, metadata);
	}
	assert(m_gif.GetActivePath() == 0);

#ifdef DEBUGGER_INCLUDED
	SaveMiniState();
#endif
}
