using BizHawk.Emulation.Cores.Computers.Commodore64.Media;

namespace BizHawk.Emulation.Cores.Computers.Commodore64.Serial
{
	public sealed partial class Drive1541
	{
		private const long LEHMER_RNG_PRIME = 48271;

		private int _diskDensityCounter; // density .. 16
		private int _diskSupplementaryCounter; // 0 .. 16
		private bool _diskFluxReversalDetected;
		private int _diskBitsLeft;
		private int _diskByteOffset;
		private int _diskBits;
		private int _diskCycle;
		private int _diskDensity;
		private bool _previousCa1;
		private int _countsBeforeRandomTransition;
		private int _rngCurrent;
		private int _clocks;
		private int _cpuClocks;
		private int _diskWriteBitsRemaining;
		private bool _diskWriteEnabled;
		private int _diskWriteLatch;
		private int _diskOutputBits;
		private bool _diskWriteProtected;

		// Lehmer RNG
		private void AdvanceRng()
		{
			if (_rngCurrent == 0)
			{
				_rngCurrent = 1;
			}

			_rngCurrent = unchecked((int) ((_rngCurrent * LEHMER_RNG_PRIME) & int.MaxValue));
		}

		private void ExecuteFlux()
		{
			// This actually executes the main 16mhz clock
			while (_clocks > 0)
			{
				_clocks--;

				// rotate disk
				if (_motorEnabled)
				{
					if (_disk == null)
					{
						_diskBitsLeft = 1;
						_diskBits = 0;
					}
					else
					{
						if (_diskBitsLeft <= 0)
						{
							if (_diskWriteEnabled)
								_trackImageData[_diskByteOffset] = _diskOutputBits;

							_diskByteOffset++;

							if (_diskByteOffset == Disk.FluxEntriesPerTrack)
								_diskByteOffset = 0;

							if (!_diskWriteEnabled)
								_diskBits = _trackImageData[_diskByteOffset];

							_diskOutputBits = 0;
							_diskBitsLeft = Disk.FluxBitsPerEntry;
						}
					}
					_diskOutputBits >>= 1;

					if (_diskWriteEnabled && !_diskWriteProtected)
						_countsBeforeRandomTransition = 0;

					if ((_diskBits & 1) != 0)
					{
						_countsBeforeRandomTransition = 0;
						_diskFluxReversalDetected = true;
						_diskOutputBits |= int.MinValue; // set bit 31
					}
					else
					{
						_diskOutputBits &= int.MaxValue; // clear bit 31
					}

					_diskBits >>= 1;
					_diskBitsLeft--;
				}

				// random flux transition readings for unformatted data
				if (_countsBeforeRandomTransition > 0)
				{
					_countsBeforeRandomTransition--;
					if (_countsBeforeRandomTransition == 0)
					{
						_diskFluxReversalDetected = true;
						AdvanceRng();
						// This constant is what VICE uses. TODO: Determine accuracy.
						_countsBeforeRandomTransition = (_rngCurrent % 367) + 33;
					}
				}

				// flux transition circuitry
				if (_diskFluxReversalDetected)
				{
					if (!_diskWriteEnabled)
					{
						_diskDensityCounter = _diskDensity;
						_diskSupplementaryCounter = 0;
					}
					_diskFluxReversalDetected = false;
					if (_countsBeforeRandomTransition == 0)
					{
						AdvanceRng();
						// This constant is what VICE uses. TODO: Determine accuracy.
						_countsBeforeRandomTransition = (_rngCurrent & 0x1F) + 289;
					}
				}

				// counter circuitry
				if (_diskDensityCounter >= 16)
				{
					_diskDensityCounter = _diskDensity;
					_diskSupplementaryCounter++;

					if ((_diskSupplementaryCounter & 0x3) == 0x2)
					{
						if (!_diskWriteEnabled)
							_diskWriteBitsRemaining = 0;
						_diskWriteEnabled = !Via1.Cb2;

						_diskWriteBitsRemaining--;
						if (_diskWriteEnabled)
						{
							_countsBeforeRandomTransition = 0;
							_byteReady = false;
							if (_diskWriteBitsRemaining <= 0)
							{
								_diskWriteLatch = Via1.EffectivePrA;
								_diskWriteBitsRemaining = 8;
								_byteReady = Via1.Ca2;
							}
							if ((_diskWriteLatch & 0x80) != 0)
							{
								_diskOutputBits |= int.MinValue; // set bit 31
							}
							_diskWriteLatch <<= 1;
						}
						else
						{
							_bitsRemainingInLatchedByte--;
							_byteReady = false;
							_bitHistory = (_bitHistory << 1) | ((_diskSupplementaryCounter & 0xC) == 0x0 ? 1 : 0);
							_sync = false;
							if (!_diskWriteEnabled && (_bitHistory & 0x3FF) == 0x3FF)
							{
								_sync = true;
								_bitsRemainingInLatchedByte = 8;
								_byteReady = false;
							}

							if (_bitsRemainingInLatchedByte <= 0)
							{
								_bitsRemainingInLatchedByte = 8;

								// SOE (SO/Byte Ready enabled)
								_byteReady = Via1.Ca2;
							}
						}
					}

					// negative transition activates SO pin on CPU
					_previousCa1 = Via1.Ca1;
					Via1.Ca1 = !_byteReady;
					if (_previousCa1 && !Via1.Ca1)
					{
						// cycle 6 is roughly 400ns
						_overflowFlagDelaySr |= _diskCycle > 6 ? 4 : 2;
					}
				}

				if (_diskSupplementaryCounter >= 16)
				{
					_diskSupplementaryCounter = 0;
				}

				_cpuClocks--;
				if (_cpuClocks <= 0)
				{
					ExecuteSystem();
					_cpuClocks = 16;
				}

				_diskDensityCounter++;
				_diskCycle = (_diskCycle + 1) & 0xF;
			}
		}
	}
}
