using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Nintendo.NES;
using BizHawk.Emulation.Common;

namespace BizHawk.Client.EmuHawk
{
	public partial class NESNameTableViewer : Form, IToolFormAutoConfig
	{
		// TODO:
		// Show Scroll Lines + UI Toggle
		[RequiredService]
		private INESPPUViewable _ppu { get; set; }
		[RequiredService]
		private IEmulator _emu { get; set; }

		[ConfigPersist]
		private int RefreshRateConfig
		{
			get { return RefreshRate.Value; }
			set { RefreshRate.Value = value; }
		}

		int scanline;

		public NESNameTableViewer()
		{
			InitializeComponent();
		}

		private void NESNameTableViewer_Load(object sender, EventArgs e)
		{
			Generate(true);
		}

		#region Public API

		public bool AskSaveChanges() { return true; }
		public bool UpdateBefore { get { return true; } }

		public void Restart()
		{
			Generate(true);
		}

		public void UpdateValues()
		{
			_ppu.InstallCallback1(() => Generate(), scanline);
		}

		public void FastUpdate()
		{
			// Do nothing
		}

		#endregion

		private unsafe void DrawTile(int* dst, int pitch, byte* pal, byte* tile, int* finalpal)
		{
			dst += 7;
			int vinc = pitch + 8;
			for (int j = 0; j < 8; j++)
			{
				int lo = tile[0];
				int hi = tile[8] << 1;
				for (int i = 0; i < 8; i++)
				{
					*dst-- = finalpal[pal[lo & 1 | hi & 2]];
					lo >>= 1;
					hi >>= 1;
				}
				dst += vinc;
				tile++;
			}
		}

		private unsafe void GenerateExAttr(int* dst, int pitch, byte[] palram, byte[] ppumem, byte[] exram)
		{
			byte[] chr = _ppu.GetExTiles();
			int chr_mask = chr.Length - 1;

			fixed (byte* chrptr = chr, palptr = palram, ppuptr = ppumem, exptr = exram)
			fixed (int* finalpal = _ppu.GetPalette())
			{
				DrawExNT(dst, pitch, palptr, ppuptr + 0x2000, exptr, chrptr, chr_mask, finalpal);
				DrawExNT(dst + 256, pitch, palptr, ppuptr + 0x2400, exptr, chrptr, chr_mask, finalpal);
				dst += pitch * 240;
				DrawExNT(dst, pitch, palptr, ppuptr + 0x2800, exptr, chrptr, chr_mask, finalpal);
				DrawExNT(dst + 256, pitch, palptr, ppuptr + 0x2c00, exptr, chrptr, chr_mask, finalpal);
			}
		}

		private unsafe void GenerateAttr(int* dst, int pitch, byte[] palram, byte[] ppumem)
		{
			fixed (byte* palptr = palram, ppuptr = ppumem)
			fixed (int* finalpal = _ppu.GetPalette())
			{
				byte* chrptr = ppuptr + (_ppu.BGBaseHigh ? 0x1000 : 0);
				DrawNT(dst, pitch, palptr, ppuptr + 0x2000, chrptr, finalpal);
				DrawNT(dst + 256, pitch, palptr, ppuptr + 0x2400, chrptr, finalpal);
				dst += pitch * 240;
				DrawNT(dst, pitch, palptr, ppuptr + 0x2800, chrptr, finalpal);
				DrawNT(dst + 256, pitch, palptr, ppuptr + 0x2c00, chrptr, finalpal);
			}
		}

		private unsafe void DrawNT(int* dst, int pitch, byte* palram, byte* nt, byte* chr, int* finalpal)
		{
			byte* at = nt + 0x3c0;

			for (int ty = 0; ty < 30; ty++)
			{
				for (int tx = 0; tx < 32; tx++)
				{
					byte t = *nt++;
					byte a = at[ty >> 2 << 3 | tx >> 2];
					a >>= tx & 2;
					a >>= (ty & 2) << 1;
					int palnum = a & 3;

					int tileaddr = t << 4;
					DrawTile(dst, pitch, palram + palnum * 4, chr + tileaddr, finalpal);
					dst += 8;
				}
				dst -= 256;
				dst += pitch * 8;
			}
		}

		private unsafe void DrawExNT(int* dst, int pitch, byte* palram, byte* nt, byte* exnt, byte* chr, int chr_mask, int* finalpal)
		{
			for (int ty = 0; ty < 30; ty++)
			{
				for (int tx = 0; tx < 32; tx++)
				{
					byte t = *nt++;
					byte ex = *exnt++;

					int tilenum = t | (ex & 0x3f) << 8;
					int palnum = ex >> 6;

					int tileaddr = tilenum << 4 & chr_mask;
					DrawTile(dst, pitch, palram + palnum * 4, chr + tileaddr, finalpal);
					dst += 8;
				}
				dst -= 256;
				dst += pitch * 8;
			}
		}

		private unsafe void Generate(bool now = false)
		{
			if (!IsHandleCreated || IsDisposed)
			{
				return;
			}

			if (now == false && _emu.Frame % RefreshRate.Value != 0)
			{
				return;
			}

			var bmpdata = NameTableView.Nametables.LockBits(
				new Rectangle(0, 0, 512, 480),
				ImageLockMode.WriteOnly,
				PixelFormat.Format32bppArgb);

			var dptr = (int*)bmpdata.Scan0.ToPointer();
			var pitch = bmpdata.Stride / 4;

			// Buffer all the data from the ppu, because it will be read multiple times and that is slow
			var ppuBuffer = _ppu.GetPPUBus();

			var palram = _ppu.GetPalRam();

			if (_ppu.ExActive)
			{
				byte[] exram = _ppu.GetExRam();
				GenerateExAttr(dptr, pitch, palram, ppuBuffer, exram);
			}
			else
			{
				GenerateAttr(dptr, pitch, palram, ppuBuffer);
			}

			NameTableView.Nametables.UnlockBits(bmpdata);
			NameTableView.Refresh();
		}

		private void RefreshFloatingWindowControl()
		{
		}

		#region Events

		#region Menu and Context Menu

		private void ScreenshotMenuItem_Click(object sender, EventArgs e)
		{
			NameTableView.Screenshot();
		}

		private void ScreenshotToClipboardMenuItem_Click(object sender, EventArgs e)
		{
			NameTableView.ScreenshotToClipboard();
		}

		private void ExitMenuItem_Click(object sender, EventArgs e)
		{
			Close();
		}

		private void RefreshImageContextMenuItem_Click(object sender, EventArgs e)
		{
			UpdateValues();
			NameTableView.Refresh();
		}

		#endregion

		#region Dialog and Controls

		private void NesNameTableViewer_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.KeyCode)
			{
				case Keys.C:
					if (e.Modifiers == Keys.Control)
					{
						NameTableView.ScreenshotToClipboard();
					}

					break;
			}
		}

		protected override void OnShown(EventArgs e)
		{
			RefreshFloatingWindowControl();
			base.OnShown(e);
		}

		private void NESNameTableViewer_FormClosed(object sender, FormClosedEventArgs e)
		{
			if (_ppu != null)
			{
				_ppu.RemoveCallback1();
			}
		}

		private void ScanlineTextbox_TextChanged(object sender, EventArgs e)
		{
			if (int.TryParse(txtScanline.Text, out scanline))
			{
				_ppu.InstallCallback1(() => Generate(), scanline);
			}
		}

		private void NametableRadio_CheckedChanged(object sender, EventArgs e)
		{
			if (rbNametableNW.Checked)
			{
				NameTableView.Which = NameTableViewer.WhichNametable.NT_2000;
			}

			if (rbNametableNE.Checked)
			{
				NameTableView.Which = NameTableViewer.WhichNametable.NT_2400;
			}

			if (rbNametableSW.Checked)
			{
				NameTableView.Which = NameTableViewer.WhichNametable.NT_2800;
			}

			if (rbNametableSE.Checked)
			{
				NameTableView.Which = NameTableViewer.WhichNametable.NT_2C00;
			}

			if (rbNametableAll.Checked)
			{
				NameTableView.Which = NameTableViewer.WhichNametable.NT_ALL;
			}
		}

		private void NameTableView_MouseMove(object sender, MouseEventArgs e)
		{
			int TileX, TileY, NameTable;
			if (NameTableView.Which == NameTableViewer.WhichNametable.NT_ALL)
			{
				TileX = e.X / 8;
				TileY = e.Y / 8;
				NameTable = (TileX / 32) + ((TileY / 30) * 2);
			}
			else
			{
				switch (NameTableView.Which)
				{
					default:
					case NameTableViewer.WhichNametable.NT_2000:
						NameTable = 0;
						break;
					case NameTableViewer.WhichNametable.NT_2400:
						NameTable = 1;
						break;
					case NameTableViewer.WhichNametable.NT_2800:
						NameTable = 2;
						break;
					case NameTableViewer.WhichNametable.NT_2C00:
						NameTable = 3;
						break;
				}

				TileX = e.X / 16;
				TileY = e.Y / 16;
			}

			XYLabel.Text = TileX + " : " + TileY;
			int PPUAddress = 0x2000 + (NameTable * 0x400) + ((TileY % 30) * 32) + (TileX % 32);
			PPUAddressLabel.Text = string.Format("{0:X4}", PPUAddress);
			int TileID = _ppu.PeekPPU(PPUAddress);
			TileIDLabel.Text = string.Format("{0:X2}", TileID);
			TableLabel.Text = NameTable.ToString();

			int ytable = 0, yline = 0;
			if (e.Y >= 240)
			{
				ytable += 2;
				yline = 240;
			}
			int table = (e.X >> 8) + ytable;
			int ntaddr = (table << 10);
			int px = e.X & 255;
			int py = e.Y - yline;
			int tx = px >> 3;
			int ty = py >> 3;
			int atbyte_ptr = ntaddr + 0x3C0 + ((ty >> 2) << 3) + (tx >> 2);
			int at = _ppu.PeekPPU(atbyte_ptr + 0x2000);
			if ((ty & 2) != 0) at >>= 4;
			if ((tx & 2) != 0) at >>= 2;
			at &= 0x03;
			PaletteLabel.Text = at.ToString();
		}

		private void NameTableView_MouseLeave(object sender, EventArgs e)
		{
			XYLabel.Text = string.Empty;
			PPUAddressLabel.Text = string.Empty;
			TileIDLabel.Text = string.Empty;
			TableLabel.Text = string.Empty;
			PaletteLabel.Text = string.Empty;
		}

		#endregion

		#endregion
	}
}
