//
//  ServiceCollectionExtensions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets.Converters;
using NosSmooth.Packets.Converters.Basic;
using NosSmooth.Packets.Converters.Common;
using NosSmooth.Packets.Converters.Packets;
using NosSmooth.Packets.Converters.Special;
using NosSmooth.Packets.Packets;
using NosSmooth.Packets.Packets.Server.Weapons;
namespace NosSmooth.Packets.Extensions;
/// 
/// Extensions for .
/// 
public static class ServiceCollectionExtensions
{
    /// 
    /// Add packet serialization classes.
    /// 
    /// 
    /// All generic implementations of ITypeConverter the class
    /// implements will be registered.
    /// 
    /// The service collection.
    /// The collection.
    public static IServiceCollection AddPacketSerialization(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddSingleton()
            .AddSingleton()
            .AddSingleton(p =>
            {
                var repository = new PacketTypesRepository(p.GetRequiredService());
                var packetTypes = typeof(ServiceCollectionExtensions).Assembly
                    .GetExportedTypes()
                    .Where(x => x != typeof(UnresolvedPacket) && !x.IsAbstract && typeof(IPacket).IsAssignableFrom(x));
                foreach (var packetType in packetTypes)
                {
                    var result = repository.AddPacketType(packetType);
                    if (!result.IsSuccess)
                    {
                        // TODO: figure out how to handle this.
                        throw new Exception(packetType + ": " + result.Error.Message);
                    }
                }
                return repository;
            })
            .AddGeneratedSerializers(typeof(ServiceCollectionExtensions).Assembly)
            .AddBasicConverters();
    }
    /// 
    /// Adds all generated serializers from the given assembly.
    /// 
    /// The service collection.
    /// The assembly.
    /// The collection.
    public static IServiceCollection AddGeneratedSerializers(this IServiceCollection serviceCollection, Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(x => x.Namespace?.Contains("Generated") ?? false)
            .Where(x => x.GetInterfaces().Any(
                i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeConverter<>)
            ));
        foreach (var type in types)
        {
            serviceCollection.AddTypeConverter(type);
        }
        return serviceCollection;
    }
    /// 
    /// Adds basic converters for int, uint, short, ushort, long, ulong, char, string
    /// and special converters for lists and enums.
    /// 
    /// The service collection.
    /// The collection.
    public static IServiceCollection AddBasicConverters(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddSpecialConverter()
            .AddSpecialConverter()
            .AddSpecialConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter()
            .AddTypeConverter();
    }
    /// 
    /// Add generic type converter.
    /// 
    /// 
    /// All generic implementations of ITypeConverter the class
    /// implements will be registered.
    /// 
    /// The service collection.
    /// The type of the converter.
    /// The collection.
    public static IServiceCollection AddTypeConverter(this IServiceCollection serviceCollection)
        where TConverter : ITypeConverter
        => serviceCollection.AddTypeConverter(typeof(TConverter));
    /// 
    /// Add generic type converter.
    /// 
    /// 
    /// All generic implementations of ITypeConverter the class
    /// implements will be registered.
    /// 
    /// The service collection.
    /// The type of the converter.
    /// The collection.
    public static IServiceCollection AddTypeConverter(this IServiceCollection serviceCollection, Type converterType)
    {
        if (!converterType.GetInterfaces().Any(
                i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ITypeConverter<>)
            ))
        {
            throw new ArgumentException(
                $"{nameof(converterType)} should implement ITypeConverter.",
                nameof(converterType));
        }
        var handlerTypeInterfaces = converterType.GetInterfaces();
        var handlerInterfaces = handlerTypeInterfaces.Where
        (
            r => r.IsGenericType && r.GetGenericTypeDefinition() == typeof(ITypeConverter<>)
        );
        foreach (var handlerInterface in handlerInterfaces)
        {
            serviceCollection.AddSingleton(handlerInterface, converterType);
        }
        return serviceCollection;
    }
    /// 
    /// Add the specified converter as a special converter.
    /// 
    /// The service collection.
    /// The type to add as a special converter.
    /// The collection.
    public static IServiceCollection AddSpecialConverter(this IServiceCollection serviceCollection)
        where TSpecialConverter : class, ISpecialTypeConverter
    {
        return serviceCollection.AddSingleton();
    }
}