using Serilog;
using Splat;
using System.Collections.Generic;
using System.Linq;

namespace SPTInstaller.Helpers;

/// <summary>
/// A helper class to handle simple service registration to Splat with constructor parameter injection
/// </summary>
/// <remarks>Splat only recognizes the registered types and doesn't account for interfaces :(</remarks>
internal static class ServiceHelper
{
    private static bool TryRegisterInstance<T, T2>(object[] parameters = null)
    {
        var instance = Activator.CreateInstance(typeof(T2), parameters);
        
        if (instance != null)
        {
            Locator.CurrentMutable.RegisterConstant<T>((T)instance);
            return true;
        }
        
        return false;
    }
    
    /// <summary>
    /// Register a class as a service
    /// </summary>
    /// <typeparam name="T">class to register</typeparam>
    public static void Register<T>() where T : class => Register<T, T>();
    
    /// <summary>
    /// Register a class as a service by another type
    /// </summary>
    /// <typeparam name="T">type to register as</typeparam>
    /// <typeparam name="T2">class to register</typeparam>
    public static void Register<T, T2>() where T : class
    {
        var constructors = typeof(T2).GetConstructors();
        
        foreach (var constructor in constructors)
        {
            var parmesan = constructor.GetParameters();
            
            if (parmesan.Length == 0)
            {
                if (TryRegisterInstance<T, T2>()) return;
                
                continue;
            }
            
            List<object> parameters = new List<object>();
            
            for (int i = 0; i < parmesan.Length; i++)
            {
                var parm = parmesan[i];
                
                var parmValue = Get(parm.ParameterType);
                
                if (parmValue != null) parameters.Add(parmValue);
            }
            
            if (TryRegisterInstance<T, T2>(parameters.ToArray())) return;
        }
    }
    
    /// <summary>
    /// Get a service from splat
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException">Thrown if the service isn't found</exception>
    public static object Get(Type type)
    {
        var service = Locator.Current.GetService(type);
        
        if (service == null)
        {
            var message = $"Could not locate service of type '{type.Name}'";
            Log.Error(message);
            throw new InvalidOperationException(message);
        }
        
        return service;
    }
    
    /// <summary>
    /// Get a service from splat
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException">Thrown if the service isn't found</exception>
    public static T Get<T>()
    {
        var service = Locator.Current.GetService<T>();
        
        if (service == null)
        {
            var message = $"Could not locate service of type '{nameof(T)}'";
            Log.Error(message);
            throw new InvalidOperationException(message);
        }
        
        return service;
    }
    
    /// <summary>
    /// Get all services of a type
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException">thrown if no services are found</exception>
    public static T[] GetAll<T>()
    {
        var services = Locator.Current.GetServices<T>().ToArray();
        
        if (services == null || services.Count() == 0)
        {
            var message = $"Could not locate service of type '{nameof(T)}'";
            Log.Error(message);
            throw new InvalidOperationException(message);
        }
        
        return services;
    }
}