using System;
using System.Collections.Generic;

using BizHawk.Common;
using BizHawk.Emulation.Common;

//http://wiki.nesdev.com/w/index.php/APU_Mixer_Emulation
//http://wiki.nesdev.com/w/index.php/APU
//http://wiki.nesdev.com/w/index.php/APU_Pulse
//sequencer ref: http://wiki.nesdev.com/w/index.php/APU_Frame_Counter

//TODO - refactor length counter to be separate component

namespace BizHawk.Emulation.Cores.Nintendo.NES
{
	public class BisqAPU : ISoundProvider
	{
		class RegBitSetStore
		{
			public RegBitSetStore(int numUints)
			{
				data = new uint[numUints];
			}
			public uint[] data;
		}

		class RegBitSet
		{
			RegBitSetStore store;
			int mask;
			int bitno, nbits, dim, index;
			public RegBitSet(RegBitSetStore store, int bitno, int nbits, int dim = 1, int index = 0)
			{
				this.store = store;
				this.bitno = bitno;
				this.nbits = nbits;
				this.dim = dim;
				this.index = index;
				mask = ((1 << (nbits / 2)) << (nbits - (nbits / 2))) - 1;
			}

			public static implicit operator int(RegBitSet rhs) { return (int)rhs.get(); }
			public static implicit operator uint(RegBitSet rhs) { return rhs.get(); }
			public static explicit operator bool(RegBitSet rhs) { return rhs.get() != 0; }

			public uint Value { get { return get(); } set { set(value); } }

			public uint this[int offset]
			{
				get
				{
					return get(offset);
				}
				set
				{
					set(value, offset);
				}
			}

			public uint get(int offset = 0)
			{
				return (uint)((store.data[index + offset] >> bitno) & mask);
			}

			public void set(uint val, int offset = 0)
			{
				long temp = (store.data[index + offset] & ~(mask << bitno))
					| ((nbits > 1 ? val & mask : ((val != 0) ? 1 : 0)) << bitno);
				store.data[index + offset] = (uint)temp;
			}

			public static uint operator ^(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() ^ rhs)); }
			public static uint operator |(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() | rhs)); }
			public static uint operator &(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() & rhs)); }
			public static uint operator +(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() + rhs)); }
			public static uint operator -(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() - rhs)); }
		};

		class SignedRegBitSet
		{
			RegBitSetStore store;
			int mask;
			int bitno, nbits, dim, index;
			public SignedRegBitSet(RegBitSetStore store, int bitno, int nbits, int dim = 1, int index = 0)
			{
				this.store = store;
				this.bitno = bitno;
				this.nbits = nbits;
				this.dim = dim;
				this.index = index;
				mask = ((1 << (nbits / 2)) << (nbits - (nbits / 2))) - 1;
			}

			public static implicit operator int(SignedRegBitSet rhs) { return rhs.get(); }
			public static implicit operator uint(SignedRegBitSet rhs) { return (uint)rhs.get(); }
			public static explicit operator bool(SignedRegBitSet rhs) { return rhs.get() != 0; }

			public int Value { get { return get(); } set { set(value); } }

			public int this[int offset]
			{
				get
				{
					return get(offset);
				}
				set
				{
					set(value, offset);
				}
			}

			public int get(int offset = 0)
			{
				return (int)((store.data[index + offset] >> bitno) & mask);
			}

			public void set(int val, int offset = 0)
			{
				long temp = ((int)store.data[index + offset] & (int)~(mask << bitno))
					| ((nbits > 1 ? val & mask : ((val != 0) ? 1 : 0)) << bitno);
				store.data[index + offset] = (uint)temp;
			}

			public static int operator ^(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() ^ rhs)); }
			public static int operator |(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() | rhs)); }
			public static int operator &(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() & rhs)); }
			public static int operator +(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() + rhs)); }
			public static int operator -(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() - rhs)); }

		};


		public static bool CFG_USE_METASPU = true;

		class APUdata
		{
			public APUdata()
			{
				bs = new RegBitSetStore(5);
				ChannelsEnabled = new RegBitSet(bs, 15, 1, 5, 0);
				frame_delay = new RegBitSet(bs, 0, 15, 5, 0);
				frame = new RegBitSet(bs, 0, 8, 5, 1);
				FiveCycleDivider = new RegBitSet(bs, 8, 1, 5, 1);
				IRQdisable = new RegBitSet(bs, 9, 1, 5, 1);
				DMC_CycleCost = new RegBitSet(bs, 10, 3, 5, 1);
				IRQ_delay = new RegBitSet(bs, 0, 15, 5, 2);
			}
			public RegBitSetStore bs;
			public RegBitSet ChannelsEnabled;
			public RegBitSet frame_delay;
			public RegBitSet frame;
			public RegBitSet FiveCycleDivider;
			public RegBitSet IRQdisable;
			public RegBitSet DMC_CycleCost;
			public RegBitSet IRQ_delay;
		}
		APUdata data = new APUdata();

		class APUchannel
		{
			public APUchannel()
			{
				bs = new RegBitSetStore(11);
					
				reg0 = new RegBitSet(bs,0, 8, 11, 0);
				DutyCycle = new RegBitSet(bs,6, 2, 11, 0);
				EnvDecayDisable = new RegBitSet(bs,4, 1, 11, 0);
				EnvDecayRate = new RegBitSet(bs,0, 4, 11, 0);
				EnvDecayLoopEnable = new RegBitSet(bs,5, 1, 11, 0);
				FixedVolume = new RegBitSet(bs,0, 4, 11, 0);
				LengthCounterDisable = new RegBitSet(bs,5, 1, 11, 0);
				LinearCounterInit = new RegBitSet(bs,0, 7, 11, 0);
				LinearCounterDisable = new RegBitSet(bs,7, 1, 11, 0);
					
				reg1 = new RegBitSet(bs,8, 8, 11, 0);
				SweepShift = new RegBitSet(bs,8, 3, 11, 0);
				SweepDecrease = new RegBitSet(bs,11, 1, 11, 0);
				SweepRate = new RegBitSet(bs,12, 3, 11, 0);
				SweepEnable = new RegBitSet(bs,15, 1, 11, 0);
				PCMlength = new RegBitSet(bs,8, 8, 11, 0);
					
				reg2 = new RegBitSet(bs,16, 8, 11, 0);
				NoiseFreq = new RegBitSet(bs,16, 4, 11, 0);
				NoiseType = new RegBitSet(bs,23, 1, 11, 0);
				WaveLength = new RegBitSet(bs,16, 11, 11, 0);
					
				reg3 = new RegBitSet(bs,24, 8, 11, 0);
				LengthCounterInit = new RegBitSet(bs,27, 5, 11, 0);
				LoopEnabled = new RegBitSet(bs,30, 1, 11, 0);
				IRQenable = new RegBitSet(bs,31, 1, 11, 0);
					
				length_counter = new SignedRegBitSet(bs,0, 32, 11, 1);
				linear_counter = new SignedRegBitSet(bs,0, 32, 11, 2);
				address = new SignedRegBitSet(bs,0, 32, 11, 3);
				envelope = new SignedRegBitSet(bs,0, 32, 11, 4);
				sweep_delay = new SignedRegBitSet(bs,0, 32, 11, 5);
				env_delay = new SignedRegBitSet(bs,0, 32, 11, 6);
				wave_counter = new SignedRegBitSet(bs,0, 32, 11, 7);
				hold = new SignedRegBitSet(bs,0, 32, 11, 8);
				phase = new SignedRegBitSet(bs,0, 32, 11, 9);
				level = new SignedRegBitSet(bs,0, 32, 11, 10);
			}
			public RegBitSetStore bs;
			// 4000, 4004, 400C, 4012:
			public RegBitSet reg0, DutyCycle, EnvDecayDisable, EnvDecayRate, EnvDecayLoopEnable, FixedVolume, LengthCounterDisable, LinearCounterInit, LinearCounterDisable;
			// 4001, 4005, 4013:  
			public RegBitSet reg1, SweepShift, SweepDecrease, SweepRate, SweepEnable, PCMlength;
			// 4002, 4006, 400A, 400E:            
			public RegBitSet reg2, NoiseFreq, NoiseType, WaveLength;
			// 4003, 4007, 400B, 400F, 4010:
			public RegBitSet reg3, LengthCounterInit, LoopEnabled, IRQenable;
			// Internals:
			public SignedRegBitSet length_counter, linear_counter, address, envelope, sweep_delay, env_delay, wave_counter, hold, phase, level;
		}

		APUchannel[] channels = new APUchannel[5] { new APUchannel(), new APUchannel(), new APUchannel(), new APUchannel(), new APUchannel() };

		static readonly byte[] LengthCounters = new byte[32]
		{ 10,254,20, 2,40, 4,80, 6,  160, 8,60,10,14,12,26,14,
			12, 16,24,18,48,20,96,22,  192,24,72,26,16,28,32,30 };

		static readonly ushort[] NoisePeriods = new ushort[16]
		{ 2,4,8,16,32,48,64,80,101,127,190,254,381,508,1017,2034 };

		static readonly ushort[] DMCperiods = new ushort[16]
		{ 428,380,340,320,  286,254,226,214,  190,160,142,128,  106,84,72,54 };
		/*
		For PAL:
		static const u16 DMCperiods[16] =
		{ 0x18E,0x162,0x13C,0x12A, 0x114,0x0EC,0x0D2,0x0C6,
			0x0B0,0x094,0x084,0x076, 0x062,0x04E,0x042,0x032 };
		*/

		const int frame_period = 7458;

		// Utility function for sound
		bool count(ref int v, int reset)
		{
			if (--v < 0)
			{
				v = reset;
				return true;
			}
			else return false;
		}

		bool count(ref int v, int reset, int n_at_time)
		{
			if ((v -= n_at_time) < 0)
			{
				v += reset;
				return true;
			}
			else return false;
		}


		int tick(int c)
		{
			APUchannel ch = channels[c];
			int wl = ch.WaveLength;
			if (c != 4) ++wl;
			if (c < 2) wl *= 2;

			if (c == 3) wl = NoisePeriods[ch.NoiseFreq];

			//if(c != 4) wl = wl * (IO::UISpeed);
			// ^ Match to the UI speed (but don't for DPCM, because it would skew the timings)

			int volume = (bool)ch.length_counter ? (bool)ch.EnvDecayDisable ? (int)ch.FixedVolume : ch.envelope : 0;
			// Sample may change at wavelen intervals.
			int ref_S = ch.level;
			int ref_ch_wave_counter = ch.wave_counter;
			if (!(data.ChannelsEnabled[c] != 0)
			|| !count(ref ref_ch_wave_counter, wl))
			{
				ch.wave_counter.Value = ref_ch_wave_counter;
				return ref_S;
			}
			ch.wave_counter.Value = ref_ch_wave_counter;
			switch (c)
			{
				case 0:
				default:
				case 1: // Square wave. With four different 8-step binary waveforms (32 bits of data total).
					ch.phase.Value++;
					if (wl < 8) return ref_S;
					if ((bool)ch.SweepEnable && !(bool)ch.SweepDecrease)
						if (wl + (wl >> ch.SweepShift) >= 0x800)
							return ref_S;
					return ref_S = ch.level.Value = (0xF33C0C04u & (1u << (ch.phase % 8 + ch.DutyCycle * 8))) != 0 ? volume : 0;
				case 2: // Triangle wave
					if ((bool)ch.length_counter && (bool)ch.linear_counter && wl >= 3) ++ch.phase.Value;
					return ref_S = ch.level.Value = (ch.phase & 15) ^ (((ch.phase & 16) != 0) ? 15 : 0);

				case 3: // Noise: Linear feedback shift register
					if (!(bool)ch.hold) ch.hold.Value = 1;
					ch.hold.Value = (ch.hold >> 1)
								| (((ch.hold ^ (ch.hold >> ((bool)ch.NoiseType ? 6 : 1))) & 1) << 14);
					return ref_S = ch.level.Value = ((ch.hold & 1) != 0) ? 0 : volume;

				case 4: // Delta modulation channel (DMC)
					// hold           = 8 bit value
					// phase          = number of bits buffered
					// length_counter = 
					if (wl == 0) return ref_S;
					if (ch.phase == 0) // Nothing in sample buffer?
					{
						if (!(bool)ch.length_counter && (bool)ch.LoopEnabled) // Loop?
						{
							ch.length_counter.Value = ch.PCMlength * 16 + 1;
							ch.address.Value = (int)((ch.reg0 | 0x300) << 6);
						}
						if (ch.length_counter > 0) // Load next 8 bits if available
						{
							//==========================TODO============== DMC COST=====================
							//for(unsigned t = data.DMC_CycleCost; t > 1; --t)
							//      CPU::RB(u16(ch.address) | 0x8000); // timing
							ch.hold.Value = nes.ReadMemory((ushort)((ch.address.Value++) | 0x8000)); // Fetch byte
							ch.phase.Value = 8;
							--ch.length_counter.Value;
						}
						else // Otherwise, disable channel or issue IRQ
						{
							if ((bool)ch.IRQenable)
							{
								//CPU::reg.APU_DMC_IRQ = true;
								dmc_irq = true;
								SyncIRQ();
							}

							data.ChannelsEnabled[4] = 0;
						}
					}
					if (ch.phase != 0) // Update the signal if sample buffer nonempty
					{
						int v = ch.linear_counter;
						if (((ch.hold << --ch.phase.Value) & 0x80)!=0) v += 2; else v -= 2;
						if (v >= 0 && v <= 0x7F) ch.linear_counter.Value = v;
					}
					return ref_S = ch.level = ch.linear_counter;
			}
		}

		bool dmc_irq;
		bool sequencer_irq;
		public bool irq_pending;
		void SyncIRQ()
		{
			irq_pending = sequencer_irq | dmc_irq;
		}

		NES nes;
		public BisqAPU(NES nes)
		{
			this.nes = nes;
		}

		public void RunOne()
		{
			if (data.IRQ_delay != 0x7FFF)
			{
				if (data.IRQ_delay > 0) --data.IRQ_delay.Value;
				else { sequencer_irq = true; SyncIRQ(); data.IRQ_delay.Value = 0x7FFF; }
			}
			if (data.frame_delay > 0)
				--data.frame_delay.Value;
			else
			{
				bool Do240 = true, Do120 = false;
				data.frame_delay.Value += frame_period;
				switch (data.frame.Value++)
				{
					case 0:
						if (!(bool)data.IRQdisable && !(bool)data.FiveCycleDivider)
							data.IRQ_delay.Value = frame_period * 4 + 2;
						// passthru
						goto case 2;
					case 2:
						Do120 = true;
						break;
					case 1:
						data.frame_delay.Value -= 2;
						break;
					case 3:
						data.frame.Value = 0;
						if ((bool)data.FiveCycleDivider)
							data.frame_delay.Value += frame_period - 6;
						break;
				}
				// Some events are invoked at 96 Hz or 120 Hz rate. Others, 192 Hz or 240 Hz.
				for (int c = 0; c < 4; ++c)
				{
					APUchannel ch = channels[c];
					int wl = ch.WaveLength;

					// 96/120 Hz events:
					if (Do120)
					{
						// Length tick (all channels except DMC, but different disable bit for triangle wave)
						if ((bool)ch.length_counter
						&& !(c == 2 ? (bool)ch.LinearCounterDisable : (bool)ch.LengthCounterDisable))
							ch.length_counter.Value -= 1;

						// Sweep tick (square waves only)
						int ref_ch_sweep_delay = ch.sweep_delay;
						if (c < 2 && count(ref ref_ch_sweep_delay, ch.SweepRate))
							if (wl >= 8 && (bool)ch.SweepEnable && (bool)ch.SweepShift)
							{
								int s = wl >> ch.SweepShift;
								wl += ((bool)ch.SweepDecrease ? ((c != 0) ? -s : ~s) : s);
								if (wl < 0x800) ch.WaveLength.Value = (uint)wl;
							}

						ch.sweep_delay.Value = ref_ch_sweep_delay;
					}

					// 240/192 Hz events:
					if (Do240)
					{
						// Linear tick (triangle wave only) (all ticks)
						if (c == 2)
							ch.linear_counter.Value = (bool)ch.LinearCounterDisable
							? ch.LinearCounterInit
							: (ch.linear_counter > 0 ? ch.linear_counter - 1 : 0);

						// Envelope tick (square and noise channels) (all ticks)
						int ref_ch_env_delay = ch.env_delay;
						if (c != 2 && count(ref ref_ch_env_delay, ch.EnvDecayRate))
							if (ch.envelope > 0 || (bool)ch.EnvDecayLoopEnable)
								ch.envelope.Value = (ch.envelope - 1) & 15;
						ch.env_delay.Value = ref_ch_env_delay;
					}

				}
			}

    // Mix the audio: Get the momentary sample from each channel and mix them.
    // #define s(c) tick<c>()
    //v = [](float m,float n, float d) { return n!=0.f ? m/n : d; };
			Func<float,float,float,float> v = (float m,float n, float d) => ( n!=0.0f ? m/n : d );

    short sample = (short)(30000 *
        (
        // Square 0 and 1
        v(95.88f,  (100.0f + v(8128.0f, tick(0) + tick(1), -100.0f)), 0.0f)
        // Triangle, noise, DMC
    +  v(159.79f, (100.0f + v(1.0f, tick(2)/8227.0f + tick(3)/12241.0f + tick(4)/22638.0f, -1000.0f)), 0.0f)
        // GamePak audio (these volume values are bogus, but sound acceptable)
    +  v(95.88f,  (100.0f + v(32512.0f, /*GamePak::ExtAudio()*/ 0, -100.0f)), 0.0f)
        - 0.5f
        ));

			EmitSample(sample);

			////this (and the similar line below) is a crude hack
			////we should be generating logic to suppress the $4015 clear when the assert signal is set instead
			////be sure to test "apu_test" if you mess with this
			//sequencer_irq |= sequencer_irq_assert;
		}

		void WriteC(int chno, int index, byte value)
		{
			APUchannel ch = channels[chno];
			switch (index)
			{
				case 0:
					if ((bool)ch.LinearCounterDisable) ch.linear_counter.Value = value & 0x7F;
					ch.reg0.Value = value;
					break;
				case 1:
					ch.reg1.Value = value;
					ch.sweep_delay.Value = ch.SweepRate;
					break;
				case 2:
					ch.reg2.Value = value;
					break;
				case 3:
					ch.reg3.Value = value;
					if (data.ChannelsEnabled[chno]!=0)
						ch.length_counter.Value = LengthCounters[ch.LengthCounterInit];
					ch.linear_counter.Value = ch.LinearCounterInit;
					ch.env_delay.Value = ch.EnvDecayRate;
					ch.envelope.Value = 15;
					if (index < 8) ch.phase.Value = 0;
					break;
				case 0x12:
					ch.reg0.Value = value;
					ch.address.Value = ((int)ch.reg0 | 0x300) << 6;
					break;
				case 0x10:
					ch.reg3.Value = value;
					ch.WaveLength.Value = (uint)(DMCperiods[value & 0x0F] - 1);
					if (!(bool)ch.IRQenable) { dmc_irq = false; SyncIRQ(); }
					break;
				case 0x13: // sample length
					ch.reg1.Value = value;
					if (ch.length_counter == 0)
						ch.length_counter.Value = ch.PCMlength * 16 + 1;
					break;
				case 0x11: // dac value
					ch.linear_counter.Value = value & 0x7F;
					break;
				case 0x15:
					for (int c = 0; c < 5; ++c)
						data.ChannelsEnabled[c] = (uint)((value >> c) & 1); //noteworthy tweak
					for (int c = 0; c < 5; ++c)
						if (data.ChannelsEnabled[c]==0)
							channels[c].length_counter.Value = 0;
						else if (c == 4 && channels[c].length_counter == 0)
						{
							APUchannel chh = channels[c];
							chh.length_counter.Value = chh.PCMlength * 16 + 1;
							chh.address.Value = ((int)chh.reg0 | 0x300) << 6;
							chh.phase.Value = 0;
						}
					//CPU::reg.APU_DMC_IRQ = false;
					dmc_irq = false; SyncIRQ();
					break;
				case 0x17:
					data.IRQdisable.Value = (uint)(value & 0x40);
					data.FiveCycleDivider.Value = (uint)(value & 0x80);
					// apu_test 1-len_ctr: Writing $80 to $4017 should clock length immediately
					//                 But Writing $00 to $4017 shouldn't clock length immediately
					data.frame_delay.Value &= 1;
					data.frame.Value = 0;
					data.IRQ_delay.Value = 0x7FFF;
					if ((bool)data.IRQdisable) { sequencer_irq = false; SyncIRQ(); }
					if (!(bool)data.FiveCycleDivider)
					{
						data.frame.Value = 1;
						data.frame_delay.Value += frame_period;

						if (!(bool)data.IRQdisable)
						{
							data.IRQ_delay.Value = data.frame_delay + frame_period * 3 + 1 - 3;
							// ^ "- 3" makes apu_test "4-jitter" not complain
							//   that "Frame irq is set too late"
						}
					}
					break;
			}
		}

		public byte ReadReg(int addr)
		{
			byte res = 0;
			for (int c = 0; c < 5; ++c) res |= (byte)(((bool)channels[c].length_counter ? 1 << c : 0));
			if (sequencer_irq) res |= 0x40; sequencer_irq = false; SyncIRQ();
			if (dmc_irq) res |= 0x80;
			return res;
		}

		public void WriteReg(int addr, byte val)
		{
			int index = addr - 0x4000;
			WriteC((index / 4) % 5, index < 0x10 ? index % 4 : index, val);
		}

		public void NESSoftReset()
		{
		}

		public void DiscardSamples()
		{
			metaspu.buffer.clear();
		}

		public void SyncState(Serializer ser)
		{
		}

		double accumulate;
		double timer;
		Queue<int> squeue = new Queue<int>();
		int last_hwsamp;
		int panic_sample, panic_count;
		void EmitSample(int samp)
		{
			//kill the annoying hum that is a consequence of the shitty code below
			if (samp == panic_sample)
				panic_count++;
			else panic_count = 0;
			if (panic_count > 178977)
				samp = 0;
			else
				panic_sample = samp;

			int this_samp = samp;
			const double kMixRate = 44100.0 / 1789772.0;
			const double kInvMixRate = (1 / kMixRate);
			timer += kMixRate;
			accumulate += samp;
			if (timer <= 1)
				return;

			accumulate -= samp;
			timer -= 1;
			double ratio = (timer / kMixRate);
			double fractional = (this_samp - last_hwsamp) * ratio;
			double factional_remainder = (this_samp - last_hwsamp) * (1 - ratio);
			accumulate += fractional;

			accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels
			int outsamp = (int)(accumulate / kInvMixRate);
			if (CFG_USE_METASPU)
				metaspu.buffer.enqueue_sample((short)outsamp, (short)outsamp);
			else squeue.Enqueue(outsamp);
			accumulate = factional_remainder;

			last_hwsamp = this_samp;
		}

		MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
		public int MaxVolume { get; set; } // not supported

		void ISoundProvider.GetSamples(short[] samples)
		{
			if (CFG_USE_METASPU)
			{
				metaspu.GetSamples(samples);
				//foreach(short sample in samples) bw.Write((short)sample);
			}
			else
				MyGetSamples(samples);

			//mix in the cart's extra sound circuit
			nes.Board.ApplyCustomAudio(samples);
		}

		//static BinaryWriter bw = new BinaryWriter(new FileStream("d:\\out.raw",FileMode.Create,FileAccess.Write,FileShare.Read));
		void MyGetSamples(short[] samples)
		{
			//Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2);

			for (int i = 0; i < samples.Length / 2; i++)
			{
				int samp = 0;
				if (squeue.Count != 0)
					samp = squeue.Dequeue();

				samples[i * 2 + 0] = (short)(samp);
				samples[i * 2 + 1] = (short)(samp);
				//bw.Write((short)samp);
			}
		}

	} //class BisqAPU
}