#include "stdafx.h"
#include "../Utilities/Serializer.h"
#include "CpuTypes.h"
#include "Sa1Cpu.h"
#include "Console.h"
#include "MemoryManager.h"
#include "EventType.h"
#include "Sa1.h"
#include "MemoryMappings.h"

#define Cpu Sa1Cpu
#include "Cpu.Instructions.h"
#include "Cpu.Shared.h"
#undef Cpu

Sa1Cpu::Sa1Cpu(Sa1* sa1, Console* console)
{
	_sa1 = sa1;
	_console = console;
}

Sa1Cpu::~Sa1Cpu()
{
}

void Sa1Cpu::Exec()
{
	_immediateMode = false;

	switch(_state.StopState) {
		case CpuStopState::Running: RunOp(); break;
		case CpuStopState::Stopped:
			//STP was executed, CPU no longer executes any code
			_state.CycleCount++;
			return;

		case CpuStopState::WaitingForIrq:
			//WAI
			Idle();
			if(_state.IrqSource || _state.NeedNmi) {
				Idle();
				_state.StopState = CpuStopState::Running;
			}
			break;
	}

	//Use the state of the IRQ/NMI flags on the previous cycle to determine if an IRQ is processed or not
	if(_state.PrevNeedNmi) {
		_state.NeedNmi = false;
		uint32_t originalPc = GetProgramAddress(_state.PC);
		ProcessInterrupt(_state.EmulationMode ? Sa1Cpu::LegacyNmiVector : Sa1Cpu::NmiVector, true);
		_console->ProcessInterrupt<CpuType::Sa1>(originalPc, GetProgramAddress(_state.PC), true);
	} else if(_state.PrevIrqSource) {
		uint32_t originalPc = GetProgramAddress(_state.PC);
		ProcessInterrupt(_state.EmulationMode ? Sa1Cpu::LegacyIrqVector : Sa1Cpu::IrqVector, true);
		_console->ProcessInterrupt<CpuType::Sa1>(originalPc, GetProgramAddress(_state.PC), false);
	}
}

void Sa1Cpu::Idle()
{
	//Do not apply any delay to internal cycles: "internal SA-1 cycles are still 10.74 MHz."
	_state.CycleCount++;
	DetectNmiSignalEdge();
	UpdateIrqNmiFlags();
}

void Sa1Cpu::IdleEndJump()
{
	IMemoryHandler* handler = _sa1->GetMemoryMappings()->GetHandler(_state.PC);
	if(handler && handler->GetMemoryType() == SnesMemoryType::PrgRom) {
		//Jumps/returns in PRG ROM take an extra cycle
		_state.CycleCount++;
		if(_sa1->GetSnesCpuMemoryType() == SnesMemoryType::PrgRom) {
			//Add an extra wait cycle if a conflict occurs at the same time
			_state.CycleCount++;
		}
	}
}

void Sa1Cpu::IdleTakeBranch()
{
	if(_state.PC & 0x01) {
		IMemoryHandler* handler = _sa1->GetMemoryMappings()->GetHandler(_state.PC);
		if(handler && handler->GetMemoryType() == SnesMemoryType::PrgRom) {
			//Branches to an odd address take an extra cycle
			_state.CycleCount++;
		}
	}
}

bool Sa1Cpu::IsAccessConflict()
{
	return _sa1->GetSnesCpuMemoryType() == _sa1->GetSa1MemoryType() && _sa1->GetSa1MemoryType() != SnesMemoryType::Register;
}

void Sa1Cpu::ProcessCpuCycle(uint32_t addr)
{
	_state.CycleCount++;

	if(_sa1->GetSa1MemoryType() == SnesMemoryType::SaveRam) {
		//BWRAM (save ram) access takes 2 cycles
		_state.CycleCount++;
		if(IsAccessConflict()) {
			_state.CycleCount += 2;
		}
	} else if(IsAccessConflict()) {
		//Add a wait cycle when a conflict occurs between both CPUs
		_state.CycleCount++;
		if(_sa1->GetSa1MemoryType() == SnesMemoryType::Sa1InternalRam && _sa1->IsSnesCpuFastRomSpeed()) {
			//If it's an IRAM access during FastROM access (speed = 6), add another wait cycle
			_state.CycleCount++;
		}
	}

	DetectNmiSignalEdge();
	UpdateIrqNmiFlags();
}

uint8_t Sa1Cpu::Read(uint32_t addr, MemoryOperationType type)
{
	ProcessCpuCycle(addr);
	return _sa1->ReadSa1(addr, type);
}

void Sa1Cpu::Write(uint32_t addr, uint8_t value, MemoryOperationType type)
{
	ProcessCpuCycle(addr);
	_sa1->WriteSa1(addr, value, type);
}

uint16_t Sa1Cpu::ReadVector(uint16_t vector)
{
	return _sa1->ReadVector(vector);
}

uint16_t Sa1Cpu::GetResetVector()
{
	return _sa1->ReadVector(Sa1Cpu::ResetVector);
}

void Sa1Cpu::IncreaseCycleCount(uint64_t cycleCount)
{
	_state.CycleCount += cycleCount;
}