using BizHawk.Common;

namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
	public partial class TIA
	{
		public class Audio
		{
			// noise/division control
			public byte AUDC_L = 0;
			public byte AUDC_R = 0;

			// frequency divider
			public byte AUDF_L = 1;
			public byte AUDF_R = 1;

			// volume
			public byte AUDV_L = 0;
			public byte AUDV_R = 0;

			// 2 state counter
			private bool sr1_L = true;
			private bool sr1_R = true;

			// 4 bit shift register
			private int sr4_L = 0x0f;
			private int sr4_R = 0x0f;

			// 5 bit shift register
			private int sr5_L = 0x1f;
			private int sr5_R = 0x1f;

			// 9 bit shift register
			private int sr9_L = 0x1ff;
			private int sr9_R = 0x1ff;

			// 3 state counter
			private int sr3_L = 2;
			private int sr3_R = 2;

			// counter based off AUDF
			private byte freqcnt_L;
			private byte freqcnt_R;

			// latched audio value
			private bool on_L = true;
			private bool on_R = true;

			private bool Run3_L()
			{
				sr3_L++;
				if (sr3_L == 3)
				{
					sr3_L = 0;
					return true;
				}

				return false;
			}

			private bool Run4_L()
			{
				bool ret = (sr4_L & 1) != 0;
				bool c = ((sr4_L & 1) != 0) ^ ((sr4_L & 2) != 0);
				sr4_L = (sr4_L >> 1) | (c ? 8 : 0);
				return ret;
			}

			private bool Run5_L()
			{
				bool ret = (sr5_L & 1) != 0;
				bool c = ((sr5_L & 1) != 0) ^ ((sr5_L & 4) != 0);
				sr5_L = (sr5_L >> 1) | (c ? 16 : 0);
				return ret;
			}

			private bool One4_L()
			{
				bool ret = (sr4_L & 1) != 0;
				sr4_L = (sr4_L >> 1) | 8;
				return ret;
			}

			private bool One5_L()
			{
				bool ret = (sr5_L & 1) != 0;
				sr5_L = (sr5_L >> 1) | 16;
				return ret;
			}

			private bool Run1_L()
			{
				sr1_L = !sr1_L;
				return !sr1_L;
			}

			private bool Run9_L()
			{
				bool ret = (sr9_L & 1) != 0;
				bool c = ((sr9_L & 1) != 0) ^ ((sr9_L & 16) != 0);
				sr9_L = (sr9_L >> 1) | (c ? 256 : 0);
				return ret;
			}

			/// <summary>
			/// call me approx 31k times a second
			/// </summary>
			/// <returns>16 bit audio sample</returns>
			public short Cycle_L()
			{
				if (++freqcnt_L >= AUDF_L)
				{
					freqcnt_L = 0;
					switch (AUDC_L)
					{
						case 0x00:
						case 0x0b:
							// Both have a 1 s
							One5_L();
							on_L = One4_L();
							break;
						case 0x01:
							// Both run, but the 5 bit is ignored
							on_L = Run4_L();
							Run5_L();
							break;
						case 0x02:
							if (((sr5_L & 0x0f) == 0) || ((sr5_L & 0x0f) == 0x0f))
							{
								on_L = Run4_L();
								Run5_L();
							}
							Run5_L();
							break;
						case 0x03:
							if (Run5_L())
							{
								on_L = Run4_L();
							}
							break;
						case 0x04:
							Run5_L();
							One4_L();
							on_L = Run1_L();
							break;
						case 0x05:
							One5_L();
							Run4_L();
							on_L = Run1_L();
							break;			
						case 0x06:
						case 0x0a:
							Run5_L();
							if ((sr5_L & 0x0f) == 0)
							{
								on_L = false;
							}
							else if ((sr5_L & 0x0f) == 0x0f)
							{
								on_L = true;
							}
							break;
						case 0x07:
						case 0x09:
							on_L = Run5_L();
							break;					
						case 0x08:
							on_L = Run9_L();
							break;
						case 0x0c:
						case 0x0d:
							if (Run3_L())
							{
								on_L = Run1_L();
							}
							break;
						case 0x0e:
							if (Run3_L())
							{
								goto case 0x06;
							}
							break;
						case 0x0f:
							// poly5 output to div 6
							if (Run5_L())
							{
								if (Run3_L())
								{
									on_L = Run1_L();
								}
							}
							break;
					}
				}

				return (short)(on_L ? AUDV_L * 1092 : 0);
			}

			private bool Run3_R()
			{
				sr3_R++;
				if (sr3_R == 3)
				{
					sr3_R = 0;
					return true;
				}

				return false;
			}

			private bool Run4_R()
			{
				bool ret = (sr4_R & 1) != 0;
				bool c = ((sr4_R & 1) != 0) ^ ((sr4_R & 2) != 0);
				sr4_R = (sr4_R >> 1) | (c ? 8 : 0);
				return ret;
			}

			private bool Run5_R()
			{
				bool ret = (sr5_R & 1) != 0;
				bool c = ((sr5_R & 1) != 0) ^ ((sr5_R & 4) != 0);
				sr5_R = (sr5_R >> 1) | (c ? 16 : 0);
				return ret;
			}

			private bool One4_R()
			{
				bool ret = (sr4_R & 1) != 0;
				sr4_R = (sr4_R >> 1) | 8;
				return ret;
			}

			private bool One5_R()
			{
				bool ret = (sr5_R & 1) != 0;
				sr5_R = (sr5_R >> 1) | 16;
				return ret;
			}

			private bool Run1_R()
			{
				sr1_R = !sr1_R;
				return !sr1_R;
			}

			private bool Run9_R()
			{
				bool ret = (sr9_R & 1) != 0;
				bool c = ((sr9_R & 1) != 0) ^ ((sr9_R & 16) != 0);
				sr9_R = (sr9_R >> 1) | (c ? 256 : 0);
				return ret;
			}

			/// <summary>
			/// call me approx 31k times a second
			/// </summary>
			/// <returns>16 bit audio sample</returns>
			public short Cycle_R()
			{
				if (++freqcnt_R >= AUDF_R)
				{
					freqcnt_R = 0;
					switch (AUDC_R)
					{
						case 0x00:
						case 0x0b:
							// Both have a 1 s
							One5_R();
							on_R = One4_R();
							break;
						case 0x01:
							// Both run, but the 5 bit is ignored
							on_R = Run4_R();
							Run5_R();
							break;
						case 0x02:
							if (((sr5_R & 0x0f) == 0) || ((sr5_R & 0x0f) == 0x0f))
							{
								on_R = Run4_R();
								Run5_R();
							}
							Run5_R();
							break;
						case 0x03:
							if (Run5_R())
							{
								on_R = Run4_R();
							}
							break;
						case 0x04:
							Run5_R();
							One4_R();
							on_R = Run1_R();
							break;
						case 0x05:
							One5_R();
							Run4_R();
							on_R = Run1_R();
							break;
						case 0x06:
						case 0x0a:
							Run5_R();
							if ((sr5_R & 0x0f) == 0)
							{
								on_R = false;
							}
							else if ((sr5_R & 0x0f) == 0x0f)
							{
								on_R = true;
							}

							break;
						case 0x07:
						case 0x09:
							on_R = Run5_R();
							break;
						case 0x08:
							on_R = Run9_R();
							break;
						case 0x0c:
						case 0x0d:
							if (Run3_R())
							{
								on_R = Run1_R();
							}
							break;
						case 0x0e:
							if (Run3_R())
							{
								goto case 0x06;
							}
							break;
						case 0x0f:
							// poly5 output to div 6
							if (Run5_R())
							{
								if (Run3_R())
								{
									on_R = Run1_R();
								}
							}
							break;
					}
				}

				return (short)(on_R ? AUDV_R * 1092 : 0);
			}

			public void SyncState(Serializer ser)
			{
				ser.Sync(nameof(AUDC_L), ref AUDC_L);
				ser.Sync(nameof(AUDF_L), ref AUDF_L);
				ser.Sync(nameof(AUDV_L), ref AUDV_L);
				ser.Sync(nameof(sr1_L), ref sr1_L);
				ser.Sync(nameof(sr3_L), ref sr3_L);
				ser.Sync(nameof(sr4_L), ref sr4_L);
				ser.Sync(nameof(sr5_L), ref sr5_L);
				ser.Sync(nameof(sr9_L), ref sr9_L);
				ser.Sync(nameof(freqcnt_L), ref freqcnt_L);
				ser.Sync(nameof(on_L), ref on_L);

				ser.Sync(nameof(AUDC_R), ref AUDC_R);
				ser.Sync(nameof(AUDF_R), ref AUDF_R);
				ser.Sync(nameof(AUDV_R), ref AUDV_R);
				ser.Sync(nameof(sr1_R), ref sr1_R);
				ser.Sync(nameof(sr3_R), ref sr3_R);
				ser.Sync(nameof(sr4_R), ref sr4_R);
				ser.Sync(nameof(sr5_R), ref sr5_R);
				ser.Sync(nameof(sr9_R), ref sr9_R);
				ser.Sync(nameof(freqcnt_R), ref freqcnt_R);
				ser.Sync(nameof(on_R), ref on_R);
			}
		}
	}
}
