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

namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
	/// <summary>
	/// Logical Beeper class
	/// Used to emulate the sound generated by tape loading
	/// This implementation uses BlipBuffer and should *always* output at 44100 with 882 samples per frame
	/// (so that it can be mixed easily further down the line)
	/// </summary>
	public class Beeper : ISoundProvider, IBeeperDevice
	{
		/// <summary>
		/// Sample Rate 
		/// This usually has to be 44100 for ISoundProvider
		/// </summary>
		private int _sampleRate;
		public int SampleRate
		{
			get => _sampleRate;
			set => _sampleRate = value;
		}

		/// <summary>
		/// Buzzer volume
		/// Accepts an int 0-100 value
		/// </summary>
		private int _volume;
		public int Volume
		{
			get => VolumeConverterOut(_volume);
			set
			{
				var newVol = VolumeConverterIn(value);
				if (newVol != _volume)
					blip.Clear();
				_volume = VolumeConverterIn(value);
			}
		}

		/// <summary>
		/// The last used volume (used to modify blipbuffer delta values)
		/// </summary>
		private int lastVolume;

		/// <summary>
		/// The number of cpu cycles per frame
		/// </summary>
		private long _tStatesPerFrame;

		/// <summary>
		/// The parent emulated machine
		/// </summary>
		private readonly CPCBase _machine;

		/// <summary>
		/// The last pulse
		/// </summary>
		private bool LastPulse;

		/// <summary>
		/// The last T-State (cpu cycle) that the last pulse was received
		/// </summary>
		private long LastPulseTState;

		/// <summary>
		/// Device blipbuffer
		/// </summary>
		private readonly BlipBuffer blip = new BlipBuffer(883);

		/// <summary>
		/// Takes an int 0-100 and returns the relevant short volume to output
		/// </summary>
		private int VolumeConverterIn(int vol)
		{
			int maxLimit = short.MaxValue / 3;
			int increment = maxLimit / 100;

			return vol * increment;
		}

		/// <summary>
		/// Takes an short volume and returns the relevant int value 0-100
		/// </summary>
		private int VolumeConverterOut(int shortvol)
		{
			int maxLimit = short.MaxValue / 3;
			int increment = maxLimit / 100;

			if (shortvol > maxLimit)
				shortvol = maxLimit;

			return shortvol / increment;
		}

		public Beeper(CPCBase machine)
		{
			_machine = machine;
		}

		/// <summary>
		/// Initialises the beeper
		/// </summary>
		public void Init(int sampleRate, int tStatesPerFrame)
		{
			blip.SetRates((4000000), sampleRate);
			_sampleRate = sampleRate;
			_tStatesPerFrame = tStatesPerFrame;
		}

		/// <summary>
		/// Processes an incoming pulse value and adds it to the blipbuffer
		/// </summary>
		public void ProcessPulseValue(bool pulse)
		{
			if (!_machine._renderSound)
				return;

			if (LastPulse == pulse)
			{
				// no change
				blip.AddDelta((uint)_machine.CurrentFrameCycle, 0);
			}

			else
			{
				if (pulse)
					blip.AddDelta((uint)_machine.CurrentFrameCycle, (short)(_volume));
				else
					blip.AddDelta((uint)_machine.CurrentFrameCycle, -(short)(_volume));

				lastVolume = _volume;
			}

			LastPulse = pulse;
		}

		public bool CanProvideAsync => false;

		public SyncSoundMode SyncMode => SyncSoundMode.Sync;

		public void SetSyncMode(SyncSoundMode mode)
		{
			if (mode != SyncSoundMode.Sync)
				throw new InvalidOperationException("Only Sync mode is supported.");
		}

		public void GetSamplesAsync(short[] samples)
		{
			throw new NotSupportedException("Async is not available");
		}

		public void DiscardSamples()
		{
			blip.Clear();
		}

		public void GetSamplesSync(out short[] samples, out int nsamp)
		{
			blip.EndFrame((uint)_tStatesPerFrame);
			nsamp = blip.SamplesAvailable();
			samples = new short[nsamp * 2];
			blip.ReadSamples(samples, nsamp, true);
			for (int i = 0; i < nsamp * 2; i += 2)
			{
				samples[i + 1] = samples[i];
			}
		}

		public void SyncState(Serializer ser)
		{
			ser.BeginSection("Buzzer");
			ser.Sync(nameof(_tStatesPerFrame), ref _tStatesPerFrame);
			ser.Sync(nameof(_sampleRate), ref _sampleRate);
			ser.Sync(nameof(LastPulse), ref LastPulse);
			ser.Sync(nameof(LastPulseTState), ref LastPulseTState);
			ser.EndSection();
		}
	}
}
