
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HidSharp;

namespace MuzikaGromche.Via;

class DevicePool<T> : IDisposable
    where T : IDevicePoolDelegate
{
    private readonly IDevicePoolFactory<T> sidecarFactory;
    // Async synchronization
    private readonly SemaphoreSlim semaphore;
    // Async access, use semaphore!
    private readonly Dictionary<string, (HidDevice, T)> existing = [];

    private bool updating = false;

    public DevicePool(IDevicePoolFactory<T> sidecarFactory)
    {
        semaphore = new SemaphoreSlim(1, 1);
        this.sidecarFactory = sidecarFactory;
        DeviceList.Local.Changed += (_sender, _args) =>
        {
            Console.WriteLine($"Pool Changed");
            _ = Task.Run(async () =>
            {
                await Task.Delay(300);
                if (!updating)
                {
                    updating = true;
                    UpdateConnections();
                    updating = false;
                    return;
                }
            });
        };
        UpdateConnections();
    }

    private void WithSemaphore(Action action)
    {
        semaphore.Wait();
        try
        {
            action.Invoke();
        }
        finally
        {
            semaphore.Release();
        }
    }

    public void Dispose()
    {
        Console.WriteLine($"Pool Dispose");
        Clear();
    }

    private void Clear()
    {
        WithSemaphore(ClearUnsafe);
    }

    private void ClearUnsafe()
    {
        Console.WriteLine($"Pool Clear");
        ForEachUnsafe((sidecar) => sidecar.Dispose());
        existing.Clear();
    }

    private void ClearUnsafe(string path)
    {
        if (existing.Remove(path, out var data))
        {
            var (_, sidecar) = data;
            sidecar.Dispose();
        }
    }

    public void Restore()
    {
        Console.WriteLine($"Pool Restore");
        ForEach((sidecar) => sidecar.Restore());
    }

    private void UpdateConnections()
    {
        WithSemaphore(() =>
        {
            // set of removed devices to be cleaned up
            var removed = existing.Keys.ToHashSet();

            foreach (var hidDevice in DeviceList.Local.GetHidDevices())
            {
                try
                {
                    // surely path is enough to uniquely differentiate
                    var path = hidDevice.DevicePath;
                    if (existing.ContainsKey(path))
                    {
                        // not gone anywhere
                        removed.Remove(path);
                        continue;
                    }

                    var sidecar = sidecarFactory.Create(hidDevice);
                    if (sidecar != null)
                    {
                        Console.WriteLine($"Pool Added {path}");
                        existing[path] = (hidDevice, sidecar);
                    }
                }
                catch (Exception)
                {
                }
            }
            foreach (var path in removed)
            {
                try
                {
                    ClearUnsafe(path);
                }
                catch (Exception)
                {
                }
            }
        });
    }

    public void ForEach(Action<T> action)
    {
        WithSemaphore(() => ForEachUnsafe(action));
    }

    private void ForEachUnsafe(Action<T> action)
    {
        foreach (var (_, sidecar) in existing.Values)
        {
            try
            {
                action(sidecar);
            }
            // ignore.  the faulty device will be removed soon.
            catch (IOException)
            {
            }
            catch (TimeoutException)
            {
            }
        }
    }

}

/// Dispose should call Restore
interface IDevicePoolDelegate : IDisposable
{
    void Restore();
}

interface IDevicePoolFactory<T>
{
    T? Create(HidDevice hidDevice);
}
