#pragma once

#include <cstdint>

#include "Signal.h"
#include "Register.h"

namespace EightBit {
	class Bus {
	public:
		Bus()
		: m_temporary(0xff), m_data(nullptr) {
			m_address.word = 0xffff;
		}

		Signal<uint16_t> WritingByte;
		Signal<uint16_t> WrittenByte;

		Signal<uint16_t> ReadingByte;
		Signal<uint16_t> ReadByte;

		register16_t& ADDRESS() { return m_address; }
		uint8_t& DATA() { return *m_data; }

		uint8_t& placeDATA(uint8_t value) {
			m_temporary = value;
			m_data = &m_temporary;
			return DATA();
		}

		uint8_t& referenceDATA(uint8_t& value) {
			m_data = &value;
			return DATA();
		}

		uint8_t peek(uint16_t address) {
			bool rom;
			return reference(address, rom);
		}

		void poke(uint16_t address, uint8_t value) {
			bool rom;
			reference(address, rom) = value;
		}

		uint16_t peekWord(uint16_t address) {
			register16_t returned;
			returned.low = peek(address);
			returned.high = peek(address + 1);
			return returned.word;
		}

		uint8_t read() {
			ReadingByte.fire(ADDRESS().word);
			auto content = reference();
			ReadByte.fire(ADDRESS().word);
			return content;
		}

		uint8_t read(uint16_t offset) {
			ADDRESS().word = offset;
			return read();
		}

		uint8_t read(register16_t address) {
			ADDRESS() = address;
			return read();
		}

		void write(uint8_t value) {
			WritingByte.fire(ADDRESS().word);
			reference() = value;
			WrittenByte.fire(ADDRESS().word);
		}

		void write(uint16_t offset, uint8_t value) {
			ADDRESS().word = offset;
			write(value);
		}

		void write(register16_t address, uint8_t value) {
			ADDRESS() = address;
			write(value);
		}

	protected:
		virtual uint8_t& reference(uint16_t address, bool& rom) = 0;

		uint8_t& reference() {
			bool rom;
			auto& value = reference(ADDRESS().word, rom);
			return rom ? placeDATA(value) : referenceDATA(value);
		}

	private:
		uint8_t m_temporary;	// Used to simulate ROM
		register16_t m_address;
		uint8_t* m_data;
	};
}