using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Security;
using System.Threading.Tasks;
using BarRaider.SdTools;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using Newtonsoft.Json;

class CachedSound
{
    public byte[] AudioData { get; private set; }
    public WaveFormat WaveFormat { get; private set; }
    public CachedSound(string audioFileName)
    {
        using (var audioFileReader = new AudioFileReader(audioFileName))
        {
            // TODO: could add resampling in here if required
            WaveFormat = audioFileReader.WaveFormat;
            var wholeFile = new List<byte>((int)(audioFileReader.Length));
            var readBuffer = new byte[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels*4];
            int samplesRead;
            while ((samplesRead = audioFileReader.Read(readBuffer, 0, readBuffer.Length)) > 0)
            {
                wholeFile.AddRange(readBuffer.Take(samplesRead));
            }
            AudioData = wholeFile.ToArray();
        }
        //Logger.Instance.LogMessage(TracingLevel.INFO, $"AudioData Length {AudioData.Length}");

    }
}
class CachedSoundSampleProvider : IWaveProvider
{
    private readonly CachedSound cachedSound;
    private long position;

    ~CachedSoundSampleProvider() {
        //Logger.Instance.LogMessage(TracingLevel.INFO, $"Cache destructor");
    }    

    public CachedSoundSampleProvider(CachedSound cachedSound)
    {
        this.cachedSound = cachedSound;
        position = 0;
    }

    public int Read(byte[] buffer, int offset, int count)
    {
        //Logger.Instance.LogMessage(TracingLevel.INFO, $"Read1 byte");
        var availableSamples = cachedSound.AudioData.Length - position;
        var samplesToCopy = Math.Min(availableSamples, count);
        try
        {
            //Logger.Instance.LogMessage(TracingLevel.INFO, $"{cachedSound.AudioData.GetType()} {buffer.GetType()}");
            Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
        }
        catch (Exception ex)
        {
            //Logger.Instance.LogMessage(TracingLevel.INFO, $"{ex.ToString()}");
        }
        //Logger.Instance.LogMessage(TracingLevel.INFO, $"Read3");
        position += samplesToCopy;
        //Logger.Instance.LogMessage(TracingLevel.INFO, $"Sending {samplesToCopy} samples");
        return (int)samplesToCopy;
    }

    public WaveFormat WaveFormat => cachedSound.WaveFormat;
}

public class WavPlayer
{
    private static WavPlayer? instance;
    public static WavPlayer Instance
    {
        get
        {
            instance ??= new WavPlayer();
            return instance;
        }

    }
    public enum PlayMode
    {
        PlayOverlap,
        PlayStop
    }

    private readonly ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>> _activePlayers;

    public WavPlayer()
    {
        _activePlayers = new ConcurrentDictionary<string, List<Tuple<WaveOutEvent, IWaveProvider>>>();
    }

    public void Play(string filePath, string id, double volume, PlayMode mode)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));

        if (mode == PlayMode.PlayOverlap)
        {
            PlayWithOverlap(filePath, id, volume);
        }
        else if (mode == PlayMode.PlayStop)
        {
            PlayWithStop(filePath, id, volume);
        }
        else
        {
            throw new ArgumentOutOfRangeException(nameof(mode), "Invalid play mode specified.");
        }
    }

    private void PlayWithOverlap(string filePath, string id, double volume)
    {

        try
        {
            //Logger.Instance.LogMessage(TracingLevel.INFO, "Play overlap");
            var player = CreatePlayer(filePath, id);

            if (!_activePlayers.ContainsKey(filePath))
            {
                _activePlayers[filePath] = new List<Tuple<WaveOutEvent, IWaveProvider>>();
            }

            _activePlayers[filePath].Add(player);
            player.Item1.Volume = (float)volume;
            player.Item1.Play();
        }
        catch(Exception ex) 
        {
            //Logger.Instance.LogMessage(TracingLevel.INFO, ex.ToString());
        }
       
        //var playersToDispose = _activePlayers[filePath].Where(x => x.Item1.PlaybackState == PlaybackState.Stopped).ToList();
        //foreach (var p in playersToDispose)
        //{
        //    p.Item1.Stop();
        //    p.Item1.Dispose();
        //}
        //_activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
    }

    private void PlayWithStop(string filePath, string id, double volume)
    {
        if (_activePlayers.TryGetValue(filePath, out var players))
        {

            // Stop and dispose all current players for this file
            if (players.Any(x => x.Item1.PlaybackState == PlaybackState.Playing))
            {
                var playersToDispose = players.ToList();
                foreach (var player in playersToDispose)
                {
                    player.Item1.Stop();
                    player.Item1.Dispose();
                }
            }
            else
            {
                PlayWithOverlap(filePath, id, volume);
            }

        }
        else
        {
            // Start a new player
            PlayWithOverlap(filePath, id, volume);
        }

        _activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);
    }

    private Tuple<WaveOutEvent, IWaveProvider> CreatePlayer(string filePath, string name)
    {
        var reader = new CachedSoundSampleProvider(new CachedSound(filePath));
        //var reader = new AudioFileReader(filePath);
        int number = -1;
        for (int i = 0; i < WaveOut.DeviceCount; ++i)
        {
            if (WaveOut.GetCapabilities(i).ProductName == name)
            {
                number = i;
            }
        }
        var player = new WaveOutEvent() { DeviceNumber = number };
        player.Init(reader);
        return new Tuple<WaveOutEvent, IWaveProvider>(player, reader);
    }

    private void CleanupPlayer(string filePath)
    {
        if (_activePlayers.TryGetValue(filePath, out var players))
        {
            var playersToDispose = players.ToList();
            foreach (var p in playersToDispose)
            {
                p.Item1.Stop();
                p.Item1.Dispose();
            }
        }
        _activePlayers[filePath].RemoveAll(x => x.Item1.PlaybackState == PlaybackState.Stopped);

    }

    public void StopAll()
    {
        foreach (var players in _activePlayers.Values)
        {
            var playersToDispose = players.ToList();
            foreach (var player in playersToDispose)
            {
                player.Item1.Stop();
                player.Item1.Dispose();
            }
            players.Clear();
        }

        _activePlayers.Clear();
    }
}
