#include "../interface.h"

Cpu::Cpu(_System* _sys, Bus& bus, Vdp& vdp, SN76489& sn76489, Inputmanager& inputmanager)
:
_sys(_sys),
bus(bus),
vdp(vdp),
sn76489(sn76489),
inputmanager(inputmanager),
Core_z80(),
frameirq(this, &vdp, _sys),
lineirq(this, &vdp, _sys),
nmi(this, &vdp, _sys, &inputmanager)
{
	bus.mmio.map(1, this);
	bus.mmio.map(2, this);
	bus.mmio.map(3, this);
	bus.mmio.map(6, this);
	bus.mmio.map(7, this);
}

u8 Cpu::openBus() {
	if (_sys->_Unit() == _System::Unit::SMS1) {
		return Core_z80::openBus();
	} if (_sys->_Unit() == _System::Unit::SMS2) {
		return 0xff;
	}
	return Core_z80::openBus();
}

u8 Cpu::memRead(u16 addr) {
	u8 data = bus.read_from(addr & 0xFFFF);
	return data;
}

void Cpu::memWrite(u16 addr, u8 value) {
	bus.write_to(addr & 0xFFFF, value);
}

u8 Cpu::ioRead(u8 addr) {
	return bus.mmio.read(addr);
}

void Cpu::ioWrite(u8 addr, u8 value) {
	bus.mmio.write(addr, value);
}

u8 Cpu::mmio_read(u8 pos) {
	switch(pos) {
		case 1: return read_port_3F();
		case 2: return read_port_7E();
		case 3: return read_port_7F();
		case 6: return read_port_DC();
		case 7: return read_port_DD();
	}
}

void Cpu::mmio_write(u8 pos, u8 value) {
	switch(pos) {
		case 1: write_port_3F(value); break;
		case 2: write_port_7E(value); break;
		case 3: write_port_7F(value); break;
		case 6: write_port_DC(value); break;
		case 7: write_port_DD(value); break;
	}
}

u8 Cpu::read_port_3F() {
	return openBus();
}

u8 Cpu::read_port_DC() {
	if (bus.isIoDisabled()) {
		return openBus();
	}
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::Up) & 1 ), 0, port_dc);
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::Down) & 1 ), 1, port_dc);
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::Left) & 1 ), 2, port_dc);
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::Right) & 1 ), 3, port_dc);
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::A) & 1 ), 4, port_dc);
	if ( (port_3f & 1) != 0) {
		port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::B) & 1 ), 5, port_dc);
	}
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::Up) & 1 ), 6, port_dc);
	port_dc = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::Down) & 1 ), 7, port_dc);
	return port_dc;
}

u8 Cpu::read_port_DD() {
	if (bus.isIoDisabled()) {
		return openBus();
	}
	port_dd = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::Left) & 1 ), 0, port_dd);
	port_dd = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::Right) & 1 ), 1, port_dd);
	port_dd = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::A) & 1 ), 2, port_dd);
	if ( (port_3f & 4) != 0) {
		port_dd = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::B) & 1 ), 3, port_dd);
	}
	//reset pin is written via reset()
	if ( (port_3f & 2) != 0) {
		port_dd = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port1, Inputmanager::IObjectID::TH) & 1 ), 6, port_dd);
	}
	if ( (port_3f & 8) != 0) {
		port_dd = doSetRes( ( inputmanager.readPort(Inputmanager::Device::Port::Port2, Inputmanager::IObjectID::TH) & 1 ), 7, port_dd);
	}
	return port_dd;
}

void Cpu::write_port_3F(u8 value) {
	if ( ( (value & 2) > (port_3f & 2) )
	|| ( (value & 8) > (port_3f & 8) ) ) {
		latchHcount();
	}

	bool port1th = (value & 0x20) != 0;
	port1th = _sys->_region() == _System::Region::JAPAN ? !port1th : port1th;
	bool port2th = (value & 0x80) != 0;
	port2th = _sys->_region() == _System::Region::JAPAN ? !port2th : port2th;
	bool port1tr = (value & 0x10) != 0;
	bool port2tr = (value & 0x40) != 0;

	if( (value & 8) == 0) port_dd = doSetRes(port2th, 7, port_dd);
	if( (value & 4) == 0) port_dd = doSetRes(port2tr, 3, port_dd);
	if( (value & 2) == 0) port_dd = doSetRes(port1th, 6, port_dd);
	if( (value & 1) == 0) port_dc = doSetRes(port1tr, 5, port_dc);

	port_3f = value;
}

void Cpu::write_port_DC(u8 value) {
	//no writes c0 - ff
}

void Cpu::write_port_DD(u8 value) {
	//no writes c0 - ff
}

//write c0 - ff individually for additional perepherie like yamaha, set 0x3e bit 2 

u8 Cpu::read_port_7E() {
	return vdp.getInternalVCounter( vcounter );
}

void Cpu::latchHcount() {
	// F5h ---- FFh | 00h --------- 20h --LCD-- 6FH -------- 93H | E9H ---- F4H

	hcountBuffer = (hcounterMCL + (hcounterMCL > 0 ? -1 : 683) ) >> 2;
	if (hcountBuffer > 0x93) {
		hcountBuffer += 0x55;
	}
}

u8 Cpu::read_port_7F() {
	return hcountBuffer;
}

void Cpu::write_port_7E(u8 value) {
	_sys->sync_cpuapu();
	sn76489.write( value );
}

void Cpu::write_port_7F(u8 value) {
	_sys->sync_cpuapu();
	sn76489.write( value );
}

void Cpu::enter() {
	while(true) {
		nextOpcode();
	}
}

void Cpu::power() {
	Core_z80::power();
	intr.imode = 1;
	vcounter = hcounterMCL = 0;
	port_dc = port_dd = 0xff;
	port_3f = 0xff;
	frameirq.init();
	lineirq.init();
	nmi.init();
	hcountBuffer = 0;
}

void Cpu::reset() {
	if (_sys->_Unit() == _System::Unit::SMS1) {
		port_dd = doSetRes(false, 4, port_dd);
	}
}

bool Cpu::checkIrq() {
	bool frameIrqRegister = frameirq.getCache(3);
	bool lineIrqRegister = lineirq.getCache(3);
	
	return frameIrqRegister || lineIrqRegister;
}

bool Cpu::checkNmi() {
	if (nmi.isRegister()) {
		nmi.resetRegister();
		return true;
	}
	return false;
}

void Cpu::sync(u8 cycles) {
	if (cycles == 0) return;
	
	_sys->addclocks_cpu(cycles);
	unsigned MCL = cycles * 3;
	
	while (MCL--) {
		hcounterMCL++;
		if (hcounterMCL == 636) {
			_sys->sync_cpuvdp();
			if (++vcounter >= (_sys->_isNTSC() ? 262 : 313)  ) { //frame complete
				port_dd = doSetRes(true, 4, port_dd); //release reset button, for SMS2 it is released already
				_sys->exit();
				vcounter = 0;
			}
			if (vcounter == (_sys->_isNTSC() ? 261 : 312)  ) nmi.poll();
		}

		if (hcounterMCL == 684) {
			_sys->sync_cpuapu();
			hcounterMCL = 0;
		}
		frameirq.poll();
		lineirq.poll();
	}
}

void Cpu::Nmi::poll() {
	if ( inputmanager->isHiToLoTransition(Inputmanager::DeviceID::IdPause, Inputmanager::IObjectID::Pause) ) {
		_register = true;
	}
}

void Cpu::FrameIrq::poll() {
	u8 height = vdp->getScreenHeight();

	if (	(height == 192 && cpu->vcounter == 192)
		||	(height == 224 && cpu->vcounter == 224)
		||	(height == 240 && cpu->vcounter == 240)) {
		
		if (cpu->hcounterMCL == 634) {
			pin_low = true;
			sys->sync_cpuvdp();
			vdp->setIrqState();
			if (enabled ) {
				_register = true;
			}
		}
	}

	if (pin_low) {
		sys->sync_cpuvdp();
	}
	setCache();
}

void Cpu::LineIrq::poll() {
	u8 height = vdp->getScreenHeight();

	if (	(height == 192 && cpu->vcounter <= 192)
		||	(height == 224 && cpu->vcounter <= 224)
		||	(height == 240 && cpu->vcounter <= 240)) {
			
		if (cpu->hcounterMCL == 638) {

			if ( --counter == 0xFF ) {
				sys->sync_cpuvdp();
				counter = vdp->getLineCounter();
				pin_low = true;
				
				if (enabled) {
					_register = true;
				}
			}
		}
	} else {
		if (cpu->hcounterMCL == 638) {
			sys->sync_cpuvdp();
			counter = vdp->getLineCounter();
		}
	}
	if (pin_low) {
		sys->sync_cpuvdp();
	}

	setCache();
}
