using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Mesen.GUI.Forms;
using Mesen.GUI.Controls;

namespace Mesen.GUI.Debugger
{
	public partial class ctrlConsoleStatus : BaseControl
	{
		public event EventHandler OnStateChanged;
		public event EventHandler OnGotoLocation;

		private bool _dirty = false;
		private bool _preventDirty = false;
		private DebugState _lastState;
		private EntityBinder _cpuBinder = new EntityBinder();
		private EntityBinder _ppuControlBinder = new EntityBinder();
		private EntityBinder _ppuStatusBinder = new EntityBinder();

		public ctrlConsoleStatus()
		{
			InitializeComponent();

			bool designMode = (LicenseManager.UsageMode == LicenseUsageMode.Designtime);
			if(!designMode) {
				tmrButton.Enabled = true;
			}

			_cpuBinder.Entity = new CPUState();
			_ppuControlBinder.Entity = new PPUControlFlags();
			_ppuStatusBinder.Entity = new PPUStatusFlags();

			_cpuBinder.AddBinding("A", txtA);
			_cpuBinder.AddBinding("X", txtX);
			_cpuBinder.AddBinding("Y", txtY);
			_cpuBinder.AddBinding("PC", txtPC);
			_cpuBinder.AddBinding("SP", txtSP);
			_cpuBinder.AddBinding("PS", txtStatus);
			_cpuBinder.AddBinding("CycleCount", txtCycleCount);
			_cpuBinder.AddBinding("NMIFlag", chkNMI);
			
			_ppuStatusBinder.AddBinding("VerticalBlank", chkVerticalBlank);
			_ppuStatusBinder.AddBinding("Sprite0Hit", chkSprite0Hit);
			_ppuStatusBinder.AddBinding("SpriteOverflow", chkSpriteOverflow);

			_ppuControlBinder.AddBinding("BackgroundEnabled", chkBGEnabled);
			_ppuControlBinder.AddBinding("SpritesEnabled", chkSpritesEnabled);
			_ppuControlBinder.AddBinding("BackgroundMask", chkDrawLeftBG);
			_ppuControlBinder.AddBinding("SpriteMask", chkDrawLeftSpr);
			_ppuControlBinder.AddBinding("VerticalWrite", chkVerticalWrite);
			_ppuControlBinder.AddBinding("VBlank", chkNMIOnBlank);
			_ppuControlBinder.AddBinding("LargeSprites", chkLargeSprites);
			_ppuControlBinder.AddBinding("Grayscale", chkGrayscale);
			_ppuControlBinder.AddBinding("IntensifyRed", chkIntensifyRed);
			_ppuControlBinder.AddBinding("IntensifyGreen", chkIntensifyGreen);
			_ppuControlBinder.AddBinding("IntensifyBlue", chkIntensifyBlue);
			_ppuControlBinder.AddBinding("BackgroundEnabled", chkBGEnabled);
			_ppuControlBinder.AddBinding("BackgroundEnabled", chkBGEnabled);

			_ppuControlBinder.AddBinding("BackgroundPatternAddr", txtBGAddr);
			_ppuControlBinder.AddBinding("SpritePatternAddr", txtSprAddr);
		}

		private void UpdateCPUStatus(ref CPUState state)
		{
			_cpuBinder.Entity = state;
			_cpuBinder.UpdateUI();

			UpdateCpuFlags();

			chkExternal.Checked = state.IRQFlag.HasFlag(IRQSource.External);
			chkFrameCounter.Checked = state.IRQFlag.HasFlag(IRQSource.FrameCounter);
			chkDMC.Checked = state.IRQFlag.HasFlag(IRQSource.DMC);
		}

		private void UpdatePPUStatus(ref PPUDebugState state)
		{
			_ppuControlBinder.Entity = state.ControlFlags;
			_ppuStatusBinder.Entity = state.StatusFlags;
			_ppuControlBinder.UpdateUI();
			_ppuStatusBinder.UpdateUI();

			txtCycle.Text = state.Cycle.ToString();
			txtScanline.Text = state.Scanline.ToString();

			txtVRAMAddr.Text = state.State.VideoRamAddr.ToString("X4");
			txtNTAddr.Text = (0x2000 | (state.State.VideoRamAddr & 0x0FFF)).ToString("X4");
		}

		private void UpdateStack(UInt16 stackPointer)
		{
			lstStack.Items.Clear();
			for(UInt32 i = (UInt32)0x100 + stackPointer; i <  0x200; i++) {
				lstStack.Items.Add("$" + InteropEmu.DebugGetMemoryValue(i).ToString("X2"));
			}
		}

		public void UpdateStatus(ref DebugState state)
		{
			this._preventDirty = true;
			_lastState = state;
			UpdateCPUStatus(ref state.CPU);
			UpdatePPUStatus(ref state.PPU);
			UpdateStack(state.CPU.SP);

			btnUndo.Enabled = false;
			this._dirty = false;
			this._preventDirty = false;
		}

		public void ApplyChanges()
		{
			if(this._dirty) {
				_cpuBinder.UpdateObject();
				_ppuControlBinder.UpdateObject();
				_ppuStatusBinder.UpdateObject();

				DebugState state = _lastState;
				state.CPU = (CPUState)_cpuBinder.Entity;

				if(chkExternal.Checked) state.CPU.IRQFlag |= IRQSource.External;
				if(chkFrameCounter.Checked) state.CPU.IRQFlag |= IRQSource.FrameCounter;
				if(chkDMC.Checked) state.CPU.IRQFlag |= IRQSource.DMC;

				state.PPU.ControlFlags = (PPUControlFlags)_ppuControlBinder.Entity;
				state.PPU.StatusFlags = (PPUStatusFlags)_ppuStatusBinder.Entity;

				state.PPU.State.Mask = state.PPU.ControlFlags.GetMask();
				state.PPU.State.Control = state.PPU.ControlFlags.GetControl();
				state.PPU.State.Status = state.PPU.StatusFlags.GetStatus();

				UInt32 cycle = 0;
				UInt32.TryParse(txtCycle.Text, out cycle);
				state.PPU.Cycle = cycle;

				Int32 scanline = 0;
				Int32.TryParse(txtScanline.Text, out scanline);
				state.PPU.Scanline = scanline;

				UInt16 vramAddr = 0;
				UInt16.TryParse(txtVRAMAddr.Text, System.Globalization.NumberStyles.HexNumber, null, out vramAddr);
				state.PPU.State.VideoRamAddr = vramAddr;

				InteropEmu.DebugSetState(state);
				btnUndo.Enabled = false;
				OnStateChanged?.Invoke(null, null);
			}
		}

		private void chkCpuFlag_Click(object sender, EventArgs e)
		{
			this.OnOptionChanged(sender, e);

			int ps = 0;
			if(chkBreak.Checked) ps |= (int)PSFlags.Break;
			if(chkCarry.Checked) ps |= (int)PSFlags.Carry;
			if(chkDecimal.Checked) ps |= (int)PSFlags.Decimal;
			if(chkInterrupt.Checked) ps |= (int)PSFlags.Interrupt;
			if(chkNegative.Checked) ps |= (int)PSFlags.Negative;
			if(chkOverflow.Checked) ps |= (int)PSFlags.Overflow;
			if(chkReserved.Checked) ps |= (int)PSFlags.Reserved;
			if(chkZero.Checked) ps |= (int)PSFlags.Zero;
			txtStatus.Text = ps.ToString("X2");
		}

		private void UpdateCpuFlags()
		{
			PSFlags flags = (PSFlags)((CPUState)_cpuBinder.Entity).PS;
			chkBreak.Checked = flags.HasFlag(PSFlags.Break);
			chkCarry.Checked = flags.HasFlag(PSFlags.Carry);
			chkDecimal.Checked = flags.HasFlag(PSFlags.Decimal);
			chkInterrupt.Checked = flags.HasFlag(PSFlags.Interrupt);
			chkNegative.Checked = flags.HasFlag(PSFlags.Negative);
			chkOverflow.Checked = flags.HasFlag(PSFlags.Overflow);
			chkReserved.Checked = flags.HasFlag(PSFlags.Reserved);
			chkZero.Checked = flags.HasFlag(PSFlags.Zero);
		}

		private void txtStatus_TextChanged(object sender, EventArgs e)
		{
			this.OnOptionChanged(sender, e);
			if(!_cpuBinder.Updating) {
				_cpuBinder.UpdateObject();
				UpdateCpuFlags();
			}
		}

		private void tmrButton_Tick(object sender, EventArgs e)
		{
			btnUndo.Enabled = this._dirty && InteropEmu.DebugIsExecutionStopped();
		}

		private void UpdateVectorAddresses()
		{
			int nmiHandler = InteropEmu.DebugGetMemoryValue(0xFFFA) | (InteropEmu.DebugGetMemoryValue(0xFFFB) << 8);
			int resetHandler = InteropEmu.DebugGetMemoryValue(0xFFFC) | (InteropEmu.DebugGetMemoryValue(0xFFFD) << 8);
			int irqHandler = InteropEmu.DebugGetMemoryValue(0xFFFE) | (InteropEmu.DebugGetMemoryValue(0xFFFF) << 8);

			mnuGoToNmiHandler.Text = "NMI Handler ($" + nmiHandler.ToString("X4") + ")";
			mnuGoToResetHandler.Text = "Reset Handler ($" + resetHandler.ToString("X4") + ")";
			mnuGoToIrqHandler.Text = "IRQ Handler ($" + irqHandler.ToString("X4") + ")";
		}

		private void btnGoto_Click(object sender, EventArgs e)
		{
			contextGoTo.Show(btnGoto.PointToScreen(new Point(0, btnGoto.Height-1)));
		}

		private void mnuGoToIrqHandler_Click(object sender, EventArgs e)
		{
			int address = (InteropEmu.DebugGetMemoryValue(0xFFFF) << 8) | InteropEmu.DebugGetMemoryValue(0xFFFE);
			this.OnGotoLocation?.Invoke(address, null);
		}

		private void mnuGoToNmiHandler_Click(object sender, EventArgs e)
		{
			int address = (InteropEmu.DebugGetMemoryValue(0xFFFB) << 8) | InteropEmu.DebugGetMemoryValue(0xFFFA);
			this.OnGotoLocation?.Invoke(address, null);
		}

		private void mnuGoToResetHandler_Click(object sender, EventArgs e)
		{
			int address = (InteropEmu.DebugGetMemoryValue(0xFFFD) << 8) | InteropEmu.DebugGetMemoryValue(0xFFFC);
			this.OnGotoLocation?.Invoke(address, null);
		}

		private void contextGoTo_Opening(object sender, CancelEventArgs e)
		{
			UpdateVectorAddresses();
		}

		private void OnOptionChanged(object sender, EventArgs e)
		{
			if(InteropEmu.DebugIsExecutionStopped()) {
				if(!this._preventDirty) {
					this._dirty = true;
					this.btnUndo.Enabled = true;
				}
			} else {
				this.UpdateStatus(ref _lastState);
			}
		}

		private void btnUndo_Click(object sender, EventArgs e)
		{
			if(this._dirty) {
				this.UpdateStatus(ref _lastState);
				this.btnUndo.Enabled = false;
			}
		}
	}
}