using System;
using System.Drawing;
using System.Windows.Forms;

using BizHawk.Client.Common;
using BizHawk.Emulation.Common;

//Just because this code was mostly rewritten, dont think it isnt still awful

namespace BizHawk.Client.EmuHawk
{
	public sealed class AnalogStickPanel : Panel
	{
		private int _x = 0;
		private int _y = 0;

		public int X
		{
			get
			{
				return _x;
			}

			set
			{
				_x = value;
				SetAnalog();
			}
		}

		public int Y
		{
			get
			{
				return _y;
			}

			set
			{
				_y = value;
				SetAnalog();
			}
		}

		public bool HasValue = false;
		public bool ReadOnly { get; set; }

		public string XName = string.Empty;
		public string YName = string.Empty;

		private IController _previous = null;

		float UserRangePercentageX = 100, UserRangePercentageY = 100;

		public void SetUserRange(float rx, float ry)
		{
			UserRangePercentageX = rx;
			UserRangePercentageY = ry;
			Rerange();
			Refresh();
		}

		public void SetRangeX(float[] range)
		{
			for (int i = 0; i < 3; i++) ActualRangeX[i] = range[i];
			Rerange();
		}

		public void SetRangeY(float[] range)
		{
			for (int i = 0; i < 3; i++) ActualRangeY[i] = range[i];
			Rerange();
		}

		public float[] RangeX = new float[] { -128f, 0.0f, 127f };
		public float[] RangeY = new float[] { -128f, 0.0f, 127f };
		public float[] ActualRangeX = new float[] { -128f, 0.0f, 127f };
		public float[] ActualRangeY = new float[] { -128f, 0.0f, 127f };

		float flipx = 1, flipy = 1;

		void Rerange()
		{
			//baseline:
			//Array.Copy(ActualRangeX, RangeX, 3);
			//Array.Copy(ActualRangeY, RangeY, 3);

			float rx = ActualRangeX[2] - ActualRangeX[0];
			float ry = ActualRangeY[2] - ActualRangeY[0];
			float midx = rx / 2 + ActualRangeX[0];
			float midy = ry / 2 + ActualRangeY[0];
			rx *= UserRangePercentageX / 100;
			ry *= UserRangePercentageY / 100;
			float minx = midx - rx / 2;
			float maxx = minx + rx;
			float miny = midy - ry / 2;
			float maxy = miny + ry;

			if (minx > maxx)
			{
				float temp = minx;
				minx = maxx;
				maxx = temp;
				flipx = -1;
			}

			if (miny > maxy)
			{
				float temp = miny;
				miny = maxy;
				maxy = temp;
				flipy = -1;
			}

			//Range?[1] isn't really used
			RangeX[0] = minx;
			RangeX[2] = maxx;
			RangeY[0] = miny;
			RangeY[2] = maxy;

			Clamp();
		}

		//dont count on this working. it's never been tested.
		//but it kind of must be, or else nothing here would work...
		public float ScaleX = 0.5f;
		public float ScaleY = 0.5f;

		int MinX { get { return (int)(RangeX[0]); } }
		int MinY { get { return (int)(RangeY[0]); } }
		int MaxX { get { return (int)(RangeX[2]); } }
		int MaxY { get { return (int)(RangeY[2]); } }
		int RangeSizeX { get { return (int)(MaxX - MinX + 1); } }
		int RangeSizeY { get { return (int)(MaxY - MinY + 1); } }

		int PixelSizeX { get { return (int)(RangeSizeX * ScaleX); } }
		int PixelSizeY { get { return (int)(RangeSizeY * ScaleY); } }
		int PixelMinX { get { return (Size.Width - PixelSizeX) / 2; } }
		int PixelMinY { get { return (Size.Height - PixelSizeY) / 2; } }
		int PixelMidX { get { return PixelMinX + PixelSizeX / 2; } }
		int PixelMidY { get { return PixelMinY + PixelSizeY / 2; } }
		int PixelMaxX { get { return PixelMinX + PixelSizeX - 1; } }
		int PixelMaxY { get { return PixelMinY + PixelSizeY - 1; } }

		private int RealToGfxX(int val)
		{
			int v = val;
			if (flipx == -1)
				v = (MaxX - val) + MinX;
			v = (int)(((float)v - MinX) * ScaleX);
			v += PixelMinX;
			return v;
		}

		private int RealToGfxY(int val)
		{
			int v = val;
			if (flipy == -1)
				v = (MaxY - val) + MinY;
			v = (int)(((float)v - MinY) * ScaleY);
			v += PixelMinY;
			return v;
		}

		private int GfxToRealX(int val)
		{
			val -= PixelMinX;
			float v = ((float)val / ScaleX + MinX);
			if (v < MinX) v = MinX;
			if (v > MaxX) v = MaxX;
			if (flipx == -1)
				v = (MaxX - v) + MinX;
			return (int)v;
		}

		private int GfxToRealY(int val)
		{
			val -= PixelMinY;
			float v;
			v = ((float)val / ScaleY + MinY);
			if (v < MinX) v = MinX;
			if (v > MaxX) v = MaxX;
			if(flipy == -1)
				v = (MaxY - v) + MinY;
			return (int)v;
		}

		private readonly Brush WhiteBrush = Brushes.White;
		private readonly Brush GrayBrush = Brushes.LightGray;
		private readonly Brush RedBrush = Brushes.Red;
		private readonly Brush OffWhiteBrush = Brushes.Beige;

		private readonly Pen BlackPen = new Pen(Brushes.Black);
		private readonly Pen BluePen = new Pen(Brushes.Blue, 2);
		private readonly Pen GrayPen = new Pen(Brushes.Gray, 2);

		private readonly Bitmap Dot = new Bitmap(7, 7);
		private readonly Bitmap GrayDot = new Bitmap(7, 7);

		public Action ClearCallback { get; set; }

		private void DoClearCallback()
		{
			if (ClearCallback != null)
			{
				ClearCallback();
			}
		}

		public AnalogStickPanel()
		{
			Size = new Size(PixelSizeX + 1, PixelSizeY + 1);
			SetStyle(ControlStyles.AllPaintingInWmPaint, true);
			SetStyle(ControlStyles.UserPaint, true);
			SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
			SetStyle(ControlStyles.SupportsTransparentBackColor, true);
			SetStyle(ControlStyles.Opaque, true);
			BackColor = Color.Gray;
			Paint += AnalogControlPanel_Paint;
			BorderStyle = BorderStyle.Fixed3D;

			// Draw the dot into a bitmap
			using (var g = Graphics.FromImage(Dot))
			{
				g.Clear(Color.Transparent);
				g.FillRectangle(RedBrush, 2, 0, 3, 7);
				g.FillRectangle(RedBrush, 1, 1, 5, 5);
				g.FillRectangle(RedBrush, 0, 2, 7, 3);
			}

			using (var gg = Graphics.FromImage(GrayDot))
			{
				gg.Clear(Color.Transparent);
				gg.FillRectangle(Brushes.Gray, 2, 0, 3, 7);
				gg.FillRectangle(Brushes.Gray, 1, 1, 5, 5);
				gg.FillRectangle(Brushes.Gray, 0, 2, 7, 3);
			}
		}

		private void SetAnalog()
		{
			var xn = HasValue ? X : (int?)null;
			var yn = HasValue ? Y : (int?)null;
			Global.StickyXORAdapter.SetFloat(XName, xn);
			Global.StickyXORAdapter.SetFloat(YName, yn);

			Refresh();
		}

		private void AnalogControlPanel_Paint(object sender, PaintEventArgs e)
		{
			unchecked
			{
				// Background
				e.Graphics.Clear(Color.Black);

				e.Graphics.FillRectangle(GrayBrush, PixelMinX, PixelMinY, PixelMaxX - PixelMinX, PixelMaxY- PixelMinY);
				e.Graphics.FillEllipse(ReadOnly ? OffWhiteBrush : WhiteBrush, PixelMinX, PixelMinY, PixelMaxX - PixelMinX - 2, PixelMaxY - PixelMinY - 3);
				e.Graphics.DrawEllipse(BlackPen, PixelMinX, PixelMinY, PixelMaxX - PixelMinX - 2, PixelMaxY - PixelMinY - 3);
				e.Graphics.DrawLine(BlackPen, PixelMidX, 0, PixelMidX, PixelMaxY);
				e.Graphics.DrawLine(BlackPen, 0, PixelMidY, PixelMaxX, PixelMidY);

				// Previous frame
				if (_previous != null)
				{
					var pX = (int)_previous.GetFloat(XName);
					var pY = (int)_previous.GetFloat(YName);
					e.Graphics.DrawLine(GrayPen, PixelMidX, PixelMidY, RealToGfxX(pX), RealToGfxY(pY));
					e.Graphics.DrawImage(GrayDot, RealToGfxX(pX) - 3, RealToGfxY(MaxY) - RealToGfxY(pY) - 3);
				}

				// Line
				if (HasValue)
				{
					e.Graphics.DrawLine(BluePen, PixelMidX, PixelMidY, RealToGfxX(X), RealToGfxY(Y));
					e.Graphics.DrawImage(ReadOnly ? GrayDot : Dot, RealToGfxX(X) - 3, RealToGfxY(Y) - 3);
				}
			}
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			if (!ReadOnly)
			{
				if (e.Button == MouseButtons.Left)
				{
					X = GfxToRealX(e.X);
					Y = GfxToRealY(e.Y);
					Clamp();
					HasValue = true;
					SetAnalog();
				}
				else if (e.Button == MouseButtons.Right)
				{
					Clear();
				}

				Refresh();
				base.OnMouseMove(e);
			}
		}

		protected override void OnMouseUp(MouseEventArgs e)
		{
			base.OnMouseUp(e);
			Capture = false;
		}

		protected override void WndProc(ref Message m)
		{
			if (m.Msg == 0x007B) // WM_CONTEXTMENU
			{
				// Don't let parent controls get this. We handle the right mouse button ourselves
				return;
			}

			base.WndProc(ref m);
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
			if (!ReadOnly)
			{
				if (e.Button == MouseButtons.Left)
				{
					X = GfxToRealX(e.X);
					Y = GfxToRealY(e.Y);
					Clamp();
					HasValue = true;
				}
				if (e.Button == MouseButtons.Right)
				{
					Clear();
				}

				Refresh();
			}
		}


		public void Clear()
		{
			if (X != 0 || Y != 0 || HasValue)
			{
				X = Y = 0;
				HasValue = false;
				DoClearCallback();
				Refresh();
			}
		}

		public void Set(IController controller)
		{
			var newX = (int)controller.GetFloat(XName);
			var newY = (int)controller.GetFloat(YName);
			var changed = newX != X || newY != Y;
			if (changed)
			{
				SetPosition(newX, newY);
			}
		}

		public void SetPrevious(IController previous)
		{
			_previous = previous;
		}

		public void SetPosition(int xval, int yval)
		{
			X = xval;
			Y = yval;
			Clamp();
			HasValue = true;
			
			Refresh();
		}

		private void Clamp()
		{
			if (X > MaxX)
			{
				X = MaxX;
			}
			else if (X < MinX)
			{
				X = MinX;
			}

			if (Y > MaxY)
			{
				Y = MaxY;
			}
			else if (Y < MinY)
			{
				Y = MinY;
			}
		}
	}
}
