
// NES video game console emulator with snapshot support

// Nes_Emu 0.7.0

#ifndef NES_EMU_H
#define NES_EMU_H

#include "blargg_common.h"
#include "Multi_Buffer.h"
#include "Nes_Cart.h"
#include "Nes_Core.h"
class Nes_State;

// Register optional mappers included with Nes_Emu
void register_optional_mappers();
void register_extra_mappers();

extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper

class Nes_Emu {
public:
	Nes_Emu();
	virtual ~Nes_Emu();

// Basic setup

	// Load iNES file into emulator and clear recording
	const char * load_ines( Auto_File_Reader );

	// Set sample rate for sound generation
	const char * set_sample_rate( long );

	// Size and depth of graphics buffer required for rendering. Note that this
	// is larger than the actual image, with a temporary area around the edge
	// that gets filled with junk.
	enum { buffer_width  = Nes_Ppu::buffer_width };
	int buffer_height() const { return buffer_height_; }
	enum { bits_per_pixel = 8 };

	// Set graphics buffer to render pixels to. Pixels points to top-left pixel and
	// row_bytes is the number of bytes to get to the next line (positive or negative).
	void set_pixels( void* pixels, long row_bytes );

	// Size of image generated in graphics buffer
	enum { image_width   = 256 };
	enum { image_height  = 240 };

// Basic emulation

	// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
	// and sound are available for output using the accessors below.
	virtual const char * emulate_frame( int joypad1, int joypad2 = 0 );

	// Emulate one video frame using joypad1 and joypad2 as input, but skips drawing.
	// Afterwards, audio is available for output using the accessors below.
	virtual const char * emulate_skip_frame( int joypad1, int joypad2 = 0 );

	// Maximum size of palette that can be generated
	enum { max_palette_size = 256 };

	// Result of current frame
	struct frame_t
	{
		int joypad_read_count;  // number of times joypads were strobed (read)
		int burst_phase;        // NTSC burst phase for frame (0, 1, or 2)

		int sample_count;       // number of samples (always a multiple of chan_count)
		int chan_count;         // 1: mono, 2: stereo

		int top;                // top-left position of image in graphics buffer
		enum { left = 8 };
		unsigned char* pixels;  // pointer to top-left pixel of image
		long pitch;             // number of bytes to get to next row of image

		int palette_begin;      // first host palette entry, as set by set_palette_range()
		int palette_size;       // number of entries used for current frame
		short palette [max_palette_size]; // [palette_begin to palette_begin+palette_size-1]
	};
	frame_t const& frame() const { return *frame_; }

	// Read samples for the current frame. Returns number of samples read into buffer.
	// Currently all samples must be read in one call.
	virtual long read_samples( short* out, long max_samples );

// Additional features

	// Use already-loaded cartridge. Retains pointer, so it must be kept around until
	// closed. A cartridge can be shared among multiple emulators. After opening,
	// cartridge's CHR data shouldn't be modified since a copy is cached internally.
	const char * set_cart( Nes_Cart const* );

	// Pointer to current cartridge, or NULL if none is loaded
	Nes_Cart const* cart() const { return emu.cart; }

	// Free any memory and close cartridge, if one was currently open. A new cartridge
	// must be opened before further emulation can take place.
	void close();

	// Emulate powering NES off and then back on. If full_reset is false, emulates
	// pressing the reset button only, which doesn't affect memory, otherwise
	// emulates powering system off then on.
	virtual void reset( bool full_reset = true, bool erase_battery_ram = false );

	// Number of undefined CPU instructions encountered. Cleared after reset() and
	// load_state(). A non-zero value indicates that cartridge is probably
	// incompatible.
	unsigned long error_count() const { return emu.error_count; }

// Sound

	// Set sample rate and use a custom sound buffer instead of the default
	// mono buffer, i.e. Nes_Buffer, Effects_Buffer, etc..
	const char * set_sample_rate( long rate, Multi_Buffer* );

	// Adjust effective frame rate by changing how many samples are generated each frame.
	// Allows fine tuning of frame rate to improve synchronization.
	void set_frame_rate( double rate );

	// Number of sound channels for current cartridge
	int channel_count() const { return channel_count_; }

	// Frequency equalizer parameters
	struct equalizer_t {
		double treble; // 5.0 = extra-crisp, -200.0 = muffled
		long bass;     // 0 = deep, 20000 = tinny
	};

	// Current frequency equalization
	equalizer_t const& equalizer() const { return equalizer_; }

	// Change frequency equalization
	void set_equalizer( equalizer_t const& );

	// Equalizer presets
	static equalizer_t const nes_eq;        // NES
	static equalizer_t const famicom_eq;    // Famicom
	static equalizer_t const tv_eq;         // TV speaker
	static equalizer_t const flat_eq;       // Flat EQ
	static equalizer_t const crisp_eq;      // Crisp EQ (Treble boost)
	static equalizer_t const tinny_eq;      // Tinny EQ (Like a handheld speaker)

// File save/load

	// Save emulator state
	void save_state( Nes_State* s ) const { emu.save_state( s ); }
	const char * save_state( Auto_File_Writer ) const;

	// Load state into emulator
	void load_state( Nes_State const& );
	const char * load_state( Auto_File_Reader );

	// True if current cartridge claims it uses battery-backed memory
	bool has_battery_ram() const { return cart()->has_battery_ram(); }

	// Save current battery RAM
	const char * save_battery_ram( Auto_File_Writer );

	// Load battery RAM from file. Best called just after reset() or loading cartridge.
	const char * load_battery_ram( Auto_File_Reader );

// Graphics

	// Number of frames generated per second
	enum { frame_rate = 60 };

	// Size of fixed NES color table (including the 8 color emphasis modes)
	enum { color_table_size = 8 * 64 };

	// NES color lookup table based on standard NTSC TV decoder. Use nes_ntsc.h to
	// generate a palette with custom parameters.
	struct rgb_t { unsigned char red, green, blue; };
	static rgb_t const nes_colors [color_table_size];

	// Hide/show/enhance sprites. Sprite mode does not affect emulation accuracy.
	enum sprite_mode_t {
		sprites_hidden = 0,
		sprites_visible = 8,  // limit of 8 sprites per scanline as on NES (default)
		sprites_enhanced = 64 // unlimited sprites per scanline (no flickering)
	};
	void set_sprite_mode( sprite_mode_t n ) { emu.ppu.sprite_limit = n; }

	// Set range of host palette entries to use in graphics buffer; default uses
	// all of them. Begin will be rounded up to next multiple of palette_alignment.
	// Use frame().palette_begin to find the adjusted beginning entry used.
	enum { palette_alignment = 64 };
	void set_palette_range( int begin, int end = 256 );

// Access to emulated memory, for viewer/cheater/debugger

	// CHR
	uint8_t const* chr_mem();
	long chr_size() const;
	void write_chr( void const*, long count, long offset );

	// Nametable
	uint8_t* nametable_mem()       { return emu.ppu.impl->nt_ram; }
	long nametable_size() const { return 0x1000; }

	// Built-in 2K memory
	enum { low_mem_size = 0x800 };
	uint8_t* low_mem()             { return emu.low_mem; }

	// Optional 8K memory
	enum { high_mem_size = 0x2000 };
	uint8_t* high_mem()            { return emu.impl->sram; }

	// End of public interface
public:
	const char * set_sample_rate( long rate, class Nes_Buffer* );
	const char * set_sample_rate( long rate, class Nes_Effects_Buffer* );
	void irq_changed() { emu.irq_changed(); }
private:

	frame_t* frame_;
	int buffer_height_;
	bool fade_sound_in;
	bool fade_sound_out;
	virtual const char * init_();

	virtual void loading_state( Nes_State const& ) { }
	void load_state( Nes_State_ const& );
	void save_state( Nes_State_* s ) const { emu.save_state( s ); }
	int joypad_read_count() const { return emu.joypad_read_count; }
	long timestamp() const { return emu.nes.frame_count; }
	void set_timestamp( long t ) { emu.nes.frame_count = t; }

private:
	// noncopyable
	Nes_Emu( const Nes_Emu& );
	Nes_Emu& operator = ( const Nes_Emu& );

	// sound
	Multi_Buffer* default_sound_buf;
	Multi_Buffer* sound_buf;
	unsigned sound_buf_changed_count;
	Silent_Buffer silent_buffer;
	equalizer_t equalizer_;
	int channel_count_;
	bool sound_enabled;
	void enable_sound( bool );
	void clear_sound_buf();
	void fade_samples( blip_sample_t*, int size, int step );

	char* host_pixels;
	int host_palette_size;
	frame_t single_frame;
	Nes_Cart private_cart;
	Nes_Core emu; // large; keep at end

	bool init_called;
	const char * auto_init();

	bool extra_fade_sound_in;
	bool extra_fade_sound_out;
	unsigned extra_sound_buf_changed_count;
public:
	void SaveAudioBufferState();
	void RestoreAudioBufferState();
};

inline void Nes_Emu::set_pixels( void* p, long n )
{
	host_pixels = (char*) p + n;
	emu.ppu.host_row_bytes = n;
}

inline uint8_t const* Nes_Emu::chr_mem()
{
	return cart()->chr_size() ? (uint8_t*) cart()->chr() : emu.ppu.impl->chr_ram;
}

inline long Nes_Emu::chr_size() const
{
	return cart()->chr_size() ? cart()->chr_size() : emu.ppu.chr_addr_size;
}

#endif
