using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace GlitchyEngine.Extensions;

public static class TypeExtension
{
    /// <summary>
    /// Determines whether the current type can be assigned to a variable of the specified <see cref="targetType"/>.
    /// </summary>
    /// <param name="targetType">The type to compare with the current type.</param>
    /// <returns></returns>
    public static bool IsAssignableTo(this Type type, Type targetType)
    {
        return targetType.IsAssignableFrom(type);
    }

    /// <summary>
    /// Enumerates all types in all assemblies.
    /// </summary>
    public static IEnumerable<Type> EnumerateAllTypes()
    {
        foreach (Assembly domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
        foreach (Type type in domainAssembly.GetTypes())
        {
            yield return type;
        }
    }

    /// <summary>
    /// Enumerates all types that derive from the given type.
    /// </summary>
    /// <param name="baseType"></param>
    /// <returns></returns>
    public static IEnumerable<Type> FindDerivedTypes(Type baseType)
    {
        foreach (Assembly domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
        foreach (Type type in domainAssembly.GetTypes())
        {
            if (baseType.IsAssignableFrom(type) && !type.IsAbstract) yield return type;
        }
    }

    /// <summary>
    /// Returns whether or not the type has an <see cref="Attribute"/> of the specified <see cref="Type"/> <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T">The type of the attribute</typeparam>
    /// <returns><see langword="true"/> if the type has the specified <see cref="Attribute"/>; <see langword="true"/> otherwise.</returns>
    public static bool HasCustomAttribute<T>(this Type type) where T: Attribute
    {
        return type.GetCustomAttribute<T>() != null;
    }

    /// <summary>
    /// Returns whether or not the type has an <see cref="Attribute"/> of the specified <see cref="Type"/> <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T">The type of the attribute</typeparam>
    /// <param name="attribute">The attribute, or <seealso langword="null"/>, if the type hasn't got the attribute specified.</param>
    /// <returns><see langword="true"/> if the type has the specified <see cref="Attribute"/>; <see langword="true"/> otherwise.</returns>
    public static bool TryGetCustomAttribute<T>(this Type type, out T attribute) where T: Attribute
    {
        attribute = type.GetCustomAttribute<T>();

        return attribute != null;
    }

    private static ReadOnlySpan<char> ReadTypeName(ReadOnlySpan<char> fullName)
    {
        for (int i = 0; i < fullName.Length; i++)
        {
            if (!char.IsLetterOrDigit(fullName[i]) && fullName[i] != '.' && fullName[i] != '+' && fullName[i] != '_')
            {
                return fullName.Slice(0, i);
            }
        }

        return fullName;
    }
    
    public static Type? FindType(string fullName)
    {
        ReadOnlySpan<char> rest = new ReadOnlySpan<char>();

        return FindType(fullName.AsSpan(), ref rest);
    }

    private static Type? FindType(ReadOnlySpan<char> fullName, ref ReadOnlySpan<char> rest)
    {
        rest = fullName;

        ReadOnlySpan<char> typeName = ReadTypeName(fullName);
        rest = rest.Slice(typeName.Length);
        
        if (rest.IsEmpty || rest[0] != '`')
        {
            // Non generic type, easy!
            return GetType(typeName);
        }

        // Handle generic type

        int brackedIndex = fullName.IndexOf('[');

        typeName = fullName.Slice(0, brackedIndex);
        rest = fullName.Slice(brackedIndex + 1).Trim();

        Type? genericType = GetType(typeName);

        Console.WriteLine($"Generic Type: {genericType}");
        
        List<Type> arguments = new List<Type>();

        while (true)
        {
            Type? argument = FindType(rest, ref rest);
            Console.WriteLine($"Argument:  {argument}");

            Debug.Assert(argument != null);

            arguments.Add(argument!);

            rest = rest.TrimStart();

            if (rest.TrimStart()[0] == ',')
            {
                rest = rest.Slice(1);
            }
            else if (rest.TrimStart()[0] == ']')
            {
                rest = rest.Slice(1);
                break;
            }
            else
            {
                break;
            }
        }
        
        return genericType?.MakeGenericType(arguments.ToArray());
    }

    private static Type? GetType(ReadOnlySpan<char> fullName)
    {
        string name = fullName.TrimEnd(']').ToString();

        // Non generic type, easy!
        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
        {
            Type? type = assembly.GetType(name);

            if (type != null)
                return type;
        }

        return null;
    }
}
