using System.IO;
using BizHawk.Emulation.Cores.Nintendo.NES;

namespace BizHawk.Client.Common.movie.import
{
	// FMV file format: http://tasvideos.org/FMV.html
	// ReSharper disable once UnusedMember.Global
	[ImporterFor("Famtasia", ".fmv")]
	internal class FmvImport : MovieImporter
	{
		private IControllerDeck _deck;

		protected override void RunImport()
		{
			using var fs = SourceFile.Open(FileMode.Open, FileAccess.Read);
			using var r = new BinaryReader(fs);
			// 000 4-byte signature: 46 4D 56 1A "FMV\x1A"
			var signature = new string(r.ReadChars(4));
			if (signature != "FMV\x1A")
			{
				Result.Errors.Add("This is not a valid .FMV file.");
				return;
			}

			// 004 1-byte flags:
			byte flags = r.ReadByte();

			// bit 7: 0=reset-based, 1=savestate-based
			if (((flags >> 2) & 0x1) != 0)
			{
				Result.Errors.Add("Movies that begin with a savestate are not supported.");
				return;
			}

			Result.Movie.HeaderEntries[HeaderKeys.Platform] = "NES";
			var syncSettings = new NES.NESSyncSettings();

			// other bits: unknown, set to 0
			// 005 1-byte flags:
			flags = r.ReadByte();

			// bit 5: is a FDS recording
			bool fds;
			if (((flags >> 5) & 0x1) != 0)
			{
				fds = true;
				Result.Movie.HeaderEntries[HeaderKeys.BoardName] = "FDS";
			}
			else
			{
				fds = false;
			}

			// bit 6: uses controller 2
			bool controller2 = ((flags >> 6) & 0x1) != 0;

			// bit 7: uses controller 1
			bool controller1 = ((flags >> 7) & 0x1) != 0;

			// other bits: unknown, set to 0
			// 006 4-byte little-endian unsigned int: unknown, set to 00000000
			r.ReadInt32();

			// 00A 4-byte little-endian unsigned int: rerecord count minus 1
			uint rerecordCount = r.ReadUInt32();

			/*
			 The rerecord count stored in the file is the number of times a savestate was loaded. If a savestate was never
			 loaded, the number is 0. Famtasia however displays "1" in such case. It always adds 1 to the number found in
			 the file.
			*/
			Result.Movie.Rerecords = rerecordCount + 1;

			// 00E 2-byte little-endian unsigned int: unknown, set to 0000
			r.ReadInt16();

			// 010 64-byte zero-terminated emulator identifier string
			string emuVersion = NullTerminated(new string(r.ReadChars(64)));
			Result.Movie.Comments.Add($"{EmulationOrigin} Famtasia version {emuVersion}");
			Result.Movie.Comments.Add($"{MovieOrigin} .FMV");

			// 050 64-byte zero-terminated movie title string
			string description = NullTerminated(new string(r.ReadChars(64)));
			Result.Movie.Comments.Add(description);
			if (!controller1 && !controller2 && !fds)
			{
				Result.Warnings.Add("No input recorded.");
			}

			var controllerSettings = new NESControlSettings
			{
				NesLeftPort = controller1 ? nameof(ControllerNES) : nameof(UnpluggedNES),
				NesRightPort = controller2 ? nameof(ControllerNES) : nameof(UnpluggedNES)
			};
			_deck = controllerSettings.Instantiate((x, y) => true);
			syncSettings.Controls.NesLeftPort = controllerSettings.NesLeftPort;
			syncSettings.Controls.NesRightPort = controllerSettings.NesRightPort;

			AddDeckControlButtons();

			var controllers = new SimpleController
			{
				Definition = _deck.GetDefinition()
			};

			/*
			 * 01 Right
			 * 02 Left
			 * 04 Up
			 * 08 Down
			 * 10 B
			 * 20 A
			 * 40 Select
			 * 80 Start
			*/
			string[] buttons = { "Right", "Left", "Up", "Down", "B", "A", "Select", "Start" };
			bool[] masks = { controller1, controller2, fds };
			/*
			 The file has no terminator byte or frame count. The number of frames is the <filesize minus 144> divided by
			 <number of bytes per frame>.
			*/
			int bytesPerFrame = 0;
			for (int player = 1; player <= masks.Length; player++)
			{
				if (masks[player - 1])
				{
					bytesPerFrame++;
				}
			}

			long frameCount = (fs.Length - 144) / bytesPerFrame;
			for (long frame = 1; frame <= frameCount; frame++)
			{
				/*
				 Each frame consists of 1 or more bytes. Controller 1 takes 1 byte, controller 2 takes 1 byte, and the FDS
				 data takes 1 byte. If all three exist, the frame is 3 bytes. For example, if the movie is a regular NES game
				 with only controller 1 data, a frame is 1 byte.
				*/
				for (int player = 1; player <= masks.Length; player++)
				{
					if (!masks[player - 1])
					{
						continue;
					}

					byte controllerState = r.ReadByte();
					if (player != 3)
					{
						for (int button = 0; button < buttons.Length; button++)
						{
							controllers[$"P{player} {buttons[button]}"] = ((controllerState >> button) & 0x1) != 0;
						}
					}
					else
					{
						Result.Warnings.Add("FDS commands are not properly supported.");
					}
				}

				Result.Movie.AppendFrame(controllers);
			}

			Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(syncSettings);
		}

		private void AddDeckControlButtons()
		{
			var controllers = new SimpleController
			{
				Definition = _deck.GetDefinition()
			};

			// TODO: FDS
			// Yes, this adds them to the deck definition too
			controllers.Definition.BoolButtons.Add("Reset");
			controllers.Definition.BoolButtons.Add("Power");
		}

		
	}
}
