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

using BizHawk.Emulation.Common;
using BizHawk.Client.Common;
using System.Windows;
using BizHawk.Common.NumberExtensions;

namespace BizHawk.Client.EmuHawk
{
	public partial class VirtualPadAnalogStick : UserControl, IVirtualPadControl
	{
		#region Fields

		private bool _programmaticallyUpdatingNumerics;
		private bool _readonly;
		private int rangeAverageX; //for coordinate transformation when non orthogonal (like PSX for example)
		private int rangeAverageY;

		private EventHandler manualXYValueChangedEventHandler;
		private EventHandler polarNumericChangedEventHandler;

		#endregion

		public VirtualPadAnalogStick()
		{
			InitializeComponent();
			AnalogStick.ClearCallback = ClearCallback;

			manualXYValueChangedEventHandler = new EventHandler(ManualXY_ValueChanged);
			polarNumericChangedEventHandler = new EventHandler(PolarNumeric_Changed);

			ManualX.ValueChanged += manualXYValueChangedEventHandler;
			ManualY.ValueChanged += manualXYValueChangedEventHandler;
			manualR.ValueChanged += polarNumericChangedEventHandler;
			manualTheta.ValueChanged += polarNumericChangedEventHandler;
		}

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

		public string SecondaryName { get; set; }


		private void VirtualPadAnalogStick_Load(object sender, EventArgs e)
		{
			AnalogStick.Name = Name;
			AnalogStick.XName = Name;
			AnalogStick.YName = !string.IsNullOrEmpty(SecondaryName)
				? SecondaryName
				: Name.Replace("X", "Y"); // Fallback
			AnalogStick.SetRangeX(RangeX);
			AnalogStick.SetRangeY(RangeY);

			ManualX.Minimum = (decimal)RangeX[0];
			ManualX.Maximum = (decimal)RangeX[2];

			ManualY.Minimum = (decimal)RangeX[0];
			ManualY.Maximum = (decimal)RangeX[2];

			MaxXNumeric.Minimum = 1;
			MaxXNumeric.Maximum = 100;
			MaxXNumeric.Value = 100;

			MaxYNumeric.Minimum = 1;
			MaxYNumeric.Maximum = 100;
			MaxYNumeric.Value = 100; // Note: these trigger change events that change the analog stick too

			rangeAverageX = (int)((RangeX[0] + RangeX[2]) / 2);
			rangeAverageY = (int)((RangeY[0] + RangeY[2]) / 2);
		}

		#region IVirtualPadControl Implementation

		public void UpdateValues()
		{
			// Nothing to do
			// This tool already draws as necessary
		}

		public void Set(IController controller)
		{
			AnalogStick.Set(controller);
			SetNumericsFromAnalog();
		}

		public void ClearCallback()
		{
			ManualX.Value = 0;
			ManualY.Value = 0;
			manualR.Value = 0;
			manualTheta.Value = 0;
			//see HOOMOO
			Global.AutofireStickyXORAdapter.SetSticky(AnalogStick.XName, false);
			Global.StickyXORAdapter.Unset(AnalogStick.XName);
			Global.AutofireStickyXORAdapter.SetSticky(AnalogStick.YName, false);
			Global.StickyXORAdapter.Unset(AnalogStick.YName);
			AnalogStick.HasValue = false;
		}

		public void Clear()
		{
			AnalogStick.Clear();
		}

		public bool ReadOnly
		{
			get
			{
				return _readonly;
			}

			set
			{
				if (_readonly != value)
				{
					XLabel.Enabled =
						ManualX.Enabled =
						YLabel.Enabled =
						ManualY.Enabled =
						MaxLabel.Enabled =
						MaxXNumeric.Enabled =
						MaxYNumeric.Enabled =
						manualR.Enabled =
						rLabel.Enabled =
						manualTheta.Enabled =
						thetaLabel.Enabled =
						!value;

					AnalogStick.ReadOnly =
						_readonly =
						value;

					Refresh();
				}
			}
		}

		#endregion

		public void Bump(int? x, int? y)
		{
			if (x.HasValue)
			{
				AnalogStick.HasValue = true;
				AnalogStick.X += x.Value;
			}

			if (y.HasValue)
			{
				AnalogStick.HasValue = true;
				AnalogStick.Y += y.Value;
			}

			SetNumericsFromAnalog();
		}

		public void SetPrevious(IController previous)
		{
			AnalogStick.SetPrevious(previous);
		}

		private void ManualXY_ValueChanged(object sender, EventArgs e)
		{
			SetAnalogControlFromNumerics();
		}
		private void MaxManualXY_ValueChanged(object sender, EventArgs e)
		{
			SetAnalogMaxFromNumerics();
		}

		private void PolarNumeric_Changed(object sender, EventArgs e)
		{
			ManualX.ValueChanged -= manualXYValueChangedEventHandler;
			ManualY.ValueChanged -= manualXYValueChangedEventHandler;

			ManualX.Value = Math.Ceiling(manualR.Value * (decimal)Math.Cos(Math.PI * (double)manualTheta.Value / 180)).Clamp(-127, 127) + rangeAverageX;
			ManualY.Value = Math.Ceiling(manualR.Value * (decimal)Math.Sin(Math.PI * (double)manualTheta.Value / 180)).Clamp(-127, 127) + rangeAverageY;

			AnalogStick.X = (int)ManualX.Value;
			AnalogStick.Y = (int)ManualY.Value;

			AnalogStick.HasValue = true;
			AnalogStick.Refresh();

			ManualX.ValueChanged += manualXYValueChangedEventHandler;
			ManualY.ValueChanged += manualXYValueChangedEventHandler;
		}

		private void SetAnalogControlFromNumerics()
		{
			if (!_programmaticallyUpdatingNumerics)
			{
				AnalogStick.X = (int)ManualX.Value;
				AnalogStick.Y = (int)ManualY.Value;
				AnalogStick.HasValue = true;
				AnalogStick.Refresh();
			}
		}

		private void SetNumericsFromAnalog()
		{
			_programmaticallyUpdatingNumerics = true;

			if (AnalogStick.HasValue)
			{
				// Setting .Value of a numeric causes a draw, so avoid it unless necessary
				if (ManualX.Value != AnalogStick.X)
				{
					ManualX.Value = AnalogStick.X;
				}

				if (ManualY.Value != AnalogStick.Y)
				{
					ManualY.Value = AnalogStick.Y;
				}
			}
			else
			{
				if (ManualX.Value != 0)
				{
					ManualX.Value = 0;
				}

				if (ManualY.Value != 0)
				{
					ManualY.Value = 0;
				}
			}

			manualR.ValueChanged -= polarNumericChangedEventHandler;
			manualTheta.ValueChanged -= polarNumericChangedEventHandler;

			manualR.Value =  Math.Min(manualR.Value, (decimal)Math.Sqrt(Math.Pow(AnalogStick.X - rangeAverageX, 2) + Math.Pow(AnalogStick.Y - rangeAverageY, 2)));
			manualTheta.Value = (decimal)(Math.Atan2(AnalogStick.Y - rangeAverageY, AnalogStick.X - rangeAverageX) * (180 / Math.PI));

			manualR.ValueChanged += polarNumericChangedEventHandler;
			manualTheta.ValueChanged += polarNumericChangedEventHandler;

			_programmaticallyUpdatingNumerics = false;
		}

		private void AnalogStick_MouseDown(object sender, MouseEventArgs e)
		{
			if (!ReadOnly)
			{
				_programmaticallyUpdatingNumerics = true;
				SetNumericsFromAnalog();
				_programmaticallyUpdatingNumerics = false;
			}
		}

		private void AnalogStick_MouseMove(object sender, MouseEventArgs e)
		{
			if (!ReadOnly)
			{
				_programmaticallyUpdatingNumerics = true;
				SetNumericsFromAnalog();
				_programmaticallyUpdatingNumerics = false;
			}
		}

		private void SetAnalogMaxFromNumerics()
		{
			if (!_programmaticallyUpdatingNumerics)
			{
				//blehh,... this damn feature
				AnalogStick.SetUserRange((float)MaxXNumeric.Value, (float)MaxYNumeric.Value);
			}
		}
	}
}
