// Memory.h: interface for the CMemory class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_MEMORY_H__589054AE_0D70_499D_B128_EEBBEEC2A5F0__INCLUDED_)
#define AFX_MEMORY_H__589054AE_0D70_499D_B128_EEBBEEC2A5F0__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "platform.h"
#include <string.h> //for memset function
#include <stdio.h>
//#include "interrupt.h"

#if NMGB_DEBUG_ON
	#include <assert.h>
#else
	#define	assert(str)
#endif


#define MAX_MEMBANKCACHE	3

template<int bank_count /*= MAX_MEMBANKCACHE*/, unsigned int bank_size /*= 16 * 1024*/>
class CMemBankCache
{
	class _MemBank
	{
	public:
		int m_nBankIndex;
		u8  m_BankData[bank_size];
	public:
		_MemBank()
		{
			m_nBankIndex = -1;
			memset(m_BankData, 0, bank_size);
		}
	};
public:
	CMemBankCache()
	{
		m_ppBankCache = new _MemBank*[bank_count];
		for(int i=0; i<bank_count; i++)
		{
			m_ppBankCache[i] = new _MemBank;
			if(!m_ppBankCache[i])
			{
				NMGB_DEBUG_PRINT(NMGB_DEBUG_COLOR_RED,"Crash in CMemBankCache, when new()!\n");
				break;
			}
		}
		m_pCurrBank = m_ppBankCache[0];

	}
	/*virtual*/ ~CMemBankCache()
	{
		for(int i=0; i<bank_count; i++)
		{
			delete m_ppBankCache[i];
			//m_ppBankCache[i] = NULL;
		}
		delete[] m_ppBankCache;
	}

	inline u8 operator [] (int nOffset)	const
	{
		assert(nOffset < bank_size && nOffset >= 0);
		return m_pCurrBank->m_BankData[nOffset];
	}

	inline u8& operator [] (int nOffset)
	{
		assert(nOffset < bank_size && nOffset >= 0);
		return m_pCurrBank->m_BankData[nOffset];
	}

	void Memset_Random()
	{
		for(int i=0; i<bank_count; i++)
		{
			for(int j=0; j<bank_size; j++)
				m_ppBankCache[i]->m_BankData[j] = rand();
		}
	}

	void Memset(u8 val)
	{
		for(int i=0; i<bank_count; i++)
			memset(&(m_ppBankCache[i]->m_BankData), val, bank_size);
	}

	void SetBank(int nBankIndex)
	{
		if(m_pCurrBank->m_nBankIndex == nBankIndex)
			return;

		int i=0;
		for(i=0; i<bank_count; i++)
		{
			if(m_ppBankCache[i]->m_nBankIndex == nBankIndex)
			{
				m_pCurrBank = m_ppBankCache[i];
				break;
			}
		}

		if(i >= bank_count) //not found in cache
		{
			ReadBank(g_configinfo.m_sROMFile,nBankIndex);
			//m_pCurrBank = m_ppBankCache[i];
		}
	}

	void ReadBank(const char* sFilePath, int nBankIndex)
	{
		FILE* fp = NULL;
		fp = fopen(sFilePath, "rb");
		if(fp)
		{
			int result = fseek(fp, nBankIndex*bank_size, SEEK_SET);
			if( result )
				NMGB_DEBUG_PRINT(NMGB_DEBUG_COLOR_RED,"Seeking File Error : Bank %d of %s !\n",nBankIndex,sFilePath);
			else
			{
				int j=0;
				for(j=0; j<bank_count && !feof(fp); j++)
				{
					int count = fread(&(m_ppBankCache[j]->m_BankData), bank_size, 1, fp);

					if(count > 0)
					{
						int nCurBank = nBankIndex+j;
						m_ppBankCache[j]->m_nBankIndex = nCurBank;

						NMGB_DEBUG_PRINT(NMGB_DEBUG_COLOR_GREEN,"Reading Bank %d OK!",nCurBank);
					}
					else
					{
						j--;
						break;
					}

				}

				if(j < bank_count-1)
				{
					for(int k=j+1; k<bank_count; k++)
					{
						m_ppBankCache[k]->m_nBankIndex = -1;
					}
				}
			}
		}
		else
		{
			NMGB_DEBUG_PRINT(NMGB_DEBUG_COLOR_RED,"Open ROM file: %s for Reading Bank %d error!\n", sFilePath,nBankIndex);
		}
		fclose(fp);

		m_pCurrBank = m_ppBankCache[0];
	}
#if NMGB_INTEGRATE_ROM
	inline void ReadBank(const char* sFilePath, int nBankIndex, u32 filesize)
	{
		u8* base_addr = (u8*)sFilePath + nBankIndex*bank_size;
		int i=0;
		for(i=0; i<bank_count; i++)
		{
			if( (nBankIndex+i+1)*bank_size > filesize)
				break;
			memcpy(m_ppBankCache[i]->m_BankData, base_addr, bank_size);
			m_ppBankCache[i]->m_nBankIndex = nBankIndex+i;
			base_addr += bank_size;
		}
		if(i < bank_count-1)
		{
			for(int k=i+1; k<bank_count; k++)
			{
				m_ppBankCache[k]->m_nBankIndex = -1;
			}
		}

		m_pCurrBank = m_ppBankCache[0];
	}
#endif
private:
	_MemBank**	m_ppBankCache;
	_MemBank*	m_pCurrBank;
	//char*		m_pFilePath;
};

typedef CMemBankCache< 1, 16 * 1024 >	CROMSwitch;
typedef CMemBankCache< 4, 8 * 1024 >	CExternRAM;

class CMBC1;
class CMBC2;
class CMBC3;
class CMBC4;
class CMBC5;
class CMBC6;
class CMBC7;
class CZ80m;
class CInterrupt;
class CGBMachine;
class CGBCMachine;
class CSGBMachine;
class CInput;

class CMemory  
{
	friend class CMBCNone;
	friend class CMBC1;
	friend class CMBC2;
	friend class CMBC3;
	friend class CMBC4;
	friend class CMBC5;
	friend class CMBC6;
	friend class CMBC7;
	friend class CZ80m;
	friend class CInterrupt;
	friend class CGBMachine;
	friend class CGBCMachine;
	friend class CSGBMachine;
	friend class CInput;
public:
	CMemory();
	virtual ~CMemory();
public:
	inline void SetMachine(CGBMachine* pMachine)
	{
		m_pMachine = pMachine;
	}
	inline void SetCPU(CZ80m* pCPU)
	{
		m_pCPU = pCPU;
	}
	inline void SetInterrupt(CInterrupt* pInterrupt)
	{
		m_pInterrupt = pInterrupt;
	}
	inline void SetInput(CInput* pInput)
	{
		m_pInput = pInput;
	}

	u8 Read8(u32 addr);
	u16 Read16(u32 addr);
	void Write8(u32 addr, u32 value);
	void Write16(u32 addr, u32 value);
	void WriteReg8(u32 addr, u32 value);
	u8 ReadReg8(u32 addr);


	//ExternRAMEnabled
	inline void ExternRAMEnabled(bool bEnable)
	{
		m_bExternRAMEnabled = bEnable;
	}
	inline bool ExternRAMEnabled() const
	{
		return m_bExternRAMEnabled;
	}

	//RAMBankNum
	inline void RAMBankNum(int nRAMBankNum)
	{
		m_nRAMBankNum = nRAMBankNum;
	}
	inline int RAMBankNum() const
	{
		return m_nRAMBankNum;
	}

	//ROMBackNum
	inline void ROMBankNum(int nROMBackNum)
	{
		m_nROMBackNum = nROMBackNum;
	}
	inline int ROMBankNum() const
	{
		return m_nROMBackNum;
	}
	
	//SelectedROMIndex
	inline void SelectedROMIndex(int nSelectedROMIndex)
	{
		m_nSelectedROMIndex = nSelectedROMIndex;
	}
	inline int SelectedROMIndex() const
	{
		return m_nSelectedROMIndex;
	}

	//SelectedRAMIndex
	inline void SelectedRAMIndex(int nSelectedRAMIndex)
	{
		m_nSelectedRAMIndex = nSelectedRAMIndex;
	}
	inline int SelectedRAMIndex() const
	{
		return m_nSelectedRAMIndex;
	}

	void InitMemory();

	inline void CompareLYC2LY(void)
	{
		if(m_IOPorts[LY_REG-0xFF00] == m_IOPorts[LYC_REG-0xFF00])
			m_IOPorts[STAT_REG-0xFF00] |= 0x04; //set bit 2 in stat,when LYC == LY
		else
			m_IOPorts[STAT_REG-0xFF00] &= ~0x04; //reset bit 2 in stat,when LYC != LY
	}

	void DumpDebugInfo();

private:
	u8	m_ROM0[16*1024];	//0000 | 16KB
//	u8* m_ROM1[512];		//4000 | 16KB
	CROMSwitch m_ROM1;
	u8  m_VideoRAM[0x4000]; //8000 | 8KB -- 2 banks in GBC - Only 0x2000 needed in gb mode, but
//	u8* m_ExternRAM[16];    //A000 | 8KB                   | let's allocate that anyway...
	CExternRAM m_EXRAM;
	u8  m_WorkRAM0[0x1000]; //C000 | 4KB
	//u8* m_WorkRAM1[7];	//D000 | 4KB -- 8 banks in GBC -- 0 only accesible from C000-CFFF
	u8	m_WorkRAM1[0x1000];
	//E000 -- ram echo
	u8  m_ObjAttrMem[0xA0]; //FE00 | (40 * 4) B
	u8  m_StrangeRAM[0x30]; //FEA0 -- strange RAM - (only GBC, see memory.c for the reason of
	u8  m_IOPorts[0x80];    //FF00 | 128B         | 0x30 instead of 0x60)
	u8  m_HighRAM[0x80];    //FF80 | 128B

	bool	m_bExternRAMEnabled;
	int		m_nRAMBankNum;
	u32		m_nROMBackNum;
	u32		m_nSelectedROMIndex;
	u32		m_nSelectedRAMIndex;

	CGBMachine* m_pMachine;
	CInterrupt*	m_pInterrupt;
	CZ80m*		m_pCPU;
	CInput*		m_pInput;
};

//#include "Memory.in"

#endif // !defined(AFX_MEMORY_H__589054AE_0D70_499D_B128_EEBBEEC2A5F0__INCLUDED_)
