
// Internal NES emulator

// Nes_Emu 0.7.0

#ifndef NES_CORE_H
#define NES_CORE_H

#include "blargg_common.h"
#include "Nes_Apu.h"
#include "Nes_Cpu.h"
#include "Nes_Ppu.h"
class Nes_Mapper;
class Nes_Cart;
class Nes_State;

class Nes_Core : private Nes_Cpu {
	typedef Nes_Cpu cpu;
public:
	Nes_Core();
	~Nes_Core();
	
	const char * init();
	const char * open( Nes_Cart const* );
	void reset( bool full_reset = true, bool erase_battery_ram = false );
	blip_time_t emulate_frame();
	void close();
	
	void save_state( Nes_State* ) const;
	void save_state( Nes_State_* ) const;
	void load_state( Nes_State_ const& );
	
	void irq_changed();
	void event_changed();
	
public: private: friend class Nes_Emu;
	
	struct impl_t
	{
		enum { sram_size = 0x2000 };
		uint8_t sram [sram_size];
		Nes_Apu apu;
		
		// extra byte allows CPU to always read operand of instruction, which
		// might go past end of data
		uint8_t unmapped_page [::Nes_Cpu::page_size + 1];
	};
	impl_t* impl; // keep large arrays separate
	unsigned long error_count;
	bool sram_present;

public:
	unsigned long current_joypad [2];
	int joypad_read_count;
	Nes_Cart const* cart;
	Nes_Mapper* mapper;
	nes_state_t nes;
	Nes_Ppu ppu;

private:
	// noncopyable
	Nes_Core( const Nes_Core& );
	Nes_Core& operator = ( const Nes_Core& );
	
	// Timing
	nes_time_t ppu_2002_time;
	void disable_rendering() { clock_ = 0; }
	nes_time_t earliest_irq( nes_time_t present );
	nes_time_t ppu_frame_length( nes_time_t present );
	nes_time_t earliest_event( nes_time_t present );
	
	// APU and Joypad
	joypad_state_t joypad;
	int  read_io( nes_addr_t );
	void write_io( nes_addr_t, int data );
	static int  read_dmc( void* emu, nes_addr_t );
	static void apu_irq_changed( void* emu );
	
	// CPU
	unsigned sram_readable;
	unsigned sram_writable;
	unsigned lrom_readable;
	nes_time_t clock_;
	nes_time_t cpu_time_offset;
	nes_time_t emulate_frame_();
	nes_addr_t read_vector( nes_addr_t );
	void vector_interrupt( nes_addr_t );
	static void log_unmapped( nes_addr_t addr, int data = -1 );
	void cpu_set_irq_time( nes_time_t t ) { cpu::set_irq_time_( t - 1 - cpu_time_offset ); }
	void cpu_set_end_time( nes_time_t t ) { cpu::set_end_time_( t - 1 - cpu_time_offset ); }
	nes_time_t cpu_time() const { return clock_ + 1; }
	void cpu_adjust_time( int offset );
	
public: private: friend class Nes_Ppu;
	void set_ppu_2002_time( nes_time_t t ) { ppu_2002_time = t - 1 - cpu_time_offset; }
	
public: private: friend class Nes_Mapper;
	void enable_prg_6000();
	void enable_sram( bool enabled, bool read_only = false );
	nes_time_t clock() const { return clock_; }
	void add_mapper_intercept( nes_addr_t start, unsigned size, bool read, bool write );
	
public: private: friend class Nes_Cpu;
	int  cpu_read_ppu( nes_addr_t, nes_time_t );
	int  cpu_read( nes_addr_t, nes_time_t );
	void cpu_write( nes_addr_t, int data, nes_time_t );
	void cpu_write_2007( int data );
	
private:
	unsigned char data_reader_mapped [page_count + 1]; // extra entry for overflow
	unsigned char data_writer_mapped [page_count + 1];
};

int mem_differs( void const* p, int cmp, unsigned long s );

#endif
