using System;

using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.M6502;

namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
	// an extension of the 6502 processor
	public sealed partial class Chip6510
	{
		// ------------------------------------
		private readonly MOS6502X<CpuLink> _cpu;
		private LatchedPort _port;
		private int _irqDelay;
		private int _nmiDelay;

		private struct CpuLink : IMOS6502XLink
		{
			private readonly Chip6510 _chip;

			public CpuLink(Chip6510 chip)
			{
				_chip = chip;
			}

			public byte DummyReadMemory(ushort address) => unchecked((byte)_chip.Read(address));

			public void OnExecFetch(ushort address) { }

			public byte PeekMemory(ushort address) => unchecked((byte)_chip.Peek(address));

			public byte ReadMemory(ushort address) => unchecked((byte)_chip.Read(address));

			public void WriteMemory(ushort address, byte value) => _chip.Write(address, value);
		}

		public Func<int, int> PeekMemory;
		public Action<int, int> PokeMemory;
		public Func<bool> ReadAec;
		public Func<bool> ReadIrq;
		public Func<bool> ReadNmi;
		public Func<bool> ReadRdy;
		public Func<int> ReadBus;
		public Func<int, int> ReadMemory;
		public Func<int> ReadPort;
		public Action<int, int> WriteMemory;
		public Action<int> WriteMemoryPort;

		public Action DebuggerStep;

		public Chip6510()
		{
			// configure cpu r/w
			_cpu = new MOS6502X<CpuLink>(new CpuLink(this));

			// perform hard reset
			HardReset();
		}

		public string TraceHeader => "6510: PC, machine code, mnemonic, operands, registers (A, X, Y, P, SP), flags (NVTBDIZCR)";

		public Action<TraceInfo> TraceCallback
		{
			get => _cpu.TraceCallback;
			set => _cpu.TraceCallback = value;
		}

		public void HardReset()
		{
			_cpu.NESSoftReset();
			_port = new LatchedPort
			{
				Direction = 0x00,
				Latch = 0xFF
			};
		}

		public void SoftReset()
		{
			_cpu.NESSoftReset();
			_port.Direction = 0x00;
			_port.Latch = 0xFF;
		}

		public void ExecutePhase()
		{
			_irqDelay >>= 1;
			_nmiDelay >>= 1;
			_irqDelay |= ReadIrq() ? 0x0 : 0x2;
			_nmiDelay |= ReadNmi() ? 0x0 : 0x2;
			_cpu.RDY = ReadRdy();
			_cpu.IRQ = (_irqDelay & 1) != 0;
			_cpu.NMI |= (_nmiDelay & 3) == 2;
			_cpu.ExecuteOne();
		}

		public int Peek(int addr)
		{
			switch (addr)
			{
				case 0x0000:
					return _port.Direction;
				case 0x0001:
					return PortData;
				default:
					return PeekMemory(addr);
			}
		}

		public void Poke(int addr, int val)
		{
			switch (addr)
			{
				case 0x0000:
					_port.Direction = val;
					break;
				case 0x0001:
					_port.Latch = val;
					break;
				default:
					PokeMemory(addr, val);
					break;
			}
		}

		public int PortData => _port.ReadInput(ReadPort());

		public int Read(int addr)
		{
			switch (addr)
			{
				case 0x0000:
					return _port.Direction;
				case 0x0001:
					return PortData;
				default:
					if (ReadAec())
						return ReadMemory(addr);
					else
						return ReadBus();
			}
		}

		public void SyncState(Serializer ser)
		{
			ser.BeginSection("Chip6510Cpu");
			_cpu.SyncState(ser);
			ser.EndSection();

			ser.BeginSection(nameof(_port));
			_port.SyncState(ser);
			ser.EndSection();
			
			ser.Sync(nameof(_irqDelay), ref _irqDelay);
			ser.Sync(nameof(_nmiDelay), ref _nmiDelay);
		}

		public void Write(int addr, int val)
		{
			switch (addr)
			{
				case 0x0000:
					_port.Direction = val;
					WriteMemoryPort(addr);
					break;
				case 0x0001:
					_port.Latch = val;
					WriteMemoryPort(addr);
					break;
				default:
					if (ReadAec())
						WriteMemory(addr, val);
					break;
			}
		}
	}
}
