/*
 * Electron emulation
 */

#include <string.h>

#include "Electron.h"
#include "utils.h"
#include "Registry.h"

#include "dlc/dlc_register.h"

#define CPU_6502
#define HACK_WRCH
//#define HACK_RDCH
#include "CPU6502Emul.cc"
template CPU6502Emul<ElectronMem>;

#define _mem ((ElectronMem *)(mem))
#define _cpu ((CPU6502Emul<ElectronMem> *)(cpu))

ElectronEmul::ElectronEmul (void)
{
	printf ("Electron emulation: Electron.cc $Revision: 1.6 $\n\n");
	
	mem = new ElectronMem (this);
	dprintf ("Memory Emulator created OK.\n");
	cpu = new CPU6502Emul<ElectronMem> (_mem);
	dprintf ("CPU Emulator created OK.\n");
	cpu->ResetCPU ();
}

ElectronEmul::~ElectronEmul (void)
{
	delete cpu;
	delete mem;
}

void ElectronEmul::ExecThread (void)
{
	while (1)
	{
		_cpu->ExecInstruction ();
		if (_cpu->check_cycles (200, 0))
		{
			// Do 1MHz bus ticks
			DoTicks (100);
			doevents ();
		}
		if (_cpu->check_cycles (40000, 1))
		{
			// Should do other things like render display here
			// (this is 50Hz)
		}
		if (_cpu->check_cycles (100000, 2))
		{
			// That's our time up.
			return;
		}
	}
}

ElectronMem::ElectronMem (ElectronEmul *p)
{
	parent = p;
	
	/* Load the ROMs */
	ReadFile ("elkos.rom", &coremem[0xc000], 0x4000);
	// BASIC appears in banks a and b
	ReadFile ("basic2.rom", swbanks[0xa], 0x4000);
	memcpy (swbanks[0xb], swbanks[0xa], 0x4000);
	dprintf ("Standard ROMs loaded OK.\n");
	// Keyboard is paged in on 8 and 9
	
	kb_paged = 0;
	cur_swbank = -1;
	cur_sw_mod = 0;
	Switch_SWbank (0xa);
	
	// Add the ULA
	ULA = new ElectronULA (this);
	AddDev (ULA);
	parent->Add_Ticker (ULA);
}

ElectronMem::~ElectronMem (void)
{
	delete ULA;		// Also removes it
}

byte ElectronMem::KbdRead (int addr)
{
	int i, j;
	
	for (i = 0, j = 1; i < 14; i++, j <<= 1)
	{
		if (!(addr & j))
		{
			printf ("KBD read col %d\n", i);
		}
	}
	printf ("KBD endread\n\n");
	return 0;
}

/*** ULA implementation ***/

static void Electron_KeyEvent (int row, int col, ElectronULA *ula)
{
	ula->KeyEvent (row, col);
}

ElectronULA::ElectronULA (ElectronMem *m): MMIO_Dev (0xff00, 0xfe00)
{
	addr.reg_mask = 0x0f;
	mem = m;
	irq_status = 0;
	irq_mask = 0xff;
	ticks_to_50hz = 0;
	SetKeyEvent (Electron_KeyEvent, this);
}

void ElectronULA::KeyEvent (int row, int col)
{
	printf ("KeyEv\n");
}

ElectronULA::~ElectronULA (void)
{
	SetKeyEvent (NULL, NULL);
}

byte ElectronULA::MMIO_Read (int addr)
{
	switch (addr)
	{
		case 0:	// IRQ status
		return irq_status;
	}
	return 0xfe;
}

void ElectronULA::MMIO_Write (int addr, byte value)
{
	switch (addr)
	{
		case 0:
		irq_mask = value;
		break;
		
		case 5:
		if ((value & 0x0e) != 8)
		{
			if ((value & 0x0c) == 0x0c)
			{
				mem->Switch_SWbank (value & 0x0f);
				dir_sw = 1;
			}
			else if (dir_sw)
			{
				dir_sw = 0;
				mem->Switch_SWbank (value & 0x0f);
			}
			mem->kb_paged = 0;
		}
		else if (dir_sw)
		{
			mem->kb_paged = 1;
			dir_sw = 0;
		}
		// IRQ clearing
		ClearIRQ (((value & 0x10) ? IRQ_DISPLAY : 0) |
			((value & 0x20) ? IRQ_RTC : 0) |
			((value & 0x40) ? IRQ_HIGHTONE : 0));
		if (value & 0x80)
			;	// FIXME clear NMI - Do we need to emulate this???
		break;
	}
}

void ElectronULA::Break (void)
{
	dir_sw = 1;
	irq_status = 0;
	irq_mask = 0;
}

void ElectronULA::Reset (void)
{
	dir_sw = 1;
	irq_status = 0;
	irq_mask = 0;
	SetInt (IRQ_POWERON);
}

void ElectronULA::SetInt (int num)
{
	irq_status |= num;
	irq_status &= ~IRQ_MASTER;
	if (irq_status & irq_mask)
	{
		SignalIRQ (IRQ_SYSVIA);
		irq_status |= IRQ_MASTER;
	}
	else
		ClearIRQ (IRQ_SYSVIA);
}

void ElectronULA::ClearInt (int num)
{
	irq_status &= ~(num | IRQ_MASTER);
	if (irq_status & irq_mask)
	{
		SignalIRQ (IRQ_SYSVIA);
		irq_status |= IRQ_MASTER;
	}
	else
		ClearIRQ (IRQ_SYSVIA);
}

void ElectronULA::DoTicks (int ticks)
{
	ticks_to_50hz += ticks;
	if (ticks_to_50hz >= 20000)
	{
		SetInt (IRQ_RTC);
		ticks_to_50hz -= 20000;
	}
}

DLC_Register (ElectronEmul);

/* End of file. */
