using System;
using System.Collections.Generic;
using System.Linq;

using BizHawk.Emulation.Common;

namespace BizHawk.Emulation.Cores.Computers.AppleII
{
	public partial class AppleII : IDebuggable
	{
		public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
		{
			var regs = _machine.GetCpuFlagsAndRegisters();

			var dic = new Dictionary<string, RegisterValue>();

			foreach (var reg in regs)
			{
				dic.Add(
					reg.Key,
					reg.Key.Contains("Flag")
						? reg.Value > 0
						: getRegisterValue(reg));
			}

			return dic;
		}

		private RegisterValue getRegisterValue(KeyValuePair<string, int> reg)
		{
			switch (reg.Key)
			{
				case "A":
				case "X":
				case "Y":
				case "S":
					return (byte)reg.Value;
				case "PC":
					return (ushort)reg.Value;
				default:
					return reg.Value;
			}
		}

		public void SetCpuRegister(string register, int value)
		{
			switch (register)
			{
				default:
					throw new InvalidOperationException();
				case "A":
					_machine.Cpu.RA = (byte)value;
					break;
				case "X":
					_machine.Cpu.RX = (byte)value;
					break;
				case "Y":
					_machine.Cpu.RY = (byte)value;
					break;
				case "S":
					_machine.Cpu.RS = (byte)value;
					break;
				case "PC":
					_machine.Cpu.RPC = (ushort)value;
					break;
				case "Flag C":
					_machine.Cpu.FlagC = value > 0;
					break;
				case "Flag Z":
					_machine.Cpu.FlagZ = value > 0;
					break;
				case "Flag I":
					_machine.Cpu.FlagI = value > 0;
					break;
				case "Flag D":
					_machine.Cpu.FlagD = value > 0;
					break;
				case "Flag B":
					_machine.Cpu.FlagB = value > 0;
					break;
				case "Flag T":
					_machine.Cpu.FlagT = value > 0;
					break;
				case "Flag V":
					_machine.Cpu.FlagV = value > 0;
					break;
				case "Flag N":
					_machine.Cpu.FlagV = value > 0;
					break;
			}
		}

		public bool CanStep(StepType type)
		{
			switch (type)
			{
				case StepType.Into:
				case StepType.Over:
				case StepType.Out:
					return true;
				default:
					return false;
			}
		}


		public void Step(StepType type) 
		{
			switch (type)
			{
				case StepType.Into:
					StepInto();
					break;
				case StepType.Out:
					StepOut();
					break;
				case StepType.Over:
					StepOver();
					break;
			}
		}

		private void StepInto()
		{
			if (Tracer.Enabled)
				_machine.Cpu.TraceCallback = (s) => Tracer.Put(s);
			else
				_machine.Cpu.TraceCallback = null;

			var machineInVblank = _machine.Video.IsVBlank;

			_machine.Events.HandleEvents(_machine.Cpu.Execute());

			if (!machineInVblank && _machine.Video.IsVBlank) // Check if a frame has passed while stepping
			{
				Frame++;
				if (_machine.Lagged)
				{
					LagCount++;
				}

				_machine.Lagged = true;
				_machine.DriveLight = false;
			}
		}

		private void StepOver()
		{
			var instruction = _machine.Memory.Read(_machine.Cpu.RPC);

			if (instruction == JSR)
			{
				var destination = _machine.Cpu.RPC + JSRSize;
				while (_machine.Cpu.RPC != destination)
				{
					StepInto();
				}
			}
			else
			{
				StepInto();
			}
		}

		private void StepOut()
		{
			var instr = _machine.Memory.Read(_machine.Cpu.RPC);

			JSRCount = instr == JSR ? 1 : 0;

			var bailOutFrame = Frame + 1;

			while (true)
			{
				StepInto();
				instr = _machine.Memory.Read(_machine.Cpu.RPC);
				if (instr == JSR)
				{
					JSRCount++;
				}
				else if (instr == RTS && JSRCount <= 0)
				{
					StepInto();
					JSRCount = 0;
					break;
				}
				else if (instr == RTS)
				{
					JSRCount--;
				}
				else //Emergency Bailout Logic
				{
					if (Frame == bailOutFrame)
					{
						break;
					}
				}
			}
		}

		private int JSRCount = 0;

		private const byte JSR = 0x20;
		private const byte RTS = 0x60;

		private const byte JSRSize = 3;

		public IMemoryCallbackSystem MemoryCallbacks { get; private set; }
	}
}
