using BizHawk.Common;

namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
	public sealed partial class Sid
	{
		private sealed class Voice
		{
			private int _accBits;
			private int _accNext;
			private int _accumulator;
			private bool _controlTestPrev;
			private int _controlWavePrev;
			private int _delay;
			private int _floatOutputTtl;
			private int _frequency;
			private bool _msbRising;
			private int _noise;
			private int _noNoise;
			private int _noNoiseOrNoise;
			private int _noPulse;
			private int _output;
			private int _pulse;
			private int _pulseWidth;
			private bool _ringMod;
			private int _ringMsbMask;
			private int _shiftRegister;
			private int _shiftRegisterReset;
			private bool _sync;
			private bool _test;
			private int[] _wave;
			private int _waveform;
			private int _waveformIndex;
			private readonly int[][] _waveTable;

			public Voice(int[][] newWaveTable)
			{
				_waveTable = newWaveTable;
				HardReset();
			}

			public void HardReset()
			{
				_accumulator = 0;
				_delay = 0;
				_floatOutputTtl = 0;
				_frequency = 0;
				_msbRising = false;
				_noNoise = 0xFFF;
				_noPulse = 0xFFF;
				_output = 0x000;
				_pulse = 0xFFF;
				_pulseWidth = 0;
				_ringMsbMask = 0;
				_sync = false;
				_test = false;
				_wave = _waveTable[0];
				_waveform = 0;

				ResetShiftReg();
			}

			public void ExecutePhase2()
			{

				{
					if (_test)
					{
						if (_shiftRegisterReset != 0 && --_shiftRegisterReset == 0)
						{
							ResetShiftReg();
						}
						_pulse = 0xFFF;
					}
					else
					{
						_accNext = (_accumulator + _frequency) & 0xFFFFFF;
						_accBits = ~_accumulator & _accNext;
						_accumulator = _accNext;
						_msbRising = (_accBits & 0x800000) != 0;

						if ((_accBits & 0x080000) != 0)
							_delay = 2;
						else if (_delay != 0 && --_delay == 0)
							ClockShiftReg();
					}
				}
			}

			// ------------------------------------

			private void ClockShiftReg()
			{

				{
					_shiftRegister = ((_shiftRegister << 1) |
						(((_shiftRegister >> 22) ^ (_shiftRegister >> 17)) & 0x1)
						) & 0x7FFFFF;
					SetNoise();
				}
			}

			private void ResetShiftReg()
			{
				_shiftRegister = 0x7FFFFF;
				_shiftRegisterReset = 0;
				SetNoise();
			}

			private void SetNoise()
			{

				{
					_noise =
						((_shiftRegister & 0x100000) >> 9) |
						((_shiftRegister & 0x040000) >> 8) |
						((_shiftRegister & 0x004000) >> 5) |
						((_shiftRegister & 0x000800) >> 3) |
						((_shiftRegister & 0x000200) >> 2) |
						((_shiftRegister & 0x000020) << 1) |
						((_shiftRegister & 0x000004) << 3) |
						((_shiftRegister & 0x000001) << 4);
					_noNoiseOrNoise = _noNoise | _noise;
				}
			}

			private void WriteShiftReg()
			{
				_output &=
					0xBB5DA |
					((_output & 0x800) << 9) |
					((_output & 0x400) << 8) |
					((_output & 0x200) << 5) |
					((_output & 0x100) << 3) |
					((_output & 0x040) >> 1) |
					((_output & 0x020) >> 3) |
					((_output & 0x010) >> 4);
				_noise &= _output;
				_noNoiseOrNoise = _noNoise | _noise;
			}

			// ------------------------------------

			public int Control
			{
				set
				{
					_controlWavePrev = _waveform;
					_controlTestPrev = _test;

					_sync = (value & 0x02) != 0;
					_ringMod = (value & 0x04) != 0;
					_test = (value & 0x08) != 0;
					_waveform = (value >> 4) & 0x0F;
					_wave = _waveTable[_waveform & 0x07];
					_ringMsbMask = ((~value >> 5) & (value >> 2) & 0x1) << 23;
					_noNoise = (_waveform & 0x8) != 0 ? 0x000 : 0xFFF;
					_noNoiseOrNoise = _noNoise | _noise;
					_noPulse = (_waveform & 0x4) != 0 ? 0x000 : 0xFFF;

					if (!_controlTestPrev && _test)
					{
						_accumulator = 0;
						_delay = 0;
						_shiftRegisterReset = 0x8000;
					}
					else if (_controlTestPrev && !_test)
					{
						_shiftRegister = ((_shiftRegister << 1) |
							((~_shiftRegister >> 17) & 0x1)
							) & 0x7FFFFF;
						SetNoise();
					}

					if (_waveform == 0 && _controlWavePrev != 0)
						_floatOutputTtl = 0x28000;
				}
			}

			public int FrequencyLo
			{
				get
				{
					return _frequency & 0xFF;
				}
				set
				{
					_frequency &= 0xFF00;
					_frequency |= value & 0x00FF;
				}
			}

			public int FrequencyHi
			{
				get
				{
					return _frequency >> 8;
				}
				set
				{
					_frequency &= 0x00FF;
					_frequency |= (value & 0x00FF) << 8;
				}
			}

			public int Output(Voice ringModSource)
			{

				{
					if (_waveform != 0)
					{
						_waveformIndex = (_accumulator ^ (ringModSource._accumulator & _ringMsbMask)) >> 12;
						_output = _wave[_waveformIndex] & (_noPulse | _pulse) & _noNoiseOrNoise;
						if (_waveform > 8)
						{
							WriteShiftReg();
						}
					}
					else
					{
						if (_floatOutputTtl != 0 && --_floatOutputTtl == 0)
						{
							_output = 0x000;
						}
					}

					_pulse = _accumulator >> 12 >= _pulseWidth ? 0xFFF : 0x000;
					return _output;
				}
			}

			public int PulseWidthLo
			{
				get
				{
					return _pulseWidth & 0xFF;
				}

				set
				{
					_pulseWidth &= 0x0F00;
					_pulseWidth |= value & 0x00FF;
				}
			}

			public int PulseWidthHi
			{
				get
				{
					return _pulseWidth >> 8;
				}

				set
				{
					_pulseWidth &= 0x00FF;
					_pulseWidth |= (value & 0x000F) << 8;
				}
			}

			public bool RingMod => _ringMod;

			public bool Sync => _sync;

			public void Synchronize(Voice target, Voice source)
			{
				if (_msbRising && target._sync && !(_sync && source._msbRising))
				{
					target._accumulator = 0;
				}
			}

			public bool Test => _test;

			public int Waveform => _waveform;

			// ------------------------------------
			public void SyncState(Serializer ser)
			{
				ser.Sync("_accBits", ref _accBits);
				ser.Sync("_accNext", ref _accNext);
				ser.Sync("_accumulator", ref _accumulator);
				ser.Sync("_controlTestPrev", ref _controlTestPrev);
				ser.Sync("_controlWavePrev", ref _controlWavePrev);
				ser.Sync("_delay", ref _delay);
				ser.Sync("_floatOutputTtl", ref _floatOutputTtl);
				ser.Sync("_frequency", ref _frequency);
				ser.Sync("_msbRising", ref _msbRising);
				ser.Sync("_noise", ref _noise);
				ser.Sync("_noNoise", ref _noNoise);
				ser.Sync("_noNoiseOrNoise", ref _noNoiseOrNoise);
				ser.Sync("_noPulse", ref _noPulse);
				ser.Sync("_output", ref _output);
				ser.Sync("_pulse", ref _pulse);
				ser.Sync("_pulseWidth", ref _pulseWidth);
				ser.Sync("_ringMod", ref _ringMod);
				ser.Sync("_ringMsbMask", ref _ringMsbMask);
				ser.Sync("_shiftRegister", ref _shiftRegister);
				ser.Sync("_shiftRegisterReset", ref _shiftRegisterReset);
				ser.Sync("_sync", ref _sync);
				ser.Sync("_test", ref _test);
				ser.Sync("_waveform", ref _waveform);
				ser.Sync("_waveformIndex", ref _waveformIndex);

				_wave = _waveTable[_waveform & 0x07];
			}
		}

	}
}
