using System;
using System.Collections.Generic;

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

namespace BizHawk.Client.Common
{
	using System.Linq;

	/// <summary>
	/// will hold buttons for 1 frame and then release them. (Calling Click() from your button click is what you want to do)
	/// TODO - should the duration be controllable?
	/// </summary>
	public class ClickyVirtualPadController : IController
	{
		public ControllerDefinition Type { get; set; }
		
		public bool this[string button]
		{
			get { return IsPressed(button); }
		}

		public float GetFloat(string name)
		{
			return 0.0f;
		}

		// TODO
		public bool IsPressed(string button)
		{
			return _pressed.Contains(button);
		}
		
		/// <summary>
		/// call this once per frame to do the timekeeping for the hold and release
		/// </summary>
		public void FrameTick()
		{
			_pressed.Clear();
		}

		/// <summary>
		/// call this to hold the button down for one frame
		/// </summary>
		public void Click(string button)
		{
			_pressed.Add(button);
		}

		public void Unclick(string button)
		{
			_pressed.Remove(button);
		}

		public void Toggle(string button)
		{
			if (IsPressed(button))
			{
				_pressed.Remove(button);
			}
			else
			{
				_pressed.Add(button);
			}
		}

		private readonly HashSet<string> _pressed = new HashSet<string>();
	}

	/// <summary>
	/// Filters input for things called Up and Down while considering the client's AllowUD_LR option. 
	/// This is a bit gross but it is unclear how to do it more nicely
	/// </summary>
	public class UD_LR_ControllerAdapter : IController
	{
		public ControllerDefinition Type
		{
			get { return Source.Type; }
		}

		public bool this[string button]
		{
			get { return IsPressed(button); }
		}

		public IController Source { get; set; }

		// The float format implies no U+D and no L+R no matter what, so just passthru
		public float GetFloat(string name)
		{
			return Source.GetFloat(name);
		}

		public bool IsPressed(string button)
		{
			if (Global.Config.AllowUD_LR)
			{
				return Source.IsPressed(button);
			}

			string prefix;

			if (button.Contains("Down") && !button.Contains(" C "))
			{
				prefix = button.GetPrecedingString("Down");
				if (Source.IsPressed(prefix + "Up"))
				{
					return false;
				}
			}

			if (button.Contains("Right") && !button.Contains(" C "))
			{
				prefix = button.GetPrecedingString("Right");
				if (Source.IsPressed(prefix + "Left"))
				{
					return false;
				}
			}

			return Source.IsPressed(button);
		}
	}

	public class SimpleController : IController
	{
		public ControllerDefinition Type { get; set; }

		protected WorkingDictionary<string, bool> Buttons = new WorkingDictionary<string, bool>();
		protected WorkingDictionary<string, float> Floats = new WorkingDictionary<string, float>();

		public virtual void Clear()
		{
			Buttons = new WorkingDictionary<string, bool>();
			Floats = new WorkingDictionary<string, float>();
		}

		public virtual bool this[string button]
		{
			get { return Buttons[button]; } set { Buttons[button] = value; }
		}

		public virtual bool IsPressed(string button)
		{
			return this[button];
		}

		public float GetFloat(string name)
		{
			return Floats[name];
		}

		public IEnumerable<KeyValuePair<string, bool>> BoolButtons()
		{
			return Buttons;
		}

		public virtual void LatchFrom(IController source)
		{
			foreach (var button in source.Type.BoolButtons)
			{
				Buttons[button] = source[button];
			}
		}

		public void AcceptNewFloats(IEnumerable<Tuple<string, float>> newValues)
		{
			foreach (var sv in newValues)
			{
				Floats[sv.Item1] = sv.Item2;
			}
		}
	}

	public class ORAdapter : IController
	{
		public bool IsPressed(string button)
		{
			return this[button];
		}

		// pass floats solely from the original source
		// this works in the code because SourceOr is the autofire controller
		public float GetFloat(string name) { return Source.GetFloat(name); }

		public IController Source { get; set; }
		public IController SourceOr { get; set; }
		public ControllerDefinition Type { get { return Source.Type; } set { throw new InvalidOperationException(); } }

		public bool this[string button]
		{
			get
			{
				return Source[button] | SourceOr[button];
			}
			set
			{
				throw new InvalidOperationException();
			}
		}

	}

	public class StickyXorAdapter : IController
	{
		protected HashSet<string> stickySet = new HashSet<string>();
		
		public IController Source { get; set; }

		public ControllerDefinition Type
		{
			get { return Source.Type; } 
			set { throw new InvalidOperationException(); }
		}

		public bool Locked { get; set; } // Pretty much a hack, 

		public bool IsPressed(string button) { return this[button]; }

		// if SetFloat() is called (typically virtual pads), then that float will entirely override the Source input
		// otherwise, the source is passed thru.
		private readonly WorkingDictionary<string,float?> _floatSet = new WorkingDictionary<string,float?>();
		
		public void SetFloat(string name, float? value)
		{
			if (value.HasValue)
			{
				_floatSet[name] = value;
			}
			else
			{
				_floatSet.Remove(name);
			}
		}

		public float GetFloat(string name)
		{
			return _floatSet[name] ?? Source.GetFloat(name);
		}

		public void ClearStickyFloats()
		{
			_floatSet.Clear();
		}

		public bool this[string button]
		{ 
			get 
			{
				var source = Source[button];
				source ^= stickySet.Contains(button);
				return source;
			}

			set
			{
				throw new InvalidOperationException();
			}
		}

		public void SetSticky(string button, bool isSticky)
		{
			if (isSticky)
			{
				stickySet.Add(button);
			}
			else
			{
				stickySet.Remove(button);
			}
		}

		public void Unset(string button)
		{
			stickySet.Remove(button);
		}

		public bool IsSticky(string button)
		{
			return stickySet.Contains(button);
		}

		public HashSet<string> CurrentStickies
		{
			get
			{
				return stickySet;
			}
		}

		public void ClearStickies()
		{
			stickySet.Clear();
		}

		public void MassToggleStickyState(List<string> buttons)
		{
			foreach (var button in buttons.Where(button => !_justPressed.Contains(button)))
			{
				if (stickySet.Contains(button))
				{
					stickySet.Remove(button);
				}
				else
				{
					stickySet.Add(button);
				}
			}

			_justPressed = buttons;
		}

		private List<string> _justPressed = new List<string>();
	}

	public class AutoFireStickyXorAdapter : IController
	{
		public int On { get; set; }
		public int Off { get; set; }
		public WorkingDictionary<string, int> buttonStarts = new WorkingDictionary<string, int>();
		
		private readonly HashSet<string> _stickySet = new HashSet<string>();

		public IController Source { get; set; }

		public void SetOnOffPatternFromConfig()
		{
			On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn;
			Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff;
		}

		public AutoFireStickyXorAdapter()
		{
			// On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn;
			// Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff;
			On = 1;
			Off = 1;
		}

		public bool IsPressed(string button)
		{
			if (_stickySet.Contains(button))
			{
				var a = (Global.Emulator.Frame - buttonStarts[button]) % (On + Off);
				if (a < On)
				{
					return this[button];
				}
				else
				{
					return false;
				}
			}
			else
			{
				return Source[button];
			}
		}

		public bool this[string button]
		{
			get
			{
				var source = Source[button];

				if (_stickySet.Contains(button))
				{
					var a = (Global.Emulator.Frame - buttonStarts[button]) % (On + Off);
					if (a < On)
					{
						source ^= true;
					}
					else
					{
						source ^= false;
					}
				}
				
				return source;
			}

			set
			{
				throw new InvalidOperationException();
			}
		}

		public ControllerDefinition Type { get { return Source.Type; } set { throw new InvalidOperationException(); } }
		public bool Locked { get; set; } // Pretty much a hack, 

		// dumb passthrough for floats, because autofire doesn't care about them
		public float GetFloat(string name)
		{
			return Source.GetFloat(name);
		}

		public void SetSticky(string button, bool isSticky)
		{
			if (isSticky)
			{
				this._stickySet.Add(button);
			}
			else
			{
				this._stickySet.Remove(button);
			}
		}

		public bool IsSticky(string button)
		{
			return this._stickySet.Contains(button);
		}

		public HashSet<string> CurrentStickies
		{
			get
			{
				return this._stickySet;
			}
		}

		public void ClearStickies()
		{
			this._stickySet.Clear();
		}

		public void MassToggleStickyState(List<string> buttons)
		{
			foreach (var button in buttons.Where(button => !_justPressed.Contains(button)))
			{
				if (_stickySet.Contains(button))
				{
					_stickySet.Remove(button);
				}
				else
				{
					_stickySet.Add(button);
				}
			}

			_justPressed = buttons;
		}

		private List<string> _justPressed = new List<string>();
	}

	/// <summary>
	/// Just copies source to sink, or returns whatever a NullController would if it is disconnected. useful for immovable hardpoints.
	/// </summary>
	public class CopyControllerAdapter : IController
	{
		public IController Source { get; set; }
		
		private readonly NullController _null = new NullController();

		private IController Curr
		{
			get
			{
				if (Source == null)
				{
					return _null;
				}
				else
				{
					return Source;
				}
			}
		}

		public ControllerDefinition Type
		{
			get { return Curr.Type; }
		}

		public bool this[string button]
		{
			get { return Curr[button]; }
		}

		public bool IsPressed(string button)
		{
			return Curr.IsPressed(button);
		}

		public float GetFloat(string name)
		{
			return Curr.GetFloat(name);
		}
	}

	public class ButtonNameParser
	{
		public static ButtonNameParser Parse(string button)
		{
			// See if we're being asked for a button that we know how to rewire
			var parts = button.Split(' ');
			
			if (parts.Length < 2)
			{
				return null;
			}

			if (parts[0][0] != 'P')
			{
				return null;
			}

			int player;
			if (!int.TryParse(parts[0].Substring(1), out player))
			{
				return null;
			}
			else
			{
				return new ButtonNameParser { PlayerNum = player, ButtonPart = button.Substring(parts[0].Length + 1) };
			}
		}

		public int PlayerNum { get; set; }
		public string ButtonPart { get; private set; }

		public override string ToString()
		{
			return string.Format("P{0} {1}", PlayerNum, ButtonPart);
		}
	}

	/// <summary>
	/// rewires player1 controls to playerN
	/// </summary>
	public class MultitrackRewiringControllerAdapter : IController
	{
		public IController Source { get; set; }
		public int PlayerSource = 1;
		public int PlayerTargetMask = 0;

		public ControllerDefinition Type { get { return Source.Type; } }
		public bool this[string button] { get { return IsPressed(button); } }
		
		// floats can be player number remapped just like boolbuttons
		public float GetFloat(string name) { return Source.GetFloat(RemapButtonName(name)); }

		private string RemapButtonName(string button)
		{
			// Do we even have a source?
			if (PlayerSource == -1)
			{
				return button;
			}

			// See if we're being asked for a button that we know how to rewire
			var bnp = ButtonNameParser.Parse(button);
			
			if (bnp == null)
			{
				return button;
			}

			// Ok, this looks like a normal `P1 Button` type thing. we can handle it
			// Were we supposed to replace this one?
			int foundPlayerMask = (1 << bnp.PlayerNum);
			if ((PlayerTargetMask & foundPlayerMask) == 0)
			{
				return button;
			}

			// Ok, we were. swap out the source player and then grab his button
			bnp.PlayerNum = PlayerSource;
			return bnp.ToString();
		}

		public bool IsPressed(string button)
		{
			return Source.IsPressed(RemapButtonName(button));
		}
	}

	/// <summary>
	/// Used to pass into an Override method to manage the logic overriding input
	/// This only works with bool buttons!
	/// </summary>
	public class OverrideAdaptor : IController
	{
		private readonly Dictionary<string, bool> _overrides = new Dictionary<string, bool>();
		private readonly List<string> _inverses = new List<string>();

		public bool this[string button]
		{
			get
			{
				if (_overrides.ContainsKey(button))
				{
					return _overrides[button];
				}

				throw new InvalidOperationException();
			}

			set
			{
				if (_overrides.ContainsKey(button))
				{
					_overrides[button] = value;
				}
				else
				{
					_overrides.Add(button, value);
				}
			}
		}

		public ControllerDefinition Type { get; set; }

		public IEnumerable<string> Overrides
		{
			get
			{
				foreach (var kvp in _overrides)
				{
					yield return kvp.Key;
				}
			}
		}

		public IEnumerable<string> InversedButtons
		{
			get
			{
				foreach (var name in _inverses)
				{
					yield return name;
				}
			}
		}

		public float GetFloat(string name)
		{
			return 0.0F;
		}

		public bool IsPressed(string button) { return this[button]; }

		public void SetButton(string button, bool value)
		{
			this[button] = value;
			_inverses.Remove(button);
		}

		public void UnSet(string button)
		{
			_overrides.Remove(button);
			_inverses.Remove(button);
		}

		public void SetInverse(string button)
		{
			_inverses.Add(button);
		}

		public void FrameTick()
		{
			_overrides.Clear();
			_inverses.Clear();
		}
	}
}