//////////////////////////////////////////////////////////////////////////
//                                                                      //
//      NES Emulation core                                              //
//                                                           Norix      //
//                                               written     2001/02/22 //
//                                               last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#ifndef	__NES_INCLUDED__
#define	__NES_INCLUDED__

#define	WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include "typedef.h"
#include "macro.h"

#include "state.h"
#include "cheat.h"
#include "VsUnisystem.h"

#include <vector>
using namespace std;

#define	NES_PROFILER	0

// コンフィグ
#define	FETCH_CYCLES	8

typedef struct tagNESCONFIG {
	FLOAT	BaseClock;		// NTSC:21477270.0  PAL:21281364.0
	FLOAT	CpuClock;		// NTSC: 1789772.5  PAL: 1773447.0

	INT	TotalScanlines;		// NTSC: 262  PAL: 312

	INT	ScanlineCycles;		// NTSC:1364  PAL:1362

	INT	HDrawCycles;		// NTSC:1024  PAL:1024
	INT	HBlankCycles;		// NTSC: 340  PAL: 338
	INT	ScanlineEndCycles;	// NTSC:   4  PAL:   2

	INT	FrameCycles;		// NTSC:29829.52  PAL:35468.94
	INT	FrameIrqCycles;		// NTSC:29829.52  PAL:35468.94

	INT	FrameRate;		// NTSC:60(59.94) PAL:50
	FLOAT	FramePeriod;		// NTSC:16.683    PAL:20.0
} NESCONFIG, *LPNESCONFIG;

extern	NESCONFIG NESCONFIG_NTSC;
extern	NESCONFIG NESCONFIG_PAL;

// class prototypes
class	CPU;
class	PPU;
class	APU;
class	ROM;
class	PAD;
class	Mapper;

//
// NES クラス
//
class NES
{
public:
// メンバ変数
	CPU*	cpu;
	PPU*	ppu;
	APU*	apu;
	ROM*	rom;
	PAD*	pad;
	Mapper*	mapper;

	NESCONFIG* nescfg;
public:
	NES( const char* fname );
	virtual	~NES();

// メンバ関数
	// エミュレーション
	void	Reset();
	void	SoftReset();

	void	Clock( INT cycles );

	BYTE	Read ( WORD addr );
	void	Write( WORD addr, BYTE data );

	void	EmulationCPU( INT basecycles );
	void	EmulationCPU_BeforeNMI( INT cycles );

	void	EmulateFrame( BOOL bDraw );

	// For NSF
	void	EmulateNSF();
	void	SetNsfPlay( INT songno, INT songmode );
	void	SetNsfStop();
	BOOL	IsNsfPlaying() { return m_bNsfPlaying; };

	// IRQ type contorol
	enum IRQMETHOD { 
		IRQ_HSYNC = 0,
		IRQ_CLOCK = 1
	};
	void	SetIrqType( INT nType ) { nIRQtype = nType; };
	INT	GetIrqType() { return (INT)nIRQtype; };

	// Frame-IRQ control (for Paris-Dakar Rally Special)
	void	SetFrameIRQmode( BOOL bMode ) { bFrameIRQ = bMode; }
	BOOL	GetFrameIRQmode() { return bFrameIRQ; }

	// NTSC/PAL
	void	SetVideoMode( BOOL bMode );
	BOOL	GetVideoMode() { return bVideoMode; }

	//
	INT	GetDiskNo();
	void	SoundSetup();

	INT	GetScanline()	{ return NES_scanline; }
	BOOL	GetZapperHit()	{ return bZapper; }
	void	GetZapperPos( LONG&x, LONG&y )	{ x = ZapperX; y = ZapperY; }
	void	SetZapperPos( LONG x, LONG y )	{ ZapperX = x; ZapperY = y; }

	// ステートファイル
	// 0:ERROR 1:CRC OK -1:CRC ERR
	static	INT	IsStateFile( const char* fname, ROM* rom );
	BOOL	LoadState( const char* fname );
	BOOL	SaveState( const char* fname );

	INT	GetSAVERAM_SIZE()		{ return SAVERAM_SIZE; }
	void	SetSAVERAM_SIZE( INT size )	{ SAVERAM_SIZE = size; }

	// VS-Unisystem
	BYTE	GetVSDipSwitch()		{ return m_VSDipValue; }
	void	SetVSDipSwitch( BYTE v )	{ m_VSDipValue = v; }
	VSDIPSWITCH*	GetVSDipSwitchTable()	{ return m_VSDipTable; }

	// スナップショット
	BOOL	Snapshot();

	// For Movie
	// 0:ERROR 1:CRC OK -1:CRC ERR
	static	INT	IsMovieFile( const char* fname, ROM* rom );

	BOOL	IsMoviePlay() { return m_bMoviePlay; }
	BOOL	IsMovieRec() { return m_bMovieRec; }
	BOOL	MoviePlay( const char* fname );
	BOOL	MovieRec( const char* fname );
	BOOL	MovieRecAppend( const char* fname );
	BOOL	MovieStop();

	// その他コントロール
	BOOL	IsDiskThrottle() { return m_bDiskThrottle; }
//	BOOL	IsBraking() { return m_bBrake; }	// Debugger

	// 描画方式
	enum RENDERMETHOD { 
		POST_ALL_RENDER = 0, // スキャンライン分の命令実行後,レンダリング
		PRE_ALL_RENDER  = 1, // レンダリングの実行後,スキャンライン分の命令実行
		POST_RENDER     = 2, // 表示期間分の命令実行後,レンダリング
		PRE_RENDER      = 3, // レンダリング実行後,表示期間分の命令実行
		TILE_RENDER     = 4  // タイルベースレンダリング
	};
	void		SetRenderMethod(RENDERMETHOD type);
	RENDERMETHOD	GetRenderMethod()			{ return RenderMethod; }

	// コマンド
	enum NESCOMMAND {
		NESCMD_NONE = 0,
		NESCMD_HWRESET,
		NESCMD_SWRESET,
		NESCMD_EXCONTROLLER,	// Commandparam
		NESCMD_DISK_THROTTLE_ON,
		NESCMD_DISK_THROTTLE_OFF,
		NESCMD_DISK_EJECT,
		NESCMD_DISK_0A,
		NESCMD_DISK_0B,
		NESCMD_DISK_1A,
		NESCMD_DISK_1B,
		NESCMD_DISK_2A,
		NESCMD_DISK_2B,
		NESCMD_DISK_3A,
		NESCMD_DISK_3B,

		NESCMD_SOUND_MUTE,	// CommandParam
	};

	void	Command( NESCOMMAND cmd );
	BOOL	CommandParam( NESCOMMAND cmd, INT param );

	// For Movie
	void	Movie();
	void	GetMovieInfo( WORD& wRecVersion, WORD& wVersion, DWORD& dwRecordFrames, DWORD& dwRecordTimes );

	// For Cheat
	void	CheatInitial();

	BOOL	IsCheatCodeAdd();

	INT	GetCheatCodeNum();
	BOOL	GetCheatCode( INT no, CHEATCODE& code );
	void	SetCheatCodeFlag( INT no, BOOL bEnable );
	void	SetCheatCodeAllFlag( BOOL bEnable, BOOL bKey );

	void	ReplaceCheatCode( INT no, CHEATCODE code );
	void	AddCheatCode( CHEATCODE code );
	void	DelCheatCode( INT no );

	DWORD	CheatRead( INT length, WORD addr );
	void	CheatWrite( INT length, WORD addr, DWORD data );
	void	CheatCodeProcess();

	// For Genie
	void	GenieInitial();
	void	GenieLoad( char* fname );
	void	GenieCodeProcess();

	// TapeDevice
	BOOL	IsTapePlay() { return m_bTapePlay; }
	BOOL	IsTapeRec() { return m_bTapeRec; }
	BOOL	TapePlay( const char* fname );
	BOOL	TapeRec( const char* fname );
	void	TapeStop();
	void	Tape( INT cycles );

	// Barcode battler(Bandai)
	void	SetBarcodeData( LPBYTE code, INT len );
	void	Barcode( INT cycles );
	BOOL	IsBarcodeEnable() { return m_bBarcode; };
	BYTE	GetBarcodeStatus() { return m_BarcodeOut; };

	// Barcode world(Sunsoft/EPOCH)
	void	SetBarcode2Data( LPBYTE code, INT len );
	BYTE	Barcode2( void );
	BOOL	IsBarcode2Enable() { return m_bBarcode2; };

	// TurboFile
	void	SetTurboFileBank( INT bank ) { m_TurboFileBank = bank; }
	INT	GetTurboFileBank() { return m_TurboFileBank; }

#if	NES_PROFILER
	// TEST
	DWORD	GetFrameTotalCycles() {
		return	m_dwTotalCycle;
	}
	DWORD	GetProfileTotalCycles() {
		return	m_dwProfileTotalCycle;
	}
	DWORD	GetProfileTotalCount() {
		return	m_dwProfileTotalCount;
	}
	DWORD	GetProfileCycles() {
		return	m_dwProfileCycle;
	}
#endif
protected:
// メンバ関数
	// エミュレーション
	BYTE	ReadReg ( WORD addr );
	void	WriteReg( WORD addr, BYTE data );

	// ステートサブ
	BOOL	ReadState( FILE* fp );
	void	WriteState( FILE* fp );

	void	LoadSRAM();
	void	SaveSRAM();

	void	LoadDISK();
	void	SaveDISK();

	void	LoadTurboFile();
	void	SaveTurboFile();

protected:
// メンバ変数
	INT	nIRQtype;
	BOOL	bVideoMode;
	BOOL	bFrameIRQ;

	BOOL	bZapper;
	LONG	ZapperX, ZapperY;

	BOOL	m_bPadStrobe;

	RENDERMETHOD	RenderMethod;

	BOOL	m_bDiskThrottle;

	SQWORD	base_cycles;
	SQWORD	emul_cycles;

	INT	NES_scanline;

	INT	SAVERAM_SIZE;

	// For VS-Unisystem
	BYTE		m_VSDipValue;
	VSDIPSWITCH*	m_VSDipTable;

	// Snapshot number
	INT	m_nSnapNo;

	// For NSF
	BOOL	m_bNsfPlaying;
	BOOL	m_bNsfInit;
	INT	m_nNsfSongNo;
	INT	m_nNsfSongMode;

	// For Movie
	BOOL	m_bMoviePlay;
	BOOL	m_bMovieRec;
	WORD	m_MovieVersion;

	FILE*	m_fpMovie;
	MOVIEFILEHDR	m_hedMovie;
	DWORD	m_MovieControl;
	LONG	m_MovieStepTotal;
	LONG	m_MovieStep;
	INT	m_CommandRequest;

	// For Tape
	BOOL	m_bTapePlay;
	BOOL	m_bTapeRec;
	FILE*	m_fpTape;
	double	m_TapeCycles;
	BYTE	m_TapeIn;
	BYTE	m_TapeOut;

	// For Barcode
	BOOL	m_bBarcode;
	BYTE	m_BarcodeOut;
	BYTE	m_BarcodePtr;
	INT	m_BarcodeCycles;
	BYTE	m_BarcodeData[256];

	// For Barcode
	BOOL	m_bBarcode2;
	INT	m_Barcode2seq;
	INT	m_Barcode2ptr;
	INT	m_Barcode2cnt;
	BYTE	m_Barcode2bit;
	BYTE	m_Barcode2data[32];

	// For TurboFile
	INT	m_TurboFileBank;

	// gameoption backup
	INT	m_saveRenderMethod;
	INT	m_saveIrqType;
	BOOL	m_saveFrameIRQ;
	BOOL	m_saveVideoMode;

	// For Cheat
	BOOL	m_bCheatCodeAdd;
	vector<CHEATCODE>	m_CheatCode;

	// For Genie
	vector<GENIECODE>	m_GenieCode;

	// For Movie pad display
	void	DrawPad();
	void	DrawBitmap( INT x, INT y, LPBYTE lpBitmap );
	static	BYTE	m_PadImg[];
	static	BYTE	m_KeyImg0[];
	static	BYTE	m_KeyImg1[];
	static	BYTE	m_KeyImg2[];

#if	NES_PROFILER
	// TEST
	BYTE	m_ProfileEnable;
	DWORD	m_dwTotalCycle;
	DWORD	m_dwTotalTempCycle;
	DWORD	m_dwProfileTotalCycle;
	DWORD	m_dwProfileTotalCount;
	DWORD	m_dwProfileCycle;
	DWORD	m_dwProfileTempCycle;
	DWORD	m_dwProfileAveCycle;
	DWORD	m_dwProfileMaxCycle;

//	static	BYTE	Font6x8[];

//	void	DrawFont( INT x, INT y, BYTE chr, BYTE col );
//	void	DrawString( INT x, INT y, LPSTR str, BYTE col );
#endif
	static	BYTE	Font6x8[];

	void	DrawFont( INT x, INT y, BYTE chr, BYTE col );
	void	DrawString( INT x, INT y, LPSTR str, BYTE col );
private:
};

#endif	// !__NES_INCLUDED__