#include "pch.h"
#include "SNES/Spc.h"
#include "SNES/SnesConsole.h"
#include "SNES/SnesMemoryManager.h"
#include "SNES/SpcFileData.h"
#ifndef DUMMYSPC
#include "SNES/DSP/Dsp.h"
#else
#undef Spc
#undef DUMMYSPC
#include "SNES/DSP/Dsp.h"
#define Spc DummySpc
#define DUMMYSPC
#endif
#include "Shared/Emulator.h"
#include "Shared/EmuSettings.h"
#include "Shared/Audio/SoundMixer.h"
#include "Utilities/Serializer.h"
#include "Shared/MemoryOperationType.h"

Spc::Spc(SnesConsole* console)
{
	_emu = console->GetEmulator();
	_console = console;
	_memoryManager = console->GetMemoryManager();

	_ram = new uint8_t[Spc::SpcRamSize];
	_emu->RegisterMemory(MemoryType::SpcRam, _ram, Spc::SpcRamSize);
	_console->InitializeRam(_ram, Spc::SpcRamSize);

	_emu->RegisterMemory(MemoryType::SpcRom, _spcBios, Spc::SpcRomSize);

#ifndef DUMMYSPC
	_dsp.reset(new Dsp(_emu, console, this));
#endif

	_state = {};
	_state.WriteEnabled = true;
	_state.TimersEnabled = true;
	_state.RomEnabled = true;
	_state.SP = 0xFF;
	_state.PC = ReadWord(Spc::ResetVector);
	_state.StopState = SnesCpuStopState::Running;

	_opCode = 0;
	_opStep = SpcOpStep::ReadOpCode;
	_opSubStep = 0;
	_tmp1 = 0;
	_tmp2 = 0;
	_tmp3 = 0;
	_operandA = 0;
	_operandB = 0;
	_enabled = true;
	_spcSampleRate = Spc::SpcSampleRate + _emu->GetSettings()->GetSnesConfig().SpcClockSpeedAdjustment;

	UpdateClockRatio();
}

#ifndef DUMMYSPC
Spc::~Spc()
{
	delete[] _ram;
}
#endif

void Spc::Reset()
{
	_state.StopState = SnesCpuStopState::Running;

	_state.Timer0.Reset();
	_state.Timer1.Reset();
	_state.Timer2.Reset();

	//Resetting appears to reset the values the main CPU can read (not doing this causes a freeze in Kaite Tsukette Asoberu Dezaemon)
	_state.OutputReg[0] = 0;
	_state.OutputReg[1] = 0;
	_state.OutputReg[2] = 0;
	_state.OutputReg[3] = 0;

	//Reset the values the SPC can read from the port, too (not doing this freezes Ranma Chounai Gekitou Hen on reset)
	_state.CpuRegs[0] = 0;
	_state.CpuRegs[1] = 0;
	_state.CpuRegs[2] = 0;
	_state.CpuRegs[3] = 0;

	_state.RomEnabled = true;
	_state.Cycle = 0;
	_state.PC = ReadWord(Spc::ResetVector);
	_state.A = 0;
	_state.X = 0;
	_state.Y = 0;
	_state.SP = 0xFF;

	//Clear P (and other flags) - if P is set after reset, the IPL ROM doesn't work properly
	_state.PS = 0;
	
	_opCode = 0;
	_opStep = SpcOpStep::ReadOpCode;
	_opSubStep = 0;
	_tmp1 = 0;
	_tmp2 = 0;
	_tmp3 = 0;
	_operandA = 0;
	_operandB = 0;

	_dsp->Reset();
}

void Spc::SetSpcState(bool enabled)
{
	//Used by overclocking logic to disable SPC during the extra scanlines added to the PPU
	if(_enabled != enabled) {
		if(enabled) {
			//When re-enabling, adjust the cycle counter to prevent running extra cycles
			UpdateClockRatio();
		} else {
			//Catch up SPC before disabling it
			Run();
		}
		_enabled = enabled;
	}
}

void Spc::UpdateClockRatio()
{
	_clockRatio = (double)(_spcSampleRate * 64) / _console->GetMasterClockRate();

	//If the target cycle is off by more than 20 cycles, reset the counter to match what was expected
	//This can happen due to overclocking (which disables the SPC for some scanlines) or if the SPC's 
	//internal sample rate is changed between versions (e.g 32000hz -> 32040hz)
	uint64_t targetCycle = (uint64_t)(_memoryManager->GetMasterClock() * _clockRatio);
	if(std::abs((int64_t)targetCycle - (int64_t)_state.Cycle) > 20) {
		_state.Cycle = targetCycle;
	}
}

void Spc::ExitExecLoop()
{
#ifndef DUMMYSPC
	_state.Cycle = _memoryManager->GetMasterClock() * _clockRatio;
#endif
}

void Spc::Idle()
{
	IncCycleCount(-1);
}

void Spc::DummyRead()
{
	Read(_state.PC, MemoryOperationType::DummyRead);
}

void Spc::DummyRead(uint16_t addr)
{
	Read(addr, MemoryOperationType::DummyRead);
}

void Spc::IncCycleCount(int32_t addr)
{
	static constexpr uint8_t cpuWait[4] = { 2, 4, 10, 20 };
	static constexpr uint8_t timerMultiplier[4] = { 2, 4, 8, 16 };

	uint8_t speedSelect;
	if(addr < 0 || ((addr & 0xFFF0) == 0x00F0) || (addr >= 0xFFC0 && _state.RomEnabled)) {
		//Use internal speed (bits 4-5) for idle cycles, register access or IPL rom access
		speedSelect = _state.InternalSpeed;
	} else {
		speedSelect = _state.ExternalSpeed;
	}

	_state.Cycle += cpuWait[speedSelect];
#ifndef DUMMYSPC
	_dsp->Exec();
#endif

	uint8_t timerInc = timerMultiplier[speedSelect];
	_state.Timer0.Run(timerInc);
	_state.Timer1.Run(timerInc);
	_state.Timer2.Run(timerInc);
}

uint8_t Spc::DebugRead(uint16_t addr)
{
	if(addr >= 0xFFC0 && _state.RomEnabled) {
		return _spcBios[addr & 0x3F];
	}

	switch(addr) {
		case 0xF0: return 0;
		case 0xF1: return 0;

		case 0xF2: return _state.DspReg;
		case 0xF3: return _dsp->Read(_state.DspReg & 0x7F);

		case 0xF4: return _state.CpuRegs[0];
		case 0xF5: return _state.CpuRegs[1];
		case 0xF6: return _state.CpuRegs[2];
		case 0xF7: return _state.CpuRegs[3];

		case 0xF8: return _state.RamReg[0];
		case 0xF9: return _state.RamReg[1];

		case 0xFA: return 0;
		case 0xFB: return 0;
		case 0xFC: return 0;

		case 0xFD: return _state.Timer0.DebugRead();
		case 0xFE: return _state.Timer1.DebugRead();
		case 0xFF: return _state.Timer2.DebugRead();

		default: return _ram[addr];
	}
}

void Spc::DebugWrite(uint16_t addr, uint8_t value)
{
	_ram[addr] = value;
}

void Spc::DebugWriteDspReg(uint8_t addr, uint8_t value)
{
	_dsp->Write(addr, value);
}

uint8_t Spc::Read(uint16_t addr, MemoryOperationType type)
{
	IncCycleCount(addr);

	uint8_t value;
	if(addr >= 0xFFC0 && _state.RomEnabled) {
		value = _spcBios[addr & 0x3F];
	} else {
		switch(addr) {
			case 0xF0: value = 0; break;
			case 0xF1: value = 0; break;

			case 0xF2: value = _state.DspReg; break;
			case 0xF3: 
				#ifndef DUMMYSPC
				value = _dsp->Read(_state.DspReg & 0x7F);
				#else
				value = 0;
				#endif
				break;

			case 0xF4: value = _state.CpuRegs[0]; break;
			case 0xF5: value = _state.CpuRegs[1]; break;
			case 0xF6: value = _state.CpuRegs[2]; break;
			case 0xF7: value = _state.CpuRegs[3]; break;

			case 0xF8: value = _state.RamReg[0]; break;
			case 0xF9: value = _state.RamReg[1]; break;

			case 0xFA: value = 0; break;
			case 0xFB: value = 0; break;
			case 0xFC: value = 0; break;

			case 0xFD: value = _state.Timer0.GetOutput(); break;
			case 0xFE: value = _state.Timer1.GetOutput(); break;
			case 0xFF: value = _state.Timer2.GetOutput(); break;

			default: value = _ram[addr]; break;
		}
	}

#ifndef DUMMYSPC
	_emu->ProcessMemoryRead<CpuType::Spc>(addr, value, type);
#else 
	LogMemoryOperation(addr, value, type);
#endif

	return value;
}

void Spc::Write(uint16_t addr, uint8_t value, MemoryOperationType type)
{
	IncCycleCount(addr);

#ifdef DUMMYSPC
	LogMemoryOperation(addr, value, type);
#else

	//Writes always affect the underlying RAM
	if(_state.WriteEnabled) {
		if(_emu->ProcessMemoryWrite<CpuType::Spc>(addr, value, type)) {
			_ram[addr] = value;
		}
	}

	switch(addr) {
		case 0xF0: 
			if(!CheckFlag(SpcFlags::DirectPage)) {
				_state.InternalSpeed = (value >> 6) & 0x03;
				_state.ExternalSpeed = (value >> 4) & 0x03;
				_state.TimersEnabled = (value & 0x08) != 0;
				_state.TimersDisabled = (value & 0x01) != 0;
				_state.WriteEnabled = value & 0x02;

				bool timersEnabled = _state.TimersEnabled && !_state.TimersDisabled;
				_state.Timer0.SetGlobalEnabled(timersEnabled);
				_state.Timer1.SetGlobalEnabled(timersEnabled);
				_state.Timer2.SetGlobalEnabled(timersEnabled);
			}
			break;

		case 0xF1:
			if(value & SpcControlFlags::ClearPortsA) {
				_state.CpuRegs[0] = _state.CpuRegs[1] = 0;
			}
			if(value & SpcControlFlags::ClearPortsB) {
				_state.CpuRegs[2] = _state.CpuRegs[3] = 0;
			}

			_state.Timer0.SetEnabled((value & SpcControlFlags::Timer0) != 0);
			_state.Timer1.SetEnabled((value & SpcControlFlags::Timer1) != 0);
			_state.Timer2.SetEnabled((value & SpcControlFlags::Timer2) != 0);

			_state.RomEnabled = (value & SpcControlFlags::EnableRom) != 0;
			break;

		case 0xF2: _state.DspReg = value; break;
		case 0xF3: 
			if(_state.DspReg < 128) {
				_dsp->Write(_state.DspReg, value);
			}
			break;

		case 0xF4: _state.OutputReg[0] = value; break;
		case 0xF5: _state.OutputReg[1] = value; break;
		case 0xF6: _state.OutputReg[2] = value; break;
		case 0xF7: _state.OutputReg[3] = value; break;
		case 0xF8: _state.RamReg[0] = value; break;
		case 0xF9: _state.RamReg[1] = value; break;

		case 0xFA: _state.Timer0.SetTarget(value); break;
		case 0xFB: _state.Timer1.SetTarget(value); break;
		case 0xFC: _state.Timer2.SetTarget(value); break;

		case 0xFD: break;
		case 0xFE: break;
		case 0xFF: break;
	}
#endif
}

uint8_t Spc::CpuReadRegister(uint16_t addr)
{
	Run();
	return _state.OutputReg[addr & 0x03];
}

void Spc::CpuWriteRegister(uint32_t addr, uint8_t value)
{
	Run();
	_state.CpuRegs[addr & 0x03] = value;
}

uint8_t Spc::DspReadRam(uint16_t addr)
{
	uint8_t value = _ram[addr];
#ifndef DUMMYSPC
	_emu->ProcessMemoryRead<CpuType::Spc, 1, MemoryAccessFlags::DspAccess>(addr, value, MemoryOperationType::Read);
#endif
	return value;
}

void Spc::DspWriteRam(uint16_t addr, uint8_t value)
{
#ifndef DUMMYSPC
	_emu->ProcessMemoryWrite<CpuType::Spc, 1, MemoryAccessFlags::DspAccess>(addr, value, MemoryOperationType::Write);
#endif
	_ram[addr] = value;
}

void Spc::Run()
{
	if(!_enabled) {
		//Used to temporarily disable the SPC when overclocking is enabled
		return;
	} else if(_state.StopState != SnesCpuStopState::Running) {
		//STOP or SLEEP were executed - execution is stopped forever.
		_emu->ProcessHaltedCpu<CpuType::Spc>();
		return;
	}

	uint64_t targetCycle = (uint64_t)(_memoryManager->GetMasterClock() * _clockRatio);
	while(_state.Cycle < targetCycle) {
		ProcessCycle();
	}
}

void Spc::ProcessCycle()
{
	if(_opStep == SpcOpStep::ReadOpCode) {
#ifndef DUMMYSPC
		_emu->ProcessInstruction<CpuType::Spc>();
#endif 
		_opCode = GetOpCode();
		_opStep = SpcOpStep::Addressing;
		_opSubStep = 0;
	} else {
		Exec();
	}
}

void Spc::ProcessEndFrame()
{
	Run();

	UpdateClockRatio();

	uint16_t sampleCount = _dsp->GetSampleCount();
	if(sampleCount != 0) {
		_emu->GetSoundMixer()->PlayAudioBuffer(_dsp->GetSamples(), sampleCount / 2, _spcSampleRate);
	}
	_dsp->ResetOutput();
}

SpcState& Spc::GetState()
{
	return _state;
}

DspState& Spc::GetDspState()
{
	return _dsp->GetState();
}

bool Spc::IsMuted()
{
	return _dsp->IsMuted();
}

AddressInfo Spc::GetAbsoluteAddress(uint16_t addr)
{
	if(addr < 0xFFC0 || !_state.RomEnabled) {
		return AddressInfo { addr, MemoryType::SpcRam };
	}
	return AddressInfo { addr & 0x3F, MemoryType::SpcRom };
}

int Spc::GetRelativeAddress(AddressInfo &absAddress)
{
	if(absAddress.Type == MemoryType::SpcRom) {
		if(_state.RomEnabled) {
			return 0xFFC0 | (absAddress.Address & 0x3F);
		}
	} else {
		if(absAddress.Address < 0xFFC0 || !_state.RomEnabled) {
			return absAddress.Address;
		}
	}
	return -1;
}

uint8_t* Spc::GetSpcRam()
{
	return _ram;
}

uint8_t* Spc::GetSpcRom()
{
	return _spcBios;
}

void Spc::Serialize(Serializer &s)
{
	if(s.IsSaving() && s.GetFormat() != SerializeFormat::Map) {
		//Catch up SPC to main CPU before creating the state
		Run();
	}

	SV(_state.A); SV(_state.Cycle); SV(_state.PC); SV(_state.PS); SV(_state.SP); SV(_state.X); SV(_state.Y);
	SV(_state.CpuRegs[0]); SV(_state.CpuRegs[1]); SV(_state.CpuRegs[2]); SV(_state.CpuRegs[3]);
	SV(_state.OutputReg[0]); SV(_state.OutputReg[1]); SV(_state.OutputReg[2]); SV(_state.OutputReg[3]);
	SV(_state.RamReg[0]); SV(_state.RamReg[1]);
	SV(_state.ExternalSpeed); SV(_state.InternalSpeed); SV(_state.WriteEnabled); SV(_state.TimersEnabled);
	SV(_state.DspReg); SV(_state.RomEnabled); SV(_clockRatio); SV(_state.TimersDisabled);

	s.PushNamePrefix("timer0", -1);
	_state.Timer0.Serialize(s);
	s.PopNamePrefix();

	s.PushNamePrefix("timer1", -1);
	_state.Timer1.Serialize(s);
	s.PopNamePrefix();

	s.PushNamePrefix("timer2", -1);
	_state.Timer2.Serialize(s);
	s.PopNamePrefix();

	SVArray(_ram, Spc::SpcRamSize);

	SV(_dsp);

	if(s.GetFormat() != SerializeFormat::Map) {
		if(!s.IsSaving()) {
			UpdateClockRatio();
		}

		SV(_operandA); SV(_operandB); SV(_tmp1); SV(_tmp2); SV(_tmp3); SV(_opCode); SV(_opStep); SV(_opSubStep); SV(_enabled);
	}
}

uint8_t Spc::GetOpCode()
{
	uint8_t value = Read(_state.PC, MemoryOperationType::ExecOpCode);
	_state.PC++;
	return value;
}

uint8_t Spc::ReadOperandByte()
{
	uint8_t value = Read(_state.PC, MemoryOperationType::ExecOperand);
	_state.PC++;
	return value;
}

uint16_t Spc::ReadWord(uint16_t addr, MemoryOperationType type)
{
	uint8_t lsb = Read(addr, type);
	uint8_t msb = Read(addr + 1, type);
	return (msb << 8) | lsb;
}

void Spc::ClearFlags(uint8_t flags)
{
	_state.PS &= ~flags;
}

void Spc::SetFlags(uint8_t flags)
{
	_state.PS |= flags;
}

bool Spc::CheckFlag(uint8_t flag)
{
	return (_state.PS & flag) == flag;
}

void Spc::SetZeroNegativeFlags(uint8_t value)
{
	ClearFlags(SpcFlags::Zero | SpcFlags::Negative);
	if(value == 0) {
		SetFlags(SpcFlags::Zero);
	} else if(value & 0x80) {
		SetFlags(SpcFlags::Negative);
	}
}

void Spc::SetZeroNegativeFlags16(uint16_t value)
{
	ClearFlags(SpcFlags::Zero | SpcFlags::Negative);
	if(value == 0) {
		SetFlags(SpcFlags::Zero);
	} else if(value & 0x8000) {
		SetFlags(SpcFlags::Negative);
	}
}

uint8_t Spc::GetByteValue()
{
	return Read(_operandA);
}

void Spc::Push(uint8_t value)
{
	Write(0x100 | _state.SP, value);
	_state.SP--;
}

uint8_t Spc::Pop()
{
	_state.SP++;
	return Read(0x100 | _state.SP);
}

uint16_t Spc::GetDirectAddress(uint8_t offset)
{
	return (CheckFlag(SpcFlags::DirectPage) ? 0x100 : 0) + offset;
}

void Spc::LoadSpcFile(SpcFileData* data)
{
	memcpy(_ram, data->SpcRam, Spc::SpcRamSize);

	if(data->HasExtraRam) {
		bool extraRamContainsIpl = memcmp(data->SpcExtraRam, _spcBios, 0x40) == 0;
		if(!extraRamContainsIpl) {
			bool ramContainsIpl = memcmp(data->SpcRam + 0xFFC0, _spcBios, 0x40) == 0;
			bool isSpcRamEmpty = true;
			bool isExtraRamEmpty = true;
			for(int i = 0; i < 0x40; i++) {
				if(data->SpcExtraRam[i] != 0 && data->SpcExtraRam[i] != 0xFF) {
					isExtraRamEmpty = false;
				}
				if(data->SpcRam[i+0xFFC0] != 0 && data->SpcRam[i + 0xFFC0] != 0xFF) {
					isSpcRamEmpty = false;
				}
			}

			if(ramContainsIpl || (isSpcRamEmpty && !isExtraRamEmpty)) {
				//Use extra ram section only if the main spc FFC0-FFFF section is empty or contains the IPL code
				memcpy(_ram + 0xFFC0, data->SpcExtraRam, 0x40);
			}
		}
	}

	_dsp->LoadSpcFileRegs(data->DspRegs);

	_state.PC = data->PC;
	_state.A = data->A;
	_state.X = data->X;
	_state.Y = data->Y;
	_state.PS = data->PS;
	_state.SP = data->SP;

	Write(0xF1, data->ControlReg);
	_state.DspReg = data->DspRegSelect;

	_state.CpuRegs[0] = data->CpuRegs[0];
	_state.CpuRegs[1] = data->CpuRegs[1];
	_state.CpuRegs[2] = data->CpuRegs[2];
	_state.CpuRegs[3] = data->CpuRegs[3];
	
	_state.RamReg[0] = data->RamRegs[0];
	_state.RamReg[1] = data->RamRegs[1];

	_state.Timer0.SetTarget(data->TimerTarget[0]);
	_state.Timer1.SetTarget(data->TimerTarget[1]);
	_state.Timer2.SetTarget(data->TimerTarget[2]);

	_state.Timer0.SetOutput(data->TimerOutput[0]);
	_state.Timer1.SetOutput(data->TimerOutput[0]);
	_state.Timer2.SetOutput(data->TimerOutput[0]);
}