using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Common;
using BizHawk.Common;
using System.Reflection;
using Newtonsoft.Json;

namespace BizHawk.Emulation.Cores.Nintendo.NES
{
	/*
	 * This file covers all NES and Famicom controller related stuff.
	 * It supports (or could be easily made to support by adding a new class) every existing
	 * controller device I know about.  It does not support some things that were theoretically
	 * possible with the electronic interface available, but never used.
	 */

	#region interfaces and such

	/// <summary>
	/// stores information about the strobe lines controlled by $4016
	/// </summary>
	public struct StrobeInfo
	{
		/// <summary>
		/// the current value of $4016.0; strobes regular controller ports
		/// </summary>
		public readonly int OUT0;
		/// <summary>
		/// the current value of $4016.1; strobes expansion port
		/// </summary>
		public readonly int OUT1;
		/// <summary>
		/// the current value of $4016.2; strobes expansion port
		/// </summary>
		public readonly int OUT2;
		/// <summary>
		/// the previous value or $4016.0 (for edge sensitive equipment)
		/// </summary>
		public readonly int OUT0old;
		/// <summary>
		/// the previous value or $4016.1 (for edge sensitive equipment)
		/// </summary>
		public readonly int OUT1old;
		/// <summary>
		/// the previous value or $4016.2 (for edge sensitive equipment)
		/// </summary>
		public readonly int OUT2old;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="oldvalue">the old latched $4016 byte</param>
		/// <param name="newvalue">the new latched $4016 byte</param>
		public StrobeInfo(byte oldvalue, byte newvalue)
		{
			OUT0old = oldvalue & 1;
			OUT1old = oldvalue >> 1 & 1;
			OUT2old = oldvalue >> 2 & 1;
			OUT0 = newvalue & 1;
			OUT1 = newvalue >> 1 & 1;
			OUT2 = newvalue >> 2 & 1;
		}
	}

	/// <summary>
	/// the main system deck, handling all $4016 writes and $4016/$4017 reads
	/// </summary>
	public interface IControllerDeck
	{
		/// <summary>
		/// call whenever $4016 is written
		/// </summary>
		/// <param name="s"></param>
		/// <param name="c"></param>
		void Strobe(StrobeInfo s, IController c);
		/// <summary>
		/// call whenever $4016 is read
		/// </summary>
		/// <param name="c"></param>
		/// <returns>bits 0-4 are valid</returns>
		byte ReadA(IController c); // D0:D4
		/// <summary>
		/// call whenever $4017 is read
		/// </summary>
		/// <param name="c"></param>
		/// <returns>bits 0-4 are valid</returns>
		byte ReadB(IController c); // D0:D4
		ControllerDefinition GetDefinition();
		void SyncState(Serializer ser);
	}

	/// <summary>
	/// a peripheral that plugs into the famicom expansion port
	/// </summary>
	public interface IFamicomExpansion
	{
		void Strobe(StrobeInfo s, IController c);
		/// <summary>
		/// read data from $4016
		/// </summary>
		/// <param name="c"></param>
		/// <returns>only bit 1 is valid</returns>
		byte ReadA(IController c);
		/// <summary>
		/// read data from $4017
		/// </summary>
		/// <param name="c"></param>
		/// <returns>bits 1-4 are valid</returns>
		byte ReadB(IController c);
		ControllerDefinition GetDefinition();
		void SyncState(Serializer ser);
	}

	/// <summary>
	/// a peripheral that plugs into either of the two NES controller ports
	/// </summary>
	public interface INesPort
	{
		void Strobe(StrobeInfo s, IController c); // only uses OUT0
		byte Read(IController c); // only uses D0, D3, D4
		ControllerDefinition GetDefinition();
		void SyncState(Serializer ser);
	}

	#endregion

	/// <summary>
	/// a NES or AV famicom, with two attached devices
	/// </summary>
	public class NesDeck : IControllerDeck
	{
		INesPort Left;
		INesPort Right;
		ControlDefUnMerger LeftU;
		ControlDefUnMerger RightU;
		ControllerDefinition Definition;

		public NesDeck(INesPort Left, INesPort Right, Func<int, int, bool> PPUCallback)
		{
			this.Left = Left;
			this.Right = Right;
			List<ControlDefUnMerger> cdum;
			Definition = ControllerDefMerger.GetMerged(new[] { Left.GetDefinition(), Right.GetDefinition() }, out cdum);
			LeftU = cdum[0];
			RightU = cdum[1];

			// apply hacks
			// if this list gets very long, then something should be changed
			// if it stays short, then no problem
			if (Left is FourScore)
				(Left as FourScore).RightPort = false;
			if (Right is FourScore)
				(Right as FourScore).RightPort = true;
			if (Left is Zapper)
				(Left as Zapper).PPUCallback = PPUCallback;
			if (Right is Zapper)
				(Right as Zapper).PPUCallback = PPUCallback;
		}

		public void Strobe(StrobeInfo s, IController c)
		{
			Left.Strobe(s, LeftU.UnMerge(c));
			Right.Strobe(s, RightU.UnMerge(c));
		}

		public byte ReadA(IController c)
		{
			return (byte)(Left.Read(LeftU.UnMerge(c)) & 0x19);
		}

		public byte ReadB(IController c)
		{
			return (byte)(Right.Read(RightU.UnMerge(c)) & 0x19);
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.BeginSection("Left");
			Left.SyncState(ser);
			ser.EndSection();
			ser.BeginSection("Right");
			Right.SyncState(ser);
			ser.EndSection();
		}
	}

	public class UnpluggedNES : INesPort
	{
		public void Strobe(StrobeInfo s, IController c)
		{
		}

		public byte Read(IController c)
		{
			return 0;
		}

		public ControllerDefinition GetDefinition()
		{
			return new ControllerDefinition();
		}

		public void SyncState(Serializer ser)
		{
		}
	}

	/// <summary>
	/// a NES controller; also used internally to represent the two famicom controllers
	/// </summary>
	public class ControllerNES : INesPort
	{
		bool resetting = false;
		int latchedvalue = 0;

		static string[] Buttons =
		{
			"0A", "0B", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right"
		};
		static string[] FamicomP2Buttons =
		{
			"0A", "0B", null, null, "0Up", "0Down", "0Left", "0Right"
		};

		bool FamicomP2Hack;

		ControllerDefinition Definition;

		public ControllerNES()
		{
			Definition = new ControllerDefinition
			{
				BoolButtons = Buttons
					.OrderBy(x => ButtonOrdinals[x])
					.ToList()
			};
		}


		Dictionary<string, int> ButtonOrdinals = new Dictionary<string, int>
		{
			{ "0Up", 1 },
			{ "0Down", 2 },
			{ "0Left", 3 },
			{ "0Right", 4 },
			{ "0Start", 5 },
			{ "0Select", 6 },
			{ "0B", 7 },
			{ "0A", 8 },
		};

		public ControllerNES(bool famicomP2)
		{
			if (famicomP2)
			{
				Definition = new ControllerDefinition
				{
					BoolButtons = FamicomP2Buttons
					.Where((s) => s != null)
					.OrderBy(x => ButtonOrdinals[x])
					.ToList()
				};
			}
			else
			{
				Definition = new ControllerDefinition
				{
					BoolButtons = Buttons
					.OrderBy(x => ButtonOrdinals[x])
					.ToList()
				};
			}

			FamicomP2Hack = famicomP2;
		}

		// reset is not edge triggered; so long as it's high, the latch is continuously reloading
		// so we need to latch in two places:
		// 1. when OUT0 goes low, to get the last set
		// 2. wheneven reading with OUT0 high, since new data for controller is always loading

		void Latch(IController c)
		{
			latchedvalue = SerialUtil.Latch(FamicomP2Hack ? FamicomP2Buttons : Buttons, c);
		}

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 != 0;
			if (s.OUT0 < s.OUT0old)
				Latch(c);
		}

		public byte Read(IController c)
		{
			if (resetting)
				Latch(c);
			byte ret = (byte)(latchedvalue & 1);
			if (!resetting)
				latchedvalue >>= 1; // ASR not LSR, so endless stream of 1s after data
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("restting", ref resetting);
			ser.Sync("latchedvalue", ref latchedvalue);
		}
	}

	/// <summary>
	/// vaus paddle, the NES (not famicom) version
	/// </summary>
	public class ArkanoidNES : INesPort
	{
		int shiftidx = 0;
		bool resetting = false;
		byte latchedvalue = 0x54 ^ 0xff;

		static ControllerDefinition Definition = new ControllerDefinition
		{
			BoolButtons = { "0Fire" },
			FloatControls = { "0Paddle" },
			FloatRanges = { new[] { 0.0f, 80.0f, 160.0f } }
		};

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 != 0;
			if (resetting)
				shiftidx = 0;
			if (s.OUT0 > s.OUT0old)
			{
				latchedvalue = (byte)(0x54 + (int)c.GetFloat("0Paddle"));
				latchedvalue ^= 0xff;
			}
		}

		public byte Read(IController c)
		{
			byte ret = c["0Fire"] ? (byte)0x08 : (byte)0x00;
			if (resetting)
				return ret;

			byte value = latchedvalue;
			value <<= shiftidx;
			ret |= (byte)(value >> 3 & 0x10);
			shiftidx++;
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("shiftidx", ref shiftidx);
			ser.Sync("restting", ref resetting);
			ser.Sync("latchedvalue", ref latchedvalue);
		}
	}

	public class FourScore : INesPort
	{
		// fourscore is actually one two port thing
		// we emulate it as two separate halves
		// each one behaves slightly differently
		public bool RightPort = false;

		static string[] Buttons =
		{
			"0A", "0B", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right",
			"1A", "1B", "1Select", "1Start", "1Up", "1Down", "1Left", "1Right",
		};
		static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(Buttons) };

		bool resetting = false;
		int latchedvalue = 0;

		void Latch(IController c)
		{
			latchedvalue = SerialUtil.Latch(Buttons, c);
			// set signatures
			latchedvalue &= ~0xff0000;
			if (RightPort) // signatures
				latchedvalue |= 0x040000;
			else
				latchedvalue |= 0x080000;
		}

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 != 0;
			if (s.OUT0 < s.OUT0old)
				Latch(c);
		}

		public byte Read(IController c)
		{
			if (resetting)
				Latch(c);
			byte ret = (byte)(latchedvalue & 1);
			if (!resetting)
				latchedvalue >>= 1; // ASR not LSR, so endless stream of 1s after data
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("restting", ref resetting);
			ser.Sync("latchedvalue", ref latchedvalue);
		}
	}

	public class PowerPad : INesPort
	{
		static string[] D3Buttons = { "0PP2", "0PP1", "0PP5", "0PP9", "0PP6", "0PP10", "0PP11", "0PP7" };
		static string[] D4Buttons = { "0PP4", "0PP3", "0PP12", "0PP8" };
		static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(D3Buttons.Concat(D4Buttons)) };

		bool resetting = false;
		int latched3 = 0;
		int latched4 = 0;

		void Latch(IController c)
		{
			latched3 = SerialUtil.Latch(D3Buttons, c);
			latched4 = SerialUtil.Latch(D4Buttons, c);
		}

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 != 0;
			if (s.OUT0 < s.OUT0old)
				Latch(c);
		}

		public byte Read(IController c)
		{
			if (resetting)
				Latch(c);
			int d3 = latched3 & 1;
			int d4 = latched4 & 1;
			if (!resetting)
			{
				latched3 >>= 1; // ASR not LSR, so endless stream of 1s after data
				latched4 >>= 1;
			}
			return (byte)(d3 << 3 | d4 << 4);
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("restting", ref resetting);
			ser.Sync("latched3", ref latched3);
			ser.Sync("latched4", ref latched4);
		}
	}

	public class Zapper : INesPort, IFamicomExpansion
	{
		/// <summary>
		/// returns true if light was detected at the ppu coordinates specified
		/// </summary>
		public Func<int, int, bool> PPUCallback;

		static ControllerDefinition Definition = new ControllerDefinition
		{
			BoolButtons = { "0Fire" },
			FloatControls = { "0Zapper X", "0Zapper Y" },
			FloatRanges = { new[] { 0.0f, 128.0f, 255.0f }, new[] { 0.0f, 120.0f, 239.0f } }
		};

		public void Strobe(StrobeInfo s, IController c)
		{
		}

		// NES controller port interface
		public byte Read(IController c)
		{
			byte ret = 0;
			if (c["0Fire"])
				ret |= 0x10;
			if (!PPUCallback((int)c.GetFloat("0Zapper X"), (int)c.GetFloat("0Zapper Y")))
				ret |= 0x08;
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
		}

		// famicom expansion hookups
		public byte ReadA(IController c)
		{
			return 0;
		}

		public byte ReadB(IController c)
		{
			return Read(c);
		}
	}

	public class FamicomDeck : IControllerDeck
	{
		// two NES controllers are maintained internally
		INesPort Player1 = new ControllerNES(false);
		INesPort Player2 = new ControllerNES(true);
		IFamicomExpansion Player3;

		ControlDefUnMerger Player1U;
		ControlDefUnMerger Player2U;
		ControlDefUnMerger Player3U;

		ControllerDefinition Definition;

		public FamicomDeck(IFamicomExpansion ExpSlot, Func<int, int, bool> PPUCallback)
		{
			Player3 = ExpSlot;
			List<ControlDefUnMerger> cdum;
			Definition = ControllerDefMerger.GetMerged(
				new[] { Player1.GetDefinition(), Player2.GetDefinition(), Player3.GetDefinition() }, out cdum);
			Definition.BoolButtons.Add("P2 Microphone");
			Player1U = cdum[0];
			Player2U = cdum[1];
			Player3U = cdum[2];

			// hack
			if (Player3 is Zapper)
				(Player3 as Zapper).PPUCallback = PPUCallback;
		}

		public void Strobe(StrobeInfo s, IController c)
		{
			Player1.Strobe(s, Player1U.UnMerge(c));
			Player2.Strobe(s, Player2U.UnMerge(c));
			Player3.Strobe(s, Player3U.UnMerge(c));
		}

		public byte ReadA(IController c)
		{
			byte ret = 0;
			ret |= (byte)(Player1.Read(Player1U.UnMerge(c)) & 1);
			ret |= (byte)(Player3.ReadA(Player3U.UnMerge(c)) & 2);
			if (c["P2 Microphone"])
				ret |= 4;
			return ret;
		}

		public byte ReadB(IController c)
		{
			byte ret = 0;
			ret |= (byte)(Player2.Read(Player2U.UnMerge(c)) & 1);
			ret |= (byte)(Player3.ReadB(Player3U.UnMerge(c)) & 30);
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.BeginSection("Left");
			Player1.SyncState(ser);
			ser.EndSection();
			ser.BeginSection("Right");
			Player2.SyncState(ser);
			ser.EndSection();
			ser.BeginSection("Expansion");
			Player3.SyncState(ser);
			ser.EndSection();
		}
	}

	/// <summary>
	/// vaus controller that plugs into a famicom's expansion port
	/// </summary>
	public class ArkanoidFam : IFamicomExpansion
	{
		int shiftidx = 0;
		bool resetting = false;
		byte latchedvalue = 0x54 ^ 0xff;

		static ControllerDefinition Definition = new ControllerDefinition
		{
			BoolButtons = { "0Fire" },
			FloatControls = { "0Paddle" },
			FloatRanges = { new[] { 0.0f, 80.0f, 160.0f } }
		};

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 != 0;
			if (resetting)
				shiftidx = 0;
			if (s.OUT0 > s.OUT0old)
			{
				latchedvalue = (byte)(0x54 + (int)c.GetFloat("0Paddle"));
				latchedvalue ^= 0xff;
			}
		}

		public byte ReadA(IController c)
		{
			return c["0Fire"] ? (byte)0x02 : (byte)0x00;
		}

		public byte ReadB(IController c)
		{
			byte ret = 0;
			if (resetting)
				return ret;

			byte value = latchedvalue;
			value <<= shiftidx;
			ret |= (byte)(value >> 6 & 0x02);
			shiftidx++;
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("shiftidx", ref shiftidx);
			ser.Sync("restting", ref resetting);
			ser.Sync("latchedvalue", ref latchedvalue);
		}
	}

	public class FamilyBasicKeyboard : IFamicomExpansion
	{
		#region buttonlookup
		static string[] Buttons =
		{
			"0]",
			"0[",
			"0RETURN",
			"0F8",
			"0STOP",
			"0¥",
			"0RSHIFT",
			"0カナ",

			"0;",
			"0:",
			"0@",
			"0F7",
			"0^",
			"0-",
			"0/",
			"0_",

			"0K",
			"0L",
			"0O",
			"0F6",
			"00",
			"0P",
			"0,",
			"0.",

			"0J",
			"0U",
			"0I",
			"0F5",
			"08",
			"09",
			"0N",
			"0M",

			"0H",
			"0G",
			"0Y",
			"0F4",
			"06",
			"07",
			"0V",
			"0B",

			"0D",
			"0R",
			"0T",
			"0F3",
			"04",
			"05",
			"0C",
			"0F",

			"0A",
			"0S",
			"0W",
			"0F2",
			"03",
			"0E",
			"0Z",
			"0X",

			"0CTR",
			"0Q",
			"0ESC",
			"0F1",
			"02",
			"01",
			"0GRPH",
			"0LSHIFT",

			"0LEFT",
			"0RIGHT",
			"0UP",
			"0CLR",
			"0INS",
			"0DEL",
			"0SPACE",
			"0DOWN",

		};
		#endregion

		static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(Buttons) };

		bool active;
		int column;
		int row;

		public void Strobe(StrobeInfo s, IController c)
		{
			active = s.OUT2 != 0;
			column = s.OUT1;
			if (s.OUT1 > s.OUT1old)
			{
				row++;
				if (row == 10)
					row = 0;
			}
			if (s.OUT0 != 0) // should this be edge triggered?
				row = 0;
		}

		public byte ReadA(IController c)
		{
			return 0;
		}

		public byte ReadB(IController c)
		{
			if (!active)
				return 0;
			if (row == 9) // empty last row
				return 0;
			int idx = row * 8 + column * 4;

			byte ret = 0;

			if (c[Buttons[idx]]) ret |= 16;
			if (c[Buttons[idx + 1]]) ret |= 8;
			if (c[Buttons[idx + 2]]) ret |= 4;
			if (c[Buttons[idx + 3]]) ret |= 2;

			// nothing is clocked here
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("active", ref active);
			ser.Sync("column", ref column);
			ser.Sync("row", ref row);
		}
	}

	public class Famicom4P : IFamicomExpansion
	{
		static string[] P1Buttons =
		{
			"0A", "0B", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right"
		};
		static string[] P2Buttons =
		{
			"1A", "1B", "1Select", "1Start", "1Up", "1Down", "1Left", "1Right",
		};
		static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(P1Buttons.Concat(P2Buttons)) };

		bool resetting = false;
		int latchedp1 = 0;
		int latchedp2 = 0;

		void Latch(IController c)
		{
			latchedp1 = SerialUtil.Latch(P1Buttons, c);
			latchedp2 = SerialUtil.Latch(P2Buttons, c);
		}

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 != 0;
			if (s.OUT0 < s.OUT0old)
				Latch(c);
		}

		public byte ReadA(IController c)
		{
			if (resetting)
				Latch(c);
			byte ret = (byte)(latchedp1 << 1 & 2);
			if (!resetting)
				latchedp1 >>= 1;
			return ret;
		}

		public byte ReadB(IController c)
		{
			if (resetting)
				Latch(c);
			byte ret = (byte)(latchedp2 << 1 & 2);
			if (!resetting)
				latchedp2 >>= 1;
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("resetting", ref resetting);
			ser.Sync("latchedp1", ref latchedp1);
			ser.Sync("latchedp2", ref latchedp2);
		}
	}

	public class OekaKids : IFamicomExpansion
	{
		static ControllerDefinition Definition = new ControllerDefinition
		{
			BoolButtons = { "0Click", "0Touch" },
			FloatControls = { "0Pen X", "0Pen Y" },
			FloatRanges = { new[] { 0.0f, 128.0f, 255.0f }, new[] { 0.0f, 120.0f, 239.0f } }
		};

		bool resetting;
		int shiftidx;
		int latchedvalue = 0;

		public void Strobe(StrobeInfo s, IController c)
		{
			resetting = s.OUT0 == 0;
			if (s.OUT0 < s.OUT0old) // H->L: latch
			{
				int x = (int)c.GetFloat("0Pen X");
				int y = (int)c.GetFloat("0Pen Y");
				// http://forums.nesdev.com/viewtopic.php?p=19454#19454
				// it almost feels like the hardware guys got the request for 
				// a tablet that returned x in [0, 255] and y in [0, 239] and then
				// accidentally flipped the whole thing sideways
				x = (x + 8) * 240 / 256;
				y = (y - 14) * 256 / 240;
				x &= 255;
				y &= 255;
				latchedvalue = x << 10 | y << 2;
				if (c["0Touch"])
					latchedvalue |= 2;
				if (c["0Click"])
					latchedvalue |= 1;
			}
			if (s.OUT0 > s.OUT0old) // L->H: reset shift
				shiftidx = 0;
			if (s.OUT1 > s.OUT1old) // L->H: increment shift
				shiftidx++;
		}

		public byte ReadA(IController c)
		{
			return 0;
		}

		public byte ReadB(IController c)
		{
			byte ret = (byte)(resetting ? 2 : 0);
			if (resetting)
				return ret;

			// the shiftidx = 0 read is one off the end
			int bit = latchedvalue >> (16 - shiftidx);
			bit &= 4;
			bit ^= 4; // inverted data
			ret |= (byte)(bit);
			return ret;
		}

		public ControllerDefinition GetDefinition()
		{
			return Definition;
		}

		public void SyncState(Serializer ser)
		{
			ser.Sync("resetting", ref resetting);
			ser.Sync("shiftidx", ref shiftidx);
			ser.Sync("latchedvalue", ref latchedvalue);
		}
	}

	public class UnpluggedFam : IFamicomExpansion
	{
		public void Strobe(StrobeInfo s, IController c)
		{
		}

		public byte ReadA(IController c)
		{
			return 0;
		}

		public byte ReadB(IController c)
		{
			return 0;
		}

		public ControllerDefinition GetDefinition()
		{
			return new ControllerDefinition();
		}

		public void SyncState(Serializer ser)
		{
		}
	}

	public static class SerialUtil
	{
		public static int Latch(string[] values, IController c)
		{
			int ret = 0;
			for (int i = 0; i < 32; i++)
			{
				if (values.Length > i)
				{
					if (values[i] != null && c[values[i]])
						ret |= 1 << i;
				}
				else
				{
					// 1 in all other bits
					ret |= 1 << i;
				}
			}
			return ret;
		}
	}

	#region control definition adapters

	// the idea here is that various connected peripherals have their controls all merged
	// into one definition, including logic to unmerge the data back so each one can work
	// with it without knowing what else is connected

	public class ControlDefUnMerger
	{
		Dictionary<string, string> Remaps;

		public ControlDefUnMerger(Dictionary<string, string> Remaps)
		{
			this.Remaps = Remaps;
		}

		private class DummyController : IController
		{
			IController src;
			Dictionary<string, string> remaps;
			public DummyController(IController src, Dictionary<string, string> remaps)
			{
				this.src = src;
				this.remaps = remaps;
			}

			public ControllerDefinition Type { get { throw new NotImplementedException(); } }

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

			public bool IsPressed(string button)
			{
				return src.IsPressed(remaps[button]);
			}

			public float GetFloat(string name)
			{
				return src.GetFloat(remaps[name]);
			}
		}

		public IController UnMerge(IController c)
		{
			return new DummyController(c, Remaps);
		}

	}

	public static class ControllerDefMerger
	{
		private static string Allocate(string input, ref int plr, ref int plrnext)
		{
			int offset = int.Parse(input.Substring(0, 1));
			int currplr = plr + offset;
			if (currplr >= plrnext)
				plrnext = currplr + 1;
			return string.Format("P{0} {1}", currplr, input.Substring(1));
		}

		/// <summary>
		/// handles all player number merging
		/// </summary>
		/// <param name="Controllers"></param>
		/// <returns></returns>
		public static ControllerDefinition GetMerged(IEnumerable<ControllerDefinition> Controllers, out List<ControlDefUnMerger> Unmergers)
		{
			ControllerDefinition ret = new ControllerDefinition();
			Unmergers = new List<ControlDefUnMerger>();
			int plr = 1;
			int plrnext = 1;
			foreach (var def in Controllers)
			{
				Dictionary<string, string> remaps = new Dictionary<string, string>();

				foreach (string s in def.BoolButtons)
				{
					string r = Allocate(s, ref plr, ref plrnext);
					ret.BoolButtons.Add(r);
					remaps[s] = r;
				}
				foreach (string s in def.FloatControls)
				{
					string r = Allocate(s, ref plr, ref plrnext);
					ret.FloatControls.Add(r);
					remaps[s] = r;
				}
				ret.FloatRanges.AddRange(def.FloatRanges);
				plr = plrnext;
				Unmergers.Add(new ControlDefUnMerger(remaps));
			}
			return ret;
		}
	}

	#endregion

	#region settings

	public class NESControlSettings
	{
		static readonly Dictionary<string, Type> FamicomExpansions;
		static readonly Dictionary<string, Type> NesPortDevices;

		static Dictionary<string, Type> Implementors<T>()
		{
			var assy = typeof(NESControlSettings).Assembly;
			var types = assy.GetTypes().Where(c => typeof(T).IsAssignableFrom(c) && !c.IsAbstract && !c.IsInterface);
			var ret = new Dictionary<string, Type>();
			foreach (Type t in types)
				ret[t.Name] = t;
			return ret;
		}

		static NESControlSettings()
		{
			FamicomExpansions = Implementors<IFamicomExpansion>();
			NesPortDevices = Implementors<INesPort>();
		}

		public static IList<string> GetFamicomExpansionValues()
		{
			return new List<string>(FamicomExpansions.Keys).AsReadOnly();
		}
		public static IList<string> GetNesPortValues()
		{
			return new List<string>(NesPortDevices.Keys).AsReadOnly();
		}

		[JsonIgnore]
		private bool _Famicom;
		public bool Famicom { get { return _Famicom; } set { _Famicom = value; } }
		[JsonIgnore]
		private string _NesLeftPort;
		[JsonIgnore]
		private string _NesRightPort;
		public string NesLeftPort
		{
			get { return _NesLeftPort; }
			set
			{
				if (NesPortDevices.ContainsKey(value))
					_NesLeftPort = value;
				else
					throw new InvalidOperationException();
			}
		}
		public string NesRightPort
		{
			get { return _NesRightPort; }
			set
			{
				if (NesPortDevices.ContainsKey(value))
					_NesRightPort = value;
				else
					throw new InvalidOperationException();
			}
		}
		[JsonIgnore]
		private string _FamicomExpPort;
		public string FamicomExpPort
		{
			get { return _FamicomExpPort; }
			set
			{
				if (FamicomExpansions.ContainsKey(value))
					_FamicomExpPort = value;
				else
					throw new InvalidOperationException();
			}
		}

		public NESControlSettings()
		{
			Famicom = false;
			FamicomExpPort = typeof(UnpluggedFam).Name;
			NesLeftPort = typeof(ControllerNES).Name;
			NesRightPort = typeof(ControllerNES).Name;
		}

		public static bool NeedsReboot(NESControlSettings x, NESControlSettings y)
		{
			return !DeepEquality.DeepEquals(x, y);
		}

		public NESControlSettings Clone()
		{
			return (NESControlSettings)MemberwiseClone();
		}

		public IControllerDeck Instantiate(Func<int, int, bool> PPUCallback)
		{
			if (Famicom)
			{
				IFamicomExpansion exp = (IFamicomExpansion)Activator.CreateInstance(FamicomExpansions[FamicomExpPort]);
				IControllerDeck ret = new FamicomDeck(exp, PPUCallback);
				return ret;
			}
			else
			{
				INesPort left = (INesPort)Activator.CreateInstance(NesPortDevices[NesLeftPort]);
				INesPort right = (INesPort)Activator.CreateInstance(NesPortDevices[NesRightPort]);
				IControllerDeck ret = new NesDeck(left, right, PPUCallback);
				return ret;
			}
		}
	}

	#endregion
}
