// Nintendo Game Boy GBS music file emulator core

// Game_Music_Emu 0.6-pre
#ifndef GBS_CORE_H
#define GBS_CORE_H

#include "Gme_Loader.h"
#include "Rom_Data.h"
#include "Gb_Cpu.h"
#include "Gb_Apu.h"

class Gbs_Core : public Gme_Loader {
public:

	// GBS file header
	struct header_t
	{
		enum { size = 112 };
		
		char tag       [ 3];
		byte vers;
		byte track_count;
		byte first_track;
		byte load_addr [ 2];
		byte init_addr [ 2];
		byte play_addr [ 2];
		byte stack_ptr [ 2];
		byte timer_modulo;
		byte timer_mode;
		char game      [32]; // strings can be 32 chars, NOT terminated
		char author    [32];
		char copyright [32];
		
		// True if header has valid file signature
		bool valid_tag() const;
	};
	
	// Header for currently loaded file
	header_t const& header() const      { return header_; }
	
	// Sound chip
	Gb_Apu& apu()                       { return apu_; }
	
	// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
	void set_tempo( double );
	
	// Starts track, where 0 is the first. Uses specified APU mode.
	blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb );
	
	// Ends time frame at time t
	typedef int time_t; // clock count
	blargg_err_t end_frame( time_t t );
	
	// Clocks between calls to play routine
	time_t play_period() const          { return play_period_; }
	
protected:
	typedef int addr_t;
	
	// Current time
	time_t time() const                 { return cpu.time() + end_time; }
	
	// Runs emulator to time t
	blargg_err_t run_until( time_t t );

	// Runs CPU until time becomes >= 0
	void run_cpu();
	
	// Reads/writes memory and I/O
	int  read_mem(  addr_t );
	void write_mem( addr_t, int );

// Implementation
public:
	Gbs_Core();
	~Gbs_Core();
	virtual void unload();

protected:
	virtual blargg_err_t load_( Data_Reader& );
	
private:
	enum { ram_addr = 0xA000 };
	enum { io_base = 0xFF00 };
	enum { hi_page = io_base - ram_addr };
	
	Rom_Data  rom;
	int       tempo;
	time_t    end_time;
	time_t    play_period_;
	time_t    next_play;
	header_t  header_;
	Gb_Cpu    cpu;
	Gb_Apu    apu_;
	byte      ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
	
	void update_timer();
	void jsr_then_stop( byte const [] );
	void set_bank( int n );
	void write_io_inline( int offset, int data, int base );
	void write_io_( int offset, int data );
	int  read_io(  int offset );
	void write_io( int offset, int data );
};

#endif
