
#include "Nes_Core.h"
#include "Nes_Mapper.h"

#include "blargg_source.h"

int Nes_Core::cpu_read( nes_addr_t addr, nes_time_t time )
{
	//LOG_FREQ( "cpu_read", 16, addr >> 12 );
	
	{
		int result = cpu::low_mem [addr & 0x7FF];
		if ( !(addr & 0xE000) )
			return result;
	}
	
	{
		int result = *cpu::get_code( addr );
		if ( addr > 0x7FFF )
			return result;
	}
	
	time += cpu_time_offset;
	if ( addr < 0x4000 )
		return ppu.read( addr, time );
	
	clock_ = time;
	if ( data_reader_mapped [addr >> page_bits] )
	{
		int result = mapper->read( time, addr );
		if ( result >= 0 )
			return result;
	}
	
	if ( addr < 0x6000 )
		return read_io( addr );
	
	if ( addr < sram_readable )
		return impl->sram [addr & (impl_t::sram_size - 1)];
	
	if ( addr < lrom_readable )
		return *cpu::get_code( addr );
	
	#ifndef NDEBUG
		log_unmapped( addr );
	#endif
	
	return addr >> 8; // simulate open bus
}

inline int Nes_Core::cpu_read_ppu( nes_addr_t addr, nes_time_t time )
{
	//LOG_FREQ( "cpu_read_ppu", 16, addr >> 12 );
	
	// Read of status register (0x2002) is heavily optimized since many games
	// poll it hundreds of times per frame.
	nes_time_t next = ppu_2002_time;
	int result = ppu.r2002;
	if ( addr == 0x2002 )
	{
		ppu.second_write = false;
		if ( time >= next )
			result = ppu.read_2002( time + cpu_time_offset );
	}
	else
	{
		result = cpu::low_mem [addr & 0x7FF];
		if ( addr >= 0x2000 )
			result = cpu_read( addr, time );
	}
	
	return result;
}

void Nes_Core::cpu_write_2007( int data )
{
	// ppu.write_2007() is inlined
	if ( ppu.write_2007( data ) & Nes_Ppu::vaddr_clock_mask )
		mapper->a12_clocked();
}

void Nes_Core::cpu_write( nes_addr_t addr, int data, nes_time_t time )
{
	//LOG_FREQ( "cpu_write", 16, addr >> 12 );
	
	if ( !(addr & 0xE000) )
	{
		cpu::low_mem [addr & 0x7FF] = data;
		return;
	}
	
	time += cpu_time_offset;
	if ( addr < 0x4000 )
	{
		if ( (addr & 7) == 7 )
			cpu_write_2007( data );
		else
			ppu.write( time, addr, data );
		return;
	}
	
	clock_ = time;
	if ( data_writer_mapped [addr >> page_bits] && mapper->write_intercepted( time, addr, data ) )
		return;
	
	if ( addr < 0x6000 )
	{
		write_io( addr, data );
		return;
	}
	
	if ( addr < sram_writable )
	{
		impl->sram [addr & (impl_t::sram_size - 1)] = data;
		return;
	}
	
	if ( addr > 0x7FFF )
	{
		mapper->write( clock_, addr, data );
		return;
	}
	
	#ifndef NDEBUG
		log_unmapped( addr, data );
	#endif
}

#define NES_CPU_READ_PPU( cpu, addr, time ) \
	STATIC_CAST(Nes_Core&,*cpu).cpu_read_ppu( addr, time )

#define NES_CPU_READ( cpu, addr, time ) \
	STATIC_CAST(Nes_Core&,*cpu).cpu_read( addr, time )

#define NES_CPU_WRITEX( cpu, addr, data, time ){\
	STATIC_CAST(Nes_Core&,*cpu).cpu_write( addr, data, time );\
}

#define NES_CPU_WRITE( cpu, addr, data, time ){\
	if ( addr < 0x800 ) cpu->low_mem [addr] = data;\
	else if ( addr == 0x2007 ) STATIC_CAST(Nes_Core&,*cpu).cpu_write_2007( data );\
	else STATIC_CAST(Nes_Core&,*cpu).cpu_write( addr, data, time );\
}
