using BizHawk.Common;

namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
	public sealed partial class Sid
	{
		private sealed class Envelope
		{
			private const int StateAttack = 0;
			private const int StateDecay = 1;
			private const int StateRelease = 2;

			private int _attack;
			private int _decay;
			private bool _delay;
			private int _envCounter;
			private int _expCounter;
			private int _expPeriod;
			private bool _freeze;
			private int _lfsr;
			private bool _gate;
			private int _rate;
			private int _release;
			private int _state;
			private int _sustain;

			private static readonly int[] AdsrTable = {
				0x7F00, 0x0006, 0x003C, 0x0330,
				0x20C0, 0x6755, 0x3800, 0x500E,
				0x1212, 0x0222, 0x1848, 0x59B8,
				0x3840, 0x77E2, 0x7625, 0x0A93
			};

			private static readonly int[] ExpCounterTable = {
				0xFF, 0x5D, 0x36, 0x1A, 0x0E, 0x06, 0x00
			};

			private static readonly int[] ExpPeriodTable = {
				0x01, 0x02, 0x04, 0x08, 0x10, 0x1E, 0x01
			};

			private static readonly int[] SustainTable = {
				0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
				0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
			};

			public Envelope()
			{
				HardReset();
			}

			public void ExecutePhase2()
			{
				if (!_delay)
				{
					_envCounter--;
					_delay = true;
					UpdateExpCounter();
				}

				if (_lfsr != _rate)
				{
					var feedback = ((_lfsr >> 14) ^ (_lfsr >> 13)) & 0x1;
					_lfsr = ((_lfsr << 1) & 0x7FFF) | feedback;
					return;
				}

				_lfsr = 0x7FFF;

				if (_state != StateAttack && ++_expCounter != _expPeriod)
				{
					return;
				}

				_expCounter = 0;
				if (_freeze)
					return;

				switch (_state)
				{
					case StateAttack:
						_envCounter++;
						if (_envCounter == 0xFF)
						{
							_state = StateDecay;
							_rate = AdsrTable[_decay];
						}
						break;
					case StateDecay:
						if (_envCounter == SustainTable[_sustain])
						{
							return;
						}
						if (_expPeriod != 1)
						{
							_delay = false;
							return;
						}
						_envCounter--;
						break;
					case StateRelease:
						if (_expPeriod != 1)
						{
							_delay = false;
							return;
						}
						_envCounter--;
						break;
				}
				_envCounter &= 0xFF;
				UpdateExpCounter();
			}

			public void HardReset()
			{
				_attack = 0;
				_decay = 0;
				_delay = true;
				_envCounter = 0;
				_expCounter = 0;
				_expPeriod = ExpPeriodTable[0];
				_freeze = false;
				_gate = false;
				_lfsr = 0x7FFF;
				_rate = AdsrTable[_release];
				_release = 0;
				_state = StateRelease;
				_sustain = 0;
			}

			private void UpdateExpCounter()
			{

				{
					for (var i = 0; i < 7; i++)
					{
						if (_envCounter == ExpCounterTable[i])
							_expPeriod = ExpPeriodTable[i];
					}
					if (_envCounter == 0)
						_freeze = true;
				}
			}

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

			public int Attack
			{
				get => _attack;
				set
				{
					_attack = value & 0xF;
					if (_state == StateAttack)
						_rate = AdsrTable[_attack];
				}
			}

			public int Decay
			{
				get => _decay;
				set
				{
					_decay = value & 0xF;
					if (_state == StateDecay)
						_rate = AdsrTable[_decay];
				}
			}

			public bool Gate
			{
				get => _gate;
				set
				{
					var nextGate = value;
					if (nextGate && !_gate)
					{
						_state = StateAttack;
						_rate = AdsrTable[_attack];
						_delay = true;
						_freeze = false;
					}
					else if (!nextGate && _gate)
					{
						_state = StateRelease;
						_rate = AdsrTable[_release];
					}
					_gate = nextGate;
				}
			}

			public int Level => _envCounter;

			public int Release
			{
				get => _release;
				set
				{
					_release = value & 0xF;
					if (_state == StateRelease)
						_rate = AdsrTable[_release];
				}
			}

			public int Sustain
			{
				get => _sustain;
				set => _sustain = value & 0xF;
			}

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

			public void SyncState(Serializer ser)
			{
				ser.Sync(nameof(_attack), ref _attack);
				ser.Sync(nameof(_decay), ref _decay);
				ser.Sync(nameof(_delay), ref _delay);
				ser.Sync(nameof(_envCounter), ref _envCounter);
				ser.Sync(nameof(_expCounter), ref _expCounter);
				ser.Sync(nameof(_expPeriod), ref _expPeriod);
				ser.Sync(nameof(_freeze), ref _freeze);
				ser.Sync(nameof(_lfsr), ref _lfsr);
				ser.Sync(nameof(_gate), ref _gate);
				ser.Sync(nameof(_rate), ref _rate);
				ser.Sync(nameof(_release), ref _release);
				ser.Sync(nameof(_state), ref _state);
				ser.Sync(nameof(_sustain), ref _sustain);
			}

			// ------------------------------------
		}
	}
}
