
#ifndef cpu_h
#define cpu_h

class Cpu : public Core_z80, Mmio {
	_System* _sys;
	Vdp& vdp;
	Bus& bus;
	SN76489& sn76489;
	Inputmanager& inputmanager;

	u16 vcounter;
	u16 hcounterMCL;

	u8 port_3f;
	u8 port_dc;
	u8 port_dd;

	u8 read_port_3F();
	u8 read_port_DC();
	u8 read_port_DD();

	void write_port_3F(u8 value);
	void write_port_DC(u8 value);
	void write_port_DD(u8 value);

	u8 read_port_7E();
	u8 read_port_7F();
	void write_port_7E(u8 value);
	void write_port_7F(u8 value);
	u8 hcountBuffer;
	void latchHcount();

	u8 doSetRes(bool set, u8 pos, u8 val) {
		return set ? doSet(pos, val) : doRes(pos, val);
	}

	bool checkIrq();
	bool checkNmi();

	class Interrupt {
	protected:
		Cpu* cpu;
		Vdp* vdp;
		_System* sys;

		bool pin_low;
		bool enabled;
		bool _register;

		struct {
			bool _register[16]; //a cpu doesn't sample registered interrupts on opcode edge. The Z80 samples it last t-state before opcode end. the cache is needed to check if sampling was happening one t-state before
			u8 counter;
		} cache;

		virtual void poll() = 0;

		void init() {
			pin_low = _register = 0;
			enabled = false;
			cache.counter = 0;
			clearCache();
		}
	public:
		void clearCache() {
			for (int i = 0; i < 16; i++) {
				cache._register[i] = false;
			}
		}
		void fillCache() {
			for (int i = 0; i < 16; i++) {
				cache._register[i] = true;
			}
		}
		void setCache() {
			cache.counter++;
			cache.counter &= 15;
			cache._register[cache.counter] = _register;
		}

		bool getCache(u8 clocks) {
			return cache._register[(cache.counter - clocks) & 15];
		}

		void resetPin() {
			pin_low = false;
		}

		bool isRegister() {
			return _register;
		}

		void resetRegister() {
			_register = false;
		}

		void activate(bool state) {
			bool old = enabled;
			enabled = state;

			if (pin_low ) {
				if (enabled) {
					_register = true;
				} else {
					_register = false;
				}
			}
		}

		Interrupt(Cpu* cpu, Vdp* vdp, _System* sys) : cpu(cpu), vdp(vdp), sys(sys)  { init(); }
	};

	class Nmi : public Interrupt {
	public:
		void poll();
		Inputmanager* inputmanager;
		Nmi(Cpu* cpu, Vdp* vdp, _System* sys, Inputmanager* inputmanager) : Interrupt(cpu, vdp, sys), inputmanager(inputmanager) { }
		void init() {
			Interrupt::init();
		}
	};

	class FrameIrq : public Interrupt {
	public:
		void poll();
		FrameIrq(Cpu* cpu, Vdp* vdp, _System* sys) : Interrupt(cpu, vdp, sys) { }
		void init() {
			Interrupt::init();
		}
	};

	class LineIrq : public Interrupt {

	private:
		u8 counter;
	public:

		void poll();
		void init() {
			counter = 0xFF;
			Interrupt::init();
		}
		LineIrq(Cpu* cpu, Vdp* vdp, _System* sys) : Interrupt(cpu, vdp, sys) { init(); }
	};

public:
	FrameIrq frameirq;
	LineIrq lineirq;
	Nmi nmi;
	Cpu(_System* _sys, Bus& bus, Vdp& vdp, SN76489& sn76489, Inputmanager& inputmanager);
	u8 memRead(u16 addr);
	void memWrite(u16 addr, u8 value);
	u8 ioRead(u8 addr);
	void ioWrite(u8 addr, u8 data);
	void enter();
	void power();
	void reset();
	void sync(u8 cycles);
	void debugLook() {}
	void butPressed() { port_dc = 0xEF; }
	void butNotPressed() { port_dc = 0xFF; }
	u8 openBus();

	u8 mmio_read(u8 pos);
	void mmio_write(u8 pos, u8 value);
};

#endif
