using BizHawk.Common;
using BizHawk.Common.NumberExtensions;

namespace BizHawk.Emulation.Cores.Components
{
	public sealed class SN76489sms
	{
		public int current_sample_L;
		public int current_sample_R;

		public SN76489sms()
		{
			Reset();
		}

		public byte[] Chan_vol = new byte[4];
		public ushort[] Chan_tone = new ushort[4];

		public int chan_sel;
		public bool vol_tone;
		public bool noise_type;
		public int noise_rate;
		public bool noise_bit;

		public bool A_L, B_L, C_L, noise_L;
		public bool A_R, B_R, C_R, noise_R;

		private int psg_clock;

		private int clock_A, clock_B, clock_C;

		private bool A_up, B_up, C_up;

		private int noise_clock;
		private int noise;

		public byte stereo_panning;
		private static readonly byte[] LogScale = { 255, 203, 161, 128, 102, 86, 64, 51, 40, 32, 26, 20, 16, 13, 10, 0 };

		public void Reset()
		{
			clock_A = clock_B = clock_C = 0x1000;
			noise_clock = 0x10;
			chan_sel = 0;

			// reset the shift register
			noise = 0x40000;

			Chan_vol[0] = 0xF;
			Chan_vol[1] = 0xF;
			Chan_vol[2] = 0xF;
			Chan_vol[3] = 0xF;

			Set_Panning(0xFF);
		}

		public void Set_Panning(byte value)
		{
			A_L = (value & 0x10) != 0;
			A_R = (value & 0x01) != 0;
			B_L = (value & 0x20) != 0;
			B_R = (value & 0x02) != 0;
			C_L = (value & 0x40) != 0;
			C_R = (value & 0x04) != 0;
			noise_L = (value & 0x80) != 0;
			noise_R = (value & 0x08) != 0;

			stereo_panning = value;
		}

		public void SyncState(Serializer ser)
		{
			ser.BeginSection("SN76489");

			ser.Sync(nameof(Chan_vol), ref Chan_vol, false);
			ser.Sync(nameof(Chan_tone), ref Chan_tone, false);

			ser.Sync(nameof(chan_sel), ref chan_sel);
			ser.Sync(nameof(vol_tone), ref vol_tone);
			ser.Sync(nameof(noise_type), ref noise_type);
			ser.Sync(nameof(noise_rate), ref noise_rate);

			ser.Sync(nameof(clock_A), ref clock_A);
			ser.Sync(nameof(clock_B), ref clock_B);
			ser.Sync(nameof(clock_C), ref clock_C);
			ser.Sync(nameof(noise_clock), ref noise_clock);
			ser.Sync(nameof(noise_bit), ref noise_bit);

			ser.Sync(nameof(psg_clock), ref psg_clock);

			ser.Sync(nameof(A_up), ref A_up);
			ser.Sync(nameof(B_up), ref B_up);
			ser.Sync(nameof(C_up), ref C_up);
			ser.Sync(nameof(noise), ref noise);

			ser.Sync(nameof(A_L), ref A_L);
			ser.Sync(nameof(B_L), ref B_L);
			ser.Sync(nameof(C_L), ref C_L);
			ser.Sync(nameof(noise_L), ref noise_L);
			ser.Sync(nameof(A_R), ref A_R);
			ser.Sync(nameof(B_R), ref B_R);
			ser.Sync(nameof(C_R), ref C_R);
			ser.Sync(nameof(noise_R), ref noise_R);

			ser.Sync(nameof(current_sample_L), ref current_sample_L);
			ser.Sync(nameof(current_sample_R), ref current_sample_R);
			ser.Sync(nameof(stereo_panning), ref stereo_panning);

			ser.EndSection();
		}

		public byte ReadReg()
		{
			// not used, reading not allowed, just return 0xFF
			return 0xFF;
		}

		public void WriteReg(byte value)
		{
			// if bit 7 is set, change the latch, otherwise modify the currently latched register
			if (value.Bit(7))
			{
				chan_sel = (value >> 5) & 3;
				vol_tone = value.Bit(4);

				if (vol_tone)
				{
					Chan_vol[chan_sel] = (byte)(value & 0xF);
				}
				else
				{
					if (chan_sel < 3)
					{
						Chan_tone[chan_sel] &= 0x3F0;
						Chan_tone[chan_sel] |= (ushort)(value & 0xF);
					}
					else
					{
						noise_type = value.Bit(2);
						noise_rate = value & 3;

						// reset the shift register
						noise = 0x40000;
					}
				}
			}
			else
			{
				if (vol_tone)
				{
					Chan_vol[chan_sel] = (byte)(value & 0xF);
				}
				else
				{
					if (chan_sel < 3)
					{
						Chan_tone[chan_sel] &= 0xF;
						Chan_tone[chan_sel] |= (ushort)((value & 0x3F) << 4);
					}
					else
					{
						noise_type = value.Bit(2);
						noise_rate = value & 3;

						// reset the shift register
						noise = 0x40000;
					}
				}
			}
		}

		public void generate_sound()
		{
			// there are 16 cpu cycles for every psg cycle
			psg_clock++;

			if (psg_clock == 16)
			{
				psg_clock = 0;

				clock_A--;
				clock_B--;
				clock_C--;
				noise_clock--;

				// clock noise
				if (noise_clock == 0)
				{
					noise_bit = noise.Bit(0);
					if (noise_type)
					{
						noise = (((noise & 1) ^ ((noise >> 1) & 1)) << 14) | (noise >> 1);						
					}
					else
					{
						noise = ((noise & 1) << 14) | (noise >> 1);
					}

					if (noise_rate == 0)
					{
						noise_clock = 0x10;
					}
					else if (noise_rate == 1)
					{
						noise_clock = 0x20;
					}
					else if (noise_rate == 2)
					{
						noise_clock = 0x40;
					}
					else
					{
						noise_clock = Chan_tone[2] + 1;
					}

					noise_clock *= 2;					
				}
					
				if (clock_A == 0)
				{
					A_up = !A_up;
					clock_A = Chan_tone[0] + 1;
				}

				if (clock_B == 0)
				{
					B_up = !B_up;
					clock_B = Chan_tone[1] + 1;
				}

				if (clock_C == 0)
				{
					C_up = !C_up;
					clock_C = Chan_tone[2] + 1;
				}

				// now calculate the volume of each channel and add them together
				current_sample_L = (A_L ? (A_up ? LogScale[Chan_vol[0]] * 42 : 0) : 0);

				current_sample_L += (B_L ? (B_up ? LogScale[Chan_vol[1]] * 42 : 0) : 0);

				current_sample_L += (C_L ? (C_up ? LogScale[Chan_vol[2]] * 42 : 0) : 0);

				current_sample_L += (noise_L ? (noise_bit ? LogScale[Chan_vol[3]] * 42 : 0) : 0);

				current_sample_R = (A_R ? (A_up ? LogScale[Chan_vol[0]] * 42 : 0) : 0);

				current_sample_R += (B_R ? (B_up ? LogScale[Chan_vol[1]] * 42 : 0) : 0);

				current_sample_R += (C_R ? (C_up ? LogScale[Chan_vol[2]] * 42 : 0) : 0);

				current_sample_R += (noise_R ? (noise_bit ? LogScale[Chan_vol[3]] * 42 : 0) : 0);
			}
		}
	}
}