// interrupt.cpp: implementation of the CInterrupt class.
//
//////////////////////////////////////////////////////////////////////

#include "platform.h"
#include "interrupt.h"
#include "Memory.h"
#include "Z80m.h"
#include "GBMachine.h"
#include "Input.h"

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

extern bool FB_OK;

CInterrupt::CInterrupt()
{
	m_pMemory = NULL;
	m_pCPU = NULL;
	m_pMachine = NULL;

	m_nVBlankDelayClks = 0;

	//CurrentScanLine = 0;

	//the following calc formular is: CPU freq / timer freq
	//e.g. m_TimeFreq[3] = 4194304/16384 = 256
	m_nTimeFreq[0] = 1024;	// 4096 Hz	~4184 Hz on SGB
	m_nTimeFreq[1] = 16;		// 262144 Hz ~268400 Hz
	m_nTimeFreq[2] = 64;		// 65536 Hz ~67110 Hz
	m_nTimeFreq[3] = 256;	// 16384 Hz ~16780 Hz

	InterruptsInit();
}

CInterrupt::~CInterrupt()
{

}

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

void CInterrupt::SetMachine(CGBMachine* pMachine)
{
	m_pMachine = pMachine;
}
void CInterrupt::SetCPU(CZ80m* pCPU)
{
	m_pCPU = pCPU;
}
void CInterrupt::SetMemory(CMemory* pMemory)
{
	m_pMemory = pMemory;
}

void CInterrupt::SetInput(CInput* pInput)
{
	m_pInput = pInput;
}


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

void CInterrupt::SetInterrupt(int flag)
{
	// 	GameBoy.Memory.IO_Ports[IF_REG-0xFF00] |= flag;
	// 	if(GameBoy.Memory.HighRAM[IE_REG-0xFF80] & flag)
	//         GameBoy.Emulator.CPUHalt = 0;
	m_pMemory->m_IOPorts[IF_REG-0xFF00] |= flag;
	if(m_pMemory->m_HighRAM[IE_REG-0xFF80] & flag)
		m_pMachine->HaltStop(GB_NORM);
}

int CInterrupt::HandleInterrupts()
{
	int interrupts = m_pMemory->m_IOPorts[IF_REG-0xFF00] & m_pMemory->m_HighRAM[IE_REG-0xFF80] & 0x1F;
	
	if(interrupts == 0)
		return 0;
	
	//if(mem->InterruptMasterEnable == 0 || GameBoy.Emulator.CPUHalt) return 0;
	if(!m_pMachine->IME() || m_pMachine->HaltStop())
		return 0;
	
	//GameBoy.Memory.InterruptMasterEnable = 0;
	m_pMachine->IME(false);
	
	m_pCPU->m_Reg16.SP -= 2;
	m_pMemory->Write16(m_pCPU->m_Reg16.SP, m_pCPU->m_Reg16.PC);
	
	if(interrupts & I_VBLANK)
	{
		m_pCPU->m_Reg16.PC = 0x0040;
		m_pMemory->m_IOPorts[IF_REG-0xFF00] &= ~I_VBLANK;
		return 1;
	}
	else if(interrupts & I_STAT)
	{
		m_pCPU->m_Reg16.PC = 0x0048;
		m_pMemory->m_IOPorts[IF_REG-0xFF00] &= ~I_STAT;
		return 1;
	}
	else if(interrupts & I_TIMER)
	{
		m_pCPU->m_Reg16.PC = 0x0050;
		m_pMemory->m_IOPorts[IF_REG-0xFF00] &= ~I_TIMER;
		return 1;
	}
	else if(interrupts & I_SERIAL)
	{
		m_pCPU->m_Reg16.PC = 0x0058;
		m_pMemory->m_IOPorts[IF_REG-0xFF00] &= ~I_SERIAL;
		return 1;
	}
	else //if(interrupts & I_JOYPAD)
	{
		m_pCPU->m_Reg16.PC = 0x0060;
		m_pMemory->m_IOPorts[IF_REG-0xFF00] &= ~I_JOYPAD;
		return 1;
	}
}

void CInterrupt::InterruptsInit(void)
{

}

/*inline*/ void CInterrupt::CheckStatInterrupt(void)
{
	u8& lcdc = m_pMemory->m_IOPorts[LCDC_REG - 0xFF00];

	bool lcd_on = lcdc & 0x80 ? true : false;

	if(!lcd_on)
		return;

	int stat = m_pMemory->m_IOPorts[STAT_REG-0xFF00];

	u8 screenmode = stat & 0x03;
	
	if(    
		(m_pMemory->m_IOPorts[LY_REG-0xFF00] == m_pMemory->m_IOPorts[LYC_REG-0xFF00] && stat & 0x40 /*enable LY and LYC compare?*/)  || 
		(stat & 0x08/*enable H-Blank interrupt?*/ && screenmode == 0) ||
		(stat & 0x20/*enable OAM interrupt?*/ && screenmode == 2)  ||
		(stat & (1<<4/*V-Blank*/ | 1<<5/*OAM*/) && screenmode == 1)
		)
		SetInterrupt(I_STAT);
		
	return;
}


void CInterrupt::HandleClock(void)
{
	//////////////////////////////////////////////////////////////////////////
	// (1) SCREEN
	//////////////////////////////////////////////////////////////////////////

	u8& stat = m_pMemory->m_IOPorts[STAT_REG - 0xFF00];
	u8& ly = m_pMemory->m_IOPorts[LY_REG-0xFF00];

	switch(stat & 0x03)
	{
		case 2: // during searching OAM-RAM
			if(m_pCPU->m_nClocks > 79)
			{
				m_pCPU->m_nClocks -= 80;

				stat |= 0x03;

				CheckStatInterrupt();
			}
			break;
		case 3: // during transfering data to LCD driver
			if(m_pCPU->m_nClocks > 171) 
			{
				m_pCPU->m_nClocks -= 172; 

				//if(ly %2 == 1)
					m_pMachine->DrawScanLine(); //draw scanline stored in LY_REG

				stat &= 0xFC;

				CheckStatInterrupt();
			}
			break;
		case 0: // during H-Blank
			if(m_pCPU->m_nClocks > 203) //(GameBoy.Emulator.mode0len-1))
			{
				m_pCPU->m_nClocks -= 204; //GameBoy.Emulator.mode0len;

				//CurrentScanLine++;
				ly++;
				//ly = CurrentScanLine;

				m_pMemory->CompareLYC2LY();

				if(ly/*CurrentScanLine*/ == 144)
				{
					//set screen mode to 1 //V-Blank...
					stat &= 0xFC;
					stat |= 0x01;

					//Call VBL interrupt...
					if(m_pMemory->m_IOPorts[LCDC_REG-0xFF00] & 0x80)//LCD is on
					{
						m_nVBlankDelayClks = VBLANK_DELAY_CLKS;
					}
				}
				else
				{
					//set screen mode to 2 //Searching OAM RAM...
					stat &= 0xFC;
					stat |= 0x02;
				}

				CheckStatInterrupt();
			}
			break;
		case 1: // during V-Blank
			if(m_pCPU->m_nClocks > 455)
			{
				m_pCPU->m_nClocks -= 456;

				if(ly/*CurrentScanLine*/ == 0)
				{
					//set screen mode to 2 //Searching OAM RAM...
					stat &= 0xFC;
					stat |= 0x02;
					CheckStatInterrupt();
					break;
				}

				int scanline = ly+1;
				//CurrentScanLine++;

				if(scanline/*CurrentScanLine*/ == 153)
				{
					m_pCPU->m_nClocks += 456 - 8; // 8 clocks this scanline

					ly++; // move to next scanline
				}
				else if(scanline/*CurrentScanLine*/ == 154)
				{
					//@@@@@@@@@@@@@@@@@@@@@@@@@
					//draw a frame butter(»­Ò»Ö¡)
					//@@@@@@@@@@@@@@@@@@@@@@@@@

					//CurrentScanLine = 0;
					scanline = 0; // move to top screen line

					FB_OK = true;
		
					m_pCPU->m_nClocks += 8; // 456 - 8 cycles left of vblank...
				}

				ly = scanline/*CurrentScanLine*/;

				m_pMemory->CompareLYC2LY();

				CheckStatInterrupt();
			}
			break;
	}
//}
	//////////////////////////////////////////////////////////////////////////
	// (2) TIMERS
	//////////////////////////////////////////////////////////////////////////

	// (2.1) DIV -- EVERY 256 CLOCKS
	u8& div = m_pMemory->m_IOPorts[DIV_REG-0xFF00];
	while(m_pCPU->m_nDivClks > 255)
	{
		div++;
		m_pCPU->m_nDivClks -= 256;
	}

	// (2.2) TIMA
	u8& tac = m_pMemory->m_IOPorts[TAC_REG-0xFF00];
	u8& tima = m_pMemory->m_IOPorts[TIMA_REG-0xFF00];
	u8 tma = m_pMemory->m_IOPorts[TMA_REG-0xFF00];
	int total_clks = m_nTimeFreq[tac & 0x03];
	if(tac & 0x04) // Timer is enable
	{
    	while(m_pCPU->m_nTimerClks > (total_clks-1))
    	{
    		if(tima == 0xFF) //overflow
    		{
    			SetInterrupt(I_TIMER);
    			tima = tma;
    		}
    		else 
				tima++;

    		m_pCPU->m_nTimerClks -= total_clks;
    	}
    }

	//////////////////////////////////////////////////////////////////////////
	// (3) JOYPAD
	//////////////////////////////////////////////////////////////////////////

	EHWType hwtype = m_pMachine->GetHWType();
	if( (hwtype == HW_GBC) || (hwtype == HW_GBA) )//No joypad interrupt in GBA/GBC
		return; 


	if(m_pInput->HasKeydown(m_pMemory->m_IOPorts[P1_REG-0xFF00]))
	{
		m_pInput->SetKeyRegister(m_pMemory->m_IOPorts[P1_REG-0xFF00]);
		SetInterrupt(I_JOYPAD);
	}

	return;
}

