using System;
using BizHawk.Emulation.Common;

namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{
	public partial class GambatteLink : IEmulator, IBoardInfo
	{
		public IEmulatorServiceProvider ServiceProvider { get; }

		public ControllerDefinition ControllerDefinition => DualGbController;

		public void FrameAdvance(IController controller, bool render, bool rendersound = true)
		{
			LCont.Clear();
			RCont.Clear();

			foreach (var s in DualGbController.BoolButtons)
			{
				if (controller.IsPressed(s))
				{
					if (s.Contains("P1 "))
					{
						LCont.Set(s.Replace("P1 ", ""));
					}
					else if (s.Contains("P2 "))
					{
						RCont.Set(s.Replace("P2 ", ""));
					}
				}
			}

			bool cablediscosignalNew = controller.IsPressed("Toggle Cable");
			if (cablediscosignalNew && !_cablediscosignal)
			{
				_cableconnected ^= true;
				Console.WriteLine("Cable connect status to {0}", _cableconnected);
				LinkConnected = _cableconnected;
			}

			_cablediscosignal = cablediscosignalNew;

			Frame++;
			L.FrameAdvancePrep(LCont);
			R.FrameAdvancePrep(RCont);

			unsafe
			{
				fixed (int* leftvbuff = &VideoBuffer[0])
				{
					// use pitch to have both cores write to the same video buffer, interleaved
					int* rightvbuff = leftvbuff + 160;
					const int Pitch = 160 * 2;

					fixed (short* leftsbuff = LeftBuffer, rightsbuff = RightBuffer)
					{
						const int Step = 32; // could be 1024 for GB

						int nL = _overflowL;
						int nR = _overflowR;

						// slowly step our way through the frame, while continually checking and resolving link cable status
						for (int target = 0; target < SampPerFrame;)
						{
							target += Step;
							if (target > SampPerFrame)
							{
								target = SampPerFrame; // don't run for slightly too long depending on step
							}

							// gambatte_runfor() aborts early when a frame is produced, but we don't want that, hence the while()
							while (nL < target)
							{
								uint nsamp = (uint)(target - nL);
								if (LibGambatte.gambatte_runfor(L.GambatteState, leftsbuff + (nL * 2), ref nsamp) > 0)
								{
									LibGambatte.gambatte_blitto(L.GambatteState, leftvbuff, Pitch);
								}

								nL += (int)nsamp;
							}

							while (nR < target)
							{
								uint nsamp = (uint)(target - nR);
								if (LibGambatte.gambatte_runfor(R.GambatteState, rightsbuff + (nR * 2), ref nsamp) > 0)
								{
									LibGambatte.gambatte_blitto(R.GambatteState, rightvbuff, Pitch);
								}

								nR += (int)nsamp;
							}

							// poll link cable statuses, but not when the cable is disconnected
							if (!_cableconnected)
							{
								continue;
							}

							if (LibGambatte.gambatte_linkstatus(L.GambatteState, 256) != 0) // ClockTrigger
							{
								LibGambatte.gambatte_linkstatus(L.GambatteState, 257); // ack
								int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut
								int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258);
								LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn
								LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn
							}

							if (LibGambatte.gambatte_linkstatus(R.GambatteState, 256) != 0) // ClockTrigger
							{
								LibGambatte.gambatte_linkstatus(R.GambatteState, 257); // ack
								int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut
								int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258);
								LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn
								LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn
							}
						}

						_overflowL = nL - SampPerFrame;
						_overflowR = nR - SampPerFrame;
						if (_overflowL < 0 || _overflowR < 0)
						{
							throw new Exception("Timing problem?");
						}

						if (rendersound)
						{
							PrepSound();
						}

						// copy extra samples back to beginning
						for (int i = 0; i < _overflowL * 2; i++)
						{
							LeftBuffer[i] = LeftBuffer[i + (SampPerFrame * 2)];
						}

						for (int i = 0; i < _overflowR * 2; i++)
						{
							RightBuffer[i] = RightBuffer[i + (SampPerFrame * 2)];
						}
					}
				}
			}

			L.FrameAdvancePost();
			R.FrameAdvancePost();
			IsLagFrame = L.IsLagFrame && R.IsLagFrame;
			if (IsLagFrame)
			{
				LagCount++;
			}
		}

		public int Frame { get; private set; }

		public string SystemId => "DGB";

		public bool DeterministicEmulation => L.DeterministicEmulation && R.DeterministicEmulation;

		public string BoardName => L.BoardName + '|' + R.BoardName;

		public void ResetCounters()
		{
			Frame = 0;
			LagCount = 0;
			IsLagFrame = false;
		}

		public CoreComm CoreComm { get; }

		public void Dispose()
		{
			if (!_disposed)
			{
				L.Dispose();
				L = null;

				R.Dispose();
				R = null;

				_blipLeft.Dispose();
				_blipLeft = null;

				_blipRight.Dispose();
				_blipRight = null;

				_disposed = true;
			}
		}
	}
}
