// <copyright file="ScratchLinkApp.cs" company="Scratch Foundation">
// Copyright (c) Scratch Foundation. All rights reserved.
// </copyright>

namespace ScratchLink;

using System;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using ScratchLink.BLE;

/// <summary>
/// Main entry point for Scratch Link and central service provider for dependency injection.
/// </summary>
public class ScratchLinkApp
{
    private const int WebSocketPort = 20111;

    private readonly SessionManager sessionManager;
    private readonly WebSocketListener webSocketListener;

    private ScratchLinkApp(IServiceProvider platformServicesProvider)
    {
        Debug.AutoFlush = Trace.AutoFlush = true;

        this.Services = platformServicesProvider;
        if (Current != null)
        {
            throw new InvalidOperationException("Attempt to create a second app instance");
        }

        Current = this;

        this.sessionManager = this.Services.GetService<SessionManager>();
        this.webSocketListener = new ()
        {
            OnWebSocketConnection = this.sessionManager.ClientDidConnect,
        };
    }

    /// <summary>
    /// Gets the current app instance.
    /// </summary>
    public static ScratchLinkApp Current { get; private set; }

    /// <summary>
    /// Gets the platform-specific services provider.
    /// This provides access to services like the session manager or GATT helpers.
    /// </summary>
    public IServiceProvider Services { get; private set; }

    /// <summary>
    /// Run the app.
    /// </summary>
    public void Run()
    {
        this.webSocketListener.Start(string.Format("http://0.0.0.0:{0}/", WebSocketPort));
    }

    /// <summary>
    /// Quit the app.
    /// </summary>
    public void Quit()
    {
        this.webSocketListener.Stop();
        this.sessionManager.EndAllSessions();
    }

    /// <summary>
    /// Builds a Scratch Link app instance.
    /// Fills the role of the .NET generic host or <c>MauiAppBuilder</c>.
    /// </summary>
    public class Builder
    {
        private string[] arguments;
        private Type sessionManagerType;
        private Type gattHelpersBaseType;
        private Type gattHelpersType;

        /// <summary>
        /// Sets the arguments which will be passed to the app host.
        /// Must be called before Build().
        /// </summary>
        /// <param name="arguments">Command line arguments from app invocation.</param>
        public void SetArguments(string[] arguments)
        {
            this.arguments = arguments;
        }

        /// <summary>
        /// Sets the type which will be used to build the platform-specific session manager.
        /// </summary>
        /// <typeparam name="TSessionManager">The platform-specific session manager type.</typeparam>
        internal void SetSessionManager<TSessionManager>()
            where TSessionManager : SessionManager
        {
            this.sessionManagerType = typeof(TSessionManager);
        }

        /// <summary>
        /// Sets the types which will be used to build the BLE GATT helpers.
        /// </summary>
        /// <typeparam name="TGattHelpers">The platform-specific GATT helpers type.</typeparam>
        /// <typeparam name="TUUID">The platform-specific type for BLE UUID values.</typeparam>
        internal void SetGattHelpers<TGattHelpers, TUUID>()
            where TGattHelpers : GattHelpers<TUUID>
        {
            this.gattHelpersBaseType = typeof(GattHelpers<TUUID>);
            this.gattHelpersType = typeof(TGattHelpers);
        }

        /// <summary>
        /// Builds a Scratch Link app host.
        /// </summary>
        /// <returns>A new Scratch Link app host.</returns>
        internal ScratchLinkApp Build()
        {
            var serviceCollection = new ServiceCollection();
            var serviceProviderOptions = new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true };
            var servicesProvider = new DefaultServiceProviderFactory(serviceProviderOptions)
                .CreateBuilder(serviceCollection)
                .AddSingleton(typeof(SessionManager), this.sessionManagerType)
                .AddSingleton(this.gattHelpersBaseType, this.gattHelpersType)
                .BuildServiceProvider();
            return new ScratchLinkApp(servicesProvider);
        }
    }
}