#pragma once

// Auxiliary carry logic from https://github.com/begoon/i8080-core

#include <IntelProcessor.h>
#include <InputOutput.h>

namespace EightBit {
	class Intel8080 : public IntelProcessor {
	public:
		enum StatusBits {
			SF = Bit7,
			ZF = Bit6,
			AC = Bit4,
			PF = Bit2,
			CF = Bit0,
		};

		Intel8080(Bus& bus, InputOutput& ports);

		Signal<Intel8080> ExecutingInstruction;

		bool isInterruptable() const;
		int interrupt(uint8_t value);

		virtual int execute(uint8_t opcode);
		int step();

		virtual register16_t& AF() override {
			auto& f = af.low;
			f = (f | Bit1) & ~(Bit5 | Bit3);
			return af;
		}

		virtual register16_t& BC() override { return bc; }
		virtual register16_t& DE() override { return de; }
		virtual register16_t& HL() override { return hl; }

	private:
		bool m_interrupt;
		InputOutput& m_ports;

		register16_t af;
		register16_t bc;
		register16_t de;
		register16_t hl;

		uint8_t R(int r) {
			switch (r) {
			case 0b000:
				return B();
			case 0b001:
				return C();
			case 0b010:
				return D();
			case 0b011:
				return E();
			case 0b100:
				return H();
			case 0b101:
				return L();
			case 0b110:
				return getByte(HL());
			case 0b111:
				return A();
			default:
				UNREACHABLE;
			}
			throw std::logic_error("Unhandled registry mechanism");
		}

		void R(int r, uint8_t value) {
			switch (r) {
			case 0b000:
				B() = value;
				break;
			case 0b001:
				C() = value;
				break;
			case 0b010:
				D() = value;
				break;
			case 0b011:
				E() = value;
				break;
			case 0b100:
				H() = value;
				break;
			case 0b101:
				L() = value;
				break;
			case 0b110:
				setByte(HL(), value);
				break;
			case 0b111:
				A() = value;
				break;
			default:
				UNREACHABLE;
			}
		}

		register16_t& RP(int rp) {
			switch (rp) {
			case 0b00:
				return BC();
			case 0b01:
				return DE();
			case 0b10:
				return HL();
			case 0b11:
				return SP();
			default:
				UNREACHABLE;
			}
		}

		register16_t& RP2(int rp) {
			switch (rp) {
			case 0b00:
				return BC();
			case 0b01:
				return DE();
			case 0b10:
				return HL();
			case 0b11:
				return AF();
			default:
				UNREACHABLE;
			}
		}

		static void adjustAuxiliaryCarryAdd(uint8_t& f, uint8_t before, uint8_t value, int calculation) {
			setFlag(f, AC, calculateHalfCarryAdd(before, value, calculation));
		}

		static void adjustAuxiliaryCarrySub(uint8_t& f, uint8_t before, uint8_t value, int calculation) {
			clearFlag(f, AC, calculateHalfCarrySub(before, value, calculation));
		}

		static void subtract(uint8_t& f, uint8_t& operand, uint8_t value, int carry = 0);

		void execute(int x, int y, int z, int p, int q);

		static void increment(uint8_t& f, uint8_t& operand);
		static void decrement(uint8_t& f, uint8_t& operand);

		bool returnConditionalFlag(uint8_t& f, int flag);
		bool jumpConditionalFlag(uint8_t& f, int flag);
		bool callConditionalFlag(uint8_t& f, int flag);

		static void add(uint8_t& f, register16_t& operand, register16_t value);

		static void add(uint8_t& f, uint8_t& operand, uint8_t value, int carry = 0);
		static void adc(uint8_t& f, uint8_t& operand, uint8_t value);
		static void sbb(uint8_t& f, uint8_t& operand, uint8_t value);
		static void andr(uint8_t& f, uint8_t& operand, uint8_t value);
		static void xorr(uint8_t& f, uint8_t& operand, uint8_t value);
		static void orr(uint8_t& f, uint8_t& operand, uint8_t value);
		static void compare(uint8_t& f, uint8_t check, uint8_t value);

		static void rlc(uint8_t& f, uint8_t& operand);
		static void rrc(uint8_t& f, uint8_t& operand);
		static void rl(uint8_t& f, uint8_t& operand);
		static void rr(uint8_t& f, uint8_t& operand);

		static void daa(uint8_t& a, uint8_t& f);

		static void cma(uint8_t& a, uint8_t& f);
		static void stc(uint8_t& a, uint8_t& f);
		static void cmc(uint8_t& a, uint8_t& f);

		void xhtl();

		void out();
		void in();

		void ei();
		void di();
	};
}