// GBMachine.cpp: implementation of the CGBMachine class.
//
//////////////////////////////////////////////////////////////////////

#include "GBMachine.h"

#include "Z80m.h"
#include "Memory.h"


typedef struct _tag_GB_OAM_ENTRY_ 
{
	u8 Y;
	u8 X;
	u8 Tile;
	u8 Info;
} _GB_OAM_ENTRY_;

typedef struct _tag_GB_OAM_ 
{
	_GB_OAM_ENTRY_ Sprite[40];
} _GB_OAM_ ;


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CGBMachine*	g_pGameBoy = NULL;

u8	CGBMachine::m_cNintendoLogo[0x30]={
	0xCE,0xED,0x66,0x66,0xCC,0x0D,0x00,0x0B,0x03,0x73,0x00,0x83,0x00,0x0C,0x00,0x0D,
	0x00,0x08,0x11,0x1F,0x88,0x89,0x00,0x0E,0xDC,0xCC,0x6E,0xE6,0xDD,0xDD,0xD9,0x99,
	0xBB,0xBB,0x67,0x63,0x6E,0x0E,0xEC,0xCC,0xDD,0xDC,0x99,0x9F,0xBB,0xB9,0x33,0x3E
};

CGBMachine::CGBMachine(const char* sInnerROMFile)
{
	m_dwEnableHW = 0;

	m_HWType = HW_GB;

	m_pMBC = NULL;

	m_pInnerROM = NULL;

// 	m_bStop = false;
// 	m_bHalt = false;
	m_bIME = false;
	m_nHaltStop = 0;

	m_bBootingInnerROM = g_configinfo.m_bLoadInnerROM;

	if(m_bBootingInnerROM && sInnerROMFile)
		LoadInnerROM(sInnerROMFile);


	 m_nPalettes[0] = GB_RGB(31,31,31);
	 m_nPalettes[1] = GB_RGB(21,21,21);
	 m_nPalettes[2] = GB_RGB(10,10,10);
	 m_nPalettes[3] = GB_RGB(0,0,0);

	 m_pFrameBuffer[0] = NULL;
	 m_pFrameBuffer[1] = NULL;
	 m_nCurFrame = 0;
	//m_nCurrentScanLine = 0;

	Init();
}

void CGBMachine::Init()
{
	//CPU
	m_CPU.SetMachine(this);
	m_CPU.SetMemory(&m_Memory);
	m_CPU.SetInterrupt(&m_Interrupt);
	m_CPU.SetInput(&m_Input);

	//Memory
	m_Memory.SetMachine(this);
	m_Memory.SetCPU(&m_CPU);
	m_Memory.SetInterrupt(&m_Interrupt);
	m_Memory.SetInput(&m_Input);
	
	//Interrupt
	m_Interrupt.SetMachine(this);
	m_Interrupt.SetCPU(&m_CPU);
	m_Interrupt.SetMemory(&m_Memory);
	m_Interrupt.SetInput(&m_Input);

	//MBC
	//Do something do SetMBC()

	m_Memory.InitMemory();
}

void CGBMachine::SetMBC(CMBC* pMBC)
{
	m_pMBC = pMBC;
	m_pMBC->SetMemory(&m_Memory);
}

void CGBMachine::LoadInnerROM(const char* sInnerROMFile, u32 InnerROMSize)
{
	if(m_pInnerROM)
	{
		free(m_pInnerROM);
		m_pInnerROM = NULL;
	}
#if !NMGB_INTEGRATE_INNERROM
	FILE* fp = fopen(sInnerROMFile, "rb");
	if(!fp)
	{
		NMGB_DEBUG_PRINT(NMGB_DEBUG_COLOR_RED, "[!]Can not load inner ROM file:%s\n",sInnerROMFile);
		return;
	}

	fseek(fp, 0, SEEK_END);
	long file_size = ftell(fp);
	rewind(fp);

	m_pInnerROM = (u8*)malloc(file_size);

	fread(m_pInnerROM, file_size, 1, fp);

	NMGB_DEBUG_PRINT(NMGB_DEBUG_COLOR_YELLOW, "Load inner ROM OK: %s\n",sInnerROMFile);

	fclose(fp);
#else
	m_pInnerROM = (u8*)malloc(InnerROMSize);
	memcpy(m_pInnerROM, (u8*)sInnerROMFile, InnerROMSize);
#endif
}

CGBMachine::~CGBMachine()
{
 	if(m_pInnerROM)
 		delete m_pInnerROM;
}

CGBCMachine::CGBCMachine(const char* sInnerROMFile):CGBMachine(sInnerROMFile)
{
	
}

CSGBMachine::CSGBMachine(const char* sInnerROMFile):CGBMachine(sInnerROMFile)
{
	
}

//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
// Game Boy 
//////////////////////////////////////////////////////////////////////////

u32 framebuffer_bgcolor0[256];

int window_current_line = 0;

void CGBMachine::DrawScanLine()
{

//	if(Video_HaveToFrameskip()) return;

	u8 y = /*m_Interrupt.CurrentScanLine;//*/m_Memory.m_IOPorts[LY_REG-0xFF00];
	if(y > 143)
		return;

	if(y == 0)
	{
		window_current_line = 0;
		if((m_Memory.m_IOPorts[WX_REG-0xFF00]-7) > 159 && m_Memory.m_IOPorts[WY_REG-0xFF00] > 143)
			window_current_line = -1; // Don't draw
		if( (m_Memory.m_IOPorts[LCDC_REG-0xFF00] & (1<<5)) == 0)//window display is off
			window_current_line = -1; // Don't draw
	}


	u32 x;
#if defined(NMGB_PLATFORM_WIN32)
	u32 base_index = y<<7;
	base_index += y<<5;
#elif defined(NMGB_PLATFORM_NDS)
	u32 base_index = y<<8;
	base_index+=24*256+48;
#endif

	u8 lcd_reg =  m_Memory.m_IOPorts[LCDC_REG-0xFF00];


	if(lcd_reg & 0x80) //SCREEN ENABLED
	{
	    if(HaltStop() == GB_STOP)
	    {
	        for(x = 0; x < 160; x++)
	        {
	        	#if defined(NMGB_PLATFORM_3DS)
	        		memset(m_pFrameBuffer[m_nCurFrame], 31<<3, 400*240*3);
	        	#else
							m_pFrameBuffer[m_nCurFrame][base_index + x] = GB_RGB(31,31,31);
						#endif
					}
	        if(y == 143) 
						m_nCurFrame ^= 1;
	        return;
	    }

		u32 bg_pal[4];
		u32 spr_pal0[4];
		u32 spr_pal1[4];

		//GAMEBOY palettes
		u32 bgp_reg = m_Memory.m_IOPorts[BGP_REG-0xFF00];
		bg_pal[0] = GetGray(bgp_reg & 0x3);
		bg_pal[1] = GetGray((bgp_reg>>2) & 0x3);
		bg_pal[2] = GetGray((bgp_reg>>4) & 0x3);
		bg_pal[3] = GetGray((bgp_reg>>6) & 0x3);

		u32 obp0_reg = m_Memory.m_IOPorts[OBP0_REG-0xFF00];
	//	spr_pal0[0] = GB_GameBoyGetGray(obp0_reg & 0x3);
		spr_pal0[1] = GetGray((obp0_reg>>2) & 0x3);
		spr_pal0[2] = GetGray((obp0_reg>>4) & 0x3);
		spr_pal0[3] = GetGray((obp0_reg>>6) & 0x3);

		u32 obp1_reg = m_Memory.m_IOPorts[OBP1_REG-0xFF00];
	//	spr_pal1[0] = GB_GameBoyGetGray(obp1_reg & 0x3);
		spr_pal1[1] = GetGray((obp1_reg>>2) & 0x3);
		spr_pal1[2] = GetGray((obp1_reg>>4) & 0x3);
		spr_pal1[3] = GetGray((obp1_reg>>6) & 0x3);

		//SCROLLS
		u32 scx_reg = m_Memory.m_IOPorts[SCX_REG-0xFF00];
		u32 scy_reg = m_Memory.m_IOPorts[SCY_REG-0xFF00];
		u32 wx_reg = m_Memory.m_IOPorts[WX_REG-0xFF00];
		u32 wy_reg = m_Memory.m_IOPorts[WY_REG-0xFF00];

		//OTHER
		u8 * tiledata = (lcd_reg & (1<<4)) ? &m_Memory.m_VideoRAM[0x0000] : &m_Memory.m_VideoRAM[0x0800];
		u8 * bgtilemap = (lcd_reg & (1<<3)) ? &m_Memory.m_VideoRAM[0x1C00] : &m_Memory.m_VideoRAM[0x1800];
		u8 * wintilemap = (lcd_reg & (1<<6)) ? &m_Memory.m_VideoRAM[0x1C00] : &m_Memory.m_VideoRAM[0x1800];

		bool increase_win = false;

		//DRAW BG + WIN
		for(x = 0; x < 160; x ++)
		{
			u32 color = GetGray(0);
			bool window_draw = false;
			bool bg_color0 = false;
			

			if(window_current_line >= 0)
			{
				if(lcd_reg & (1<<5)) //WIN
				{
					if(wy_reg <= y)
					if(  (wx_reg < 8)  || ( (wx_reg > 7) && ((wx_reg-7) <= x))  )
					{
						increase_win = true;

						u32 y_ = window_current_line;
						u32 x_ = x-wx_reg;
						u32 tile = wintilemap[( (y_>>3) /** 32*/<<5) + ((x_+7) >> 3)];

						if(!(lcd_reg & (1<<4))) //If tile base is 0x8800
						{
							if(tile & (1<<7)) 
								tile &= 0x7F;
							else 
								tile += 128;
						}

						u8 * data = &tiledata[tile<<4];//????????????为什么tile要左移4位???????????????
													   //!!!!!!!!因为每个tile需要16字节来存储!!!!!!!!!!

						data += ((y_&7) <<1/* *2 */);//将data定位到要显示的扫描线那行(y_行)的起始字节处

						x_ = 7-((x_+7)&7);//将x_定位到该tile块的扫描线行的最左边像素处

						color = ( (*data >> x_) & 1 ) |  ( ( ( (*(data+1)) >> x_)  << 1) & 2);//按两字节的相应位组成一个颜色

						bg_color0 = (color == 0);

						color = bg_pal[color];
						window_draw = true;
					}
				}
			}

			if(lcd_reg & (1<<0) && (!window_draw)) //BG
			{
				u32 x_ = x+scx_reg;
				u32 y_ = y+scy_reg;

				u32 tile = bgtilemap[( ((y_>>3) & 31) /** 32*/<<5) + ((x_ >> 3) & 31)];

				if(!(lcd_reg & (1<<4))) //If tile base is 0x8800
				{
					if(tile & (1<<7)) 
						tile &= 0x7F;
					else 
						tile += 128;
				}

				u8 * data = &tiledata[tile<<4];	//????????????为什么tile要左移4位???????????????????
												//因为tiledata是16字节一个,所以第tile个tiledata对应的地址是tile<<4

				data += (y_&7) << 1;

				x_ = 7-(x_&7);

				color = ( (*data >> x_) & 1 ) |  ( ( ( (*(data+1)) >> x_)  << 1) & 2);

				bg_color0 = (color == 0);

				color = bg_pal[color];
			}
			#if defined(NMGB_PLATFORM_3DS)
				putpixel_FB(y,x,color);
			#else
 				m_pFrameBuffer[m_nCurFrame][base_index + x] = (u16)color;
 			#endif
			framebuffer_bgcolor0[x] = bg_color0;
		}

		if(increase_win) 
			window_current_line ++;

		//Draw sprites... bg transparent color is bg_pal[0]
		if(lcd_reg & (1<<1)) //if sprites, let's see what sprites are drawn in this scanline
		{
			s32 spriteheight =  8 << ((lcd_reg & (1<<2)) != 0);
			_GB_OAM_* GB_OAM = (_GB_OAM_*)m_Memory.m_ObjAttrMem;
			u32 tilemask = ((spriteheight == 16) ? 0xFE : 0xFF); //For 8x16 sprites, last bit is ignored
			_GB_OAM_ENTRY_* GB_Sprite;

			u32 spritesdrawn = 0; //For the 10-sprites-per-scanline limit
			u32 drawsprite[40];

			int a;
			for(a = 0; a < 40; a++)
			{
				GB_Sprite = &GB_OAM->Sprite[a];

				drawsprite[a] = 0;

				s32 real_y = (GB_Sprite->Y-16);

				if( ( real_y <= y ) && ( (real_y+spriteheight) > y ) )
				{
					if(spritesdrawn < 10)
					{
						drawsprite[a] = 1;
						spritesdrawn ++;
					}
				}
			}

			for(a = 39; a >= 0; a --) if(drawsprite[a]) //TODO: FIX?
				/*
				When sprites with different x coordinate values overlap, the one with the
				smaller x coordinate (closer to the left) will have priority and appear above
				any others. This applies in Non CGB Mode only.
				*/
			{
				GB_Sprite = &GB_OAM->Sprite[a];

				s32 real_y = (GB_Sprite->Y-16);

				if( real_y <= y && (real_y+spriteheight) > y)
				{
					u32 tile = GB_Sprite->Tile & tilemask;

					u8 * data = &m_Memory.m_VideoRAM[tile<<4];

					//flip Y
					if(GB_Sprite->Info & (1<<6)) 
						data += (spriteheight-y+real_y-1)<<1;//*2
					else 
						data += ( (y-real_y)*2 );

					//Lets draw the sprite...
					u32 x__;
					s32 real_x = (GB_Sprite->X-8);

					for(x__ = 0; x__ < 8; x__ ++)
					{
						u32 color = ( (*data >> x__) & 1 ) |  ( ( ( (*(data+1)) >> x__)  << 1) & 2);

						if(color != 0) //Color 0 is transparent
						{
							if(GB_Sprite->Info & (1<<4)) 
								color = spr_pal1[color];
							else 
								color = spr_pal0[color];

							s32 x_ = real_x;

							//flip X
							if(GB_Sprite->Info & (1<<5)) 
								x_ = x_+ x__;
							else 
								x_ += 7- x__;

							if(x_ >= 0 && x_ < 160)
							{
								//If Bg has priority and it is enabled...
								if( (GB_Sprite->Info & (1<<7)) && (lcd_reg & (1<<0)) )
								{
									if(framebuffer_bgcolor0[x_])
									{
										#if defined(NMGB_PLATFORM_3DS)
											putpixel_FB(y,x_,color);
										#else
 											m_pFrameBuffer[m_nCurFrame][base_index + x_] = (u16)color;
 										#endif
									}
								}
								else
								{
									#if defined(NMGB_PLATFORM_3DS)
										putpixel_FB(y,x_,color);
									#else
 										m_pFrameBuffer[m_nCurFrame][base_index + x_] = (u16)color;
 									#endif
								}
							}
						}
					}
				}
			}
		}
	}
	else //SCREEN IS OFF
	{
		#if defined(NMGB_PLATFORM_3DS)
			u32 color = GB_RGB(31,31,31);
		#else
			u32 color = GB_RGB(31,31,31);
		#endif
		
		for(x = 0; x < 160; x++) 
		{
			#if defined(NMGB_PLATFORM_3DS)
				putpixel_FB(y,x,color);
 			#else
 				m_pFrameBuffer[m_nCurFrame][base_index + x] = color;
 			#endif
		}
	}

	if(y == 143) 
		m_nCurFrame ^= 1;//XOR异或操作符
}

//////////////////////////////////////////////////////////////////////////
// Game Boy Color
//////////////////////////////////////////////////////////////////////////
void CGBCMachine::DrawScanLine()
{

}

//////////////////////////////////////////////////////////////////////////
// Super Game Boy
//////////////////////////////////////////////////////////////////////////
void CSGBMachine::DrawScanLine()
{
	
}
