using System;
using System.Security.Cryptography;
using System.Threading;

namespace TrajectoryPlanningVisualization;

public sealed class PlanningVisualizationSessionInfo
{
    public PlanningVisualizationSessionInfo(Uri uri, string token)
    {
        Uri = uri ?? throw new ArgumentNullException(nameof(uri));
        Token = token ?? throw new ArgumentNullException(nameof(token));
    }

    public Uri Uri { get; }
    public string Token { get; }
}

public sealed class PlanningVisualizationSession : IDisposable
{
    private readonly object lifecycleLock = new object();
    private readonly PlanningVisualizationOptions options;
    private LoopbackVisualizationServer server;
    private PlanningVisualizationSessionInfo sessionInfo;

    public PlanningVisualizationSession(PlanningVisualizationOptions options)
    {
        this.options = options ?? throw new ArgumentNullException(nameof(options));
    }

    public bool IsRunning
    {
        get
        {
            lock (lifecycleLock)
            {
                return server != null && server.IsRunning;
            }
        }
    }

    public PlanningVisualizationSessionInfo Start(PlanningVisualizationStaticSnapshot staticSnapshot)
    {
        if (staticSnapshot == null)
        {
            throw new ArgumentNullException(nameof(staticSnapshot));
        }

        lock (lifecycleLock)
        {
            if (server != null)
            {
                return sessionInfo;
            }

            PlanningVisualizationOptionsSnapshot validated = options.CreateValidatedSnapshot();
            string token = CreateToken();
            var created = new LoopbackVisualizationServer(validated, staticSnapshot, token);
            try
            {
                created.Start();
                server = created;
                sessionInfo = new PlanningVisualizationSessionInfo(
                    new Uri("http://127.0.0.1:" + created.Port + "/?token=" + token), token);
                return sessionInfo;
            }
            catch
            {
                created.Dispose();
                throw;
            }
        }
    }

    public void Publish(PlanningVisualizationDynamicSnapshot snapshot)
    {
        if (snapshot == null)
        {
            throw new ArgumentNullException(nameof(snapshot));
        }

        try
        {
            Volatile.Read(ref server)?.Publish(snapshot);
        }
        catch
        {
        }
    }

    public void Stop()
    {
        LoopbackVisualizationServer current;
        lock (lifecycleLock)
        {
            current = server;
            server = null;
            sessionInfo = null;
        }

        current?.Dispose();
    }

    public void Dispose()
    {
        Stop();
    }

    private static string CreateToken()
    {
        byte[] tokenBytes = new byte[32];
        using (RandomNumberGenerator random = RandomNumberGenerator.Create())
        {
            random.GetBytes(tokenBytes);
        }

        return BitConverter.ToString(tokenBytes).Replace("-", string.Empty).ToLowerInvariant();
    }
}
