Core/Library.cs
namespace Nodebox;


public static class Library {
    public abstract record All { }
    public abstract record Float { }
    public abstract record Integer { }
    public abstract record IntegerUnsigned { }
    public abstract record Vector { }
    public abstract record FloatVector { }
    public abstract record IntegerVector { }
    public abstract record FloatN { }
    public abstract record IntegerN { }

    public static Dictionary<Type, HashSet<Type>> Types;
    public static Dictionary<Type, HashSet<Type>> TypeCollections;

    private static Dictionary<Type, HashSet<Type>> GenerateTypeCollections() {
        var result = new Dictionary<Type, HashSet<Type>>();
        var all = result.GetOrCreate(typeof(All));
        Types.ForEach(x => {
            var type = x.Key;
            all.Add(type);

            x.Value.ForEach(collection => {
                var set = result.GetOrCreate(collection);
                set.Add(type);
            });
        });

        return result;
    }

    public static HashSet<Type> AllTypes => GetTypeCollection<All>();
    public static HashSet<Type> GetTypeCollection<T>() => GetTypeCollection(typeof(T));
    public static HashSet<Type> GetTypeCollection(Type type) {
        TypeCollections.TryGetValue(type, out var types);
        return types;
    }

    public static Dictionary<(Type In, Type Out), Func<object, object>> ImplicitConvertions { get; set; }
    private static bool IsGenericType(Type In) => TypeLibrary.GetType(In).IsGenericType;
    public static bool TryGetImplicitConversion(Type In, Type Out, out Func<object, object> func) {
        if (ImplicitConvertions.TryGetValue((In, Out), out func))
            return true;
            
        if (IsGenericType(In)) {
            var InGeneric = TypeLibrary.GetType(In).TargetType; // GetGenericTypeDefinition
            if (ImplicitConvertions.TryGetValue((InGeneric, Out), out func))
                return true;
        }
        return false;
    }

    public struct Entry: IComparable<Entry> {
        [Property] public Type Type { get; private set; }

        public Entry(Type type) {
            Type = type;
        }
        
        public Entry(Type type, IEnumerable<Type> generics) : this(type, [.. generics]) { }
        public Entry(Type type, Type[] generics) {
            var typeDescription = TypeLibrary.GetType(type);
            if (!typeDescription.IsGenericType) {
                if (generics.Length > 0)
                    Log.Warning("Attempt to create an Entry with generics for a non-generic type");
                Type = type;
                return;
            }

            Type = typeDescription.MakeGenericType( [.. generics] );
        }

        public readonly string Name => Type.GetDisplayName();
        public readonly bool IsGenericType => TypeLibrary.GetType(Type).IsGenericType;
        public readonly Type[] Generics { get {
            if (!IsGenericType) {
                return null;
            }

            return TypeLibrary.GetGenericArguments(Type);
        } }
        public readonly bool IsPolymorphic => TypeLibrary.GetType(Type).HasAttribute<PolymorphicAttribute>(false);
        public readonly Type PolymorphParent => TypeLibrary.GetType(Type).GetAttribute<PolymorphicAttribute>(false).Parent;
        public readonly bool IsPolymorphRequired => IsPolymorphic && TypeLibrary.GetType(Type).GetAttribute<PolymorphicAttribute>(false).PolymorphRequired;
        public readonly bool IsInitialized => TypeLibrary.GetType(Type).HasAttribute<InitializedAttribute>(false);

		public readonly int CompareTo(Entry other) {
            return Type.GetDisplayName().CompareTo(other.Type.GetDisplayName());
        }

		[Pure]
        [System.Diagnostics.Contracts.Pure]
        public readonly Node CreateNode(params object[] args) => Type.CreateClosedGeneric<Node>(args);
    }

    public static List<Entry> Entries { get; set; } = [];

    
    static Library() {
        Reload();
    }

    [ConCmd("nodebox_reload")]
    public static void Reload() {
        Types = new(){
            { typeof(bool), new() {
                } },

            { typeof(float), new() {
                typeof(Float),
                typeof(FloatN),
                } },
            
            { typeof(double), new() {
                typeof(Float),
                typeof(FloatN),
                } },

            { typeof(byte), new() {
                typeof(IntegerUnsigned),
                } },
            
            { typeof(int), new() {
                typeof(Integer),
                typeof(IntegerN),
                } },

            { typeof(long), new() {
                typeof(Integer),
                typeof(IntegerN),
                } },

            { typeof(char), new() {
                typeof(IntegerUnsigned),
                } },

            { typeof(string), new() {
                } },

            { typeof(Vector2), new() {
                typeof(Vector),
                typeof(FloatVector),
                typeof(FloatN),
                } },

            { typeof(Vector3), new() {
                typeof(Vector),
                typeof(FloatVector),
                typeof(FloatN),
                } },

            { typeof(Vector4), new() {
                typeof(Vector),
                typeof(FloatVector),
                typeof(FloatN),
                } },

            { typeof(Vector2Int), new() {
                typeof(Vector),
                typeof(IntegerVector),
                typeof(IntegerN),
                } },

            { typeof(Vector3Int), new() {
                typeof(Vector),
                typeof(IntegerVector),
                typeof(IntegerN),
                } },

            { typeof(Angles), new() {
                } },

            { typeof(Rotation), new() {
                } },

            { typeof(Color), new() {
                } },

            { typeof(GameObject), new() {
                } },
                
            { typeof(Reference), new() {
                } },

            { typeof(Reference<>), new() {
                } },

            { typeof(List<>), new() {
                } },

            { typeof(Dictionary<,>), new() {
                } },
        };

        ImplicitConvertions = new() {
            { (typeof(float), typeof(double)), value => Convert.ToDouble(value) },
            { (typeof(float), typeof(byte)), x => Convert.ToByte(x) },
            { (typeof(float), typeof(int)), x => Convert.ToInt32(x) },
            { (typeof(float), typeof(long)), value => Convert.ToInt64(value) },

            { (typeof(double), typeof(float)), value => Convert.ToSingle(value) },
            { (typeof(double), typeof(byte)), value => Convert.ToByte(value) },
            { (typeof(double), typeof(int)), value => Convert.ToInt32(value) },
            { (typeof(double), typeof(long)), value => Convert.ToInt64(value) },
            
            { (typeof(int), typeof(float)), x => Convert.ToSingle(x) },
            { (typeof(int), typeof(double)), x => Convert.ToDouble(x) },
            { (typeof(int), typeof(byte)), x => Convert.ToByte(x) },
            { (typeof(int), typeof(long)), x => Convert.ToInt64(x) },
            { (typeof(int), typeof(char)), x => Convert.ToChar(x) },

            { (typeof(long), typeof(float)), x => Convert.ToSingle(x) },
            { (typeof(long), typeof(double)), x => Convert.ToDouble(x) },
            { (typeof(long), typeof(byte)), x => Convert.ToByte(x) },
            { (typeof(long), typeof(int)), x => Convert.ToInt32(x) },
            { (typeof(long), typeof(char)), x => Convert.ToChar(x) },

            { (typeof(float), typeof(Vector2)), x => new Vector2((float)x, (float)x) },
            { (typeof(float), typeof(Vector3)), x => new Vector3((float)x, (float)x, (float)x) },
            { (typeof(float), typeof(Vector4)), x => new Vector4((float)x, (float)x, (float)x, (float)x) },
            
            { (typeof(int), typeof(Vector2Int)), x => new Vector2Int((int)x, (int)x) },
            { (typeof(int), typeof(Vector3Int)), x => new Vector3Int((int)x, (int)x, (int)x) },
            
            { (typeof(float), typeof(Angles)), x => new Angles((float)x, (float)x, (float)x) },
            { (typeof(float), typeof(Color)), x => new Color((float)x, (float)x, (float)x, 1.0f) },

            { (typeof(Reference<>), typeof(Reference)), x => (Reference)x },
        };

        TypeCollections = GenerateTypeCollections();
        // Log.Info("Type Collections:");
        // TypeCollections.Keys.ForEach(Log.Info);

        Entries.Clear();
        TypeLibrary.GetTypesWithAttribute<RegisterAttribute>(false)
            .ForEach(x => {
                var typeDescription = x.Type;
                var type = typeDescription.TargetType;
                if (!typeDescription.IsGenericType) {
                    Entries.Add(new(type));
                    return;
                }

                var generics = x.Attribute.Array;
                if (generics.Length == 0) {
                    Log.Error($"Bad RegisterAttribute on {type.GetDisplayName()}");
                    return;
                }

                var collection = generics[0];
                var substitutions = GetTypeCollection(collection);
                if (substitutions == null) {
                    Entries.Add(new(type, generics));
                    return;
                }

                substitutions
                    .Select(sub => generics.Select(type => {
                        if (type == collection) {
                            return sub;
                        }

                        return type;
                    }))
                    .ForEach(subGenerics => {
                        try {
                            Entries.Add(new(type, subGenerics));
                        }
                        catch (Exception e) {
                            var genericsStr = string.Join(", ", subGenerics.Select(x => x.GetDisplayName()));
                            Log.Warning($"Couldn't register {type.GetDisplayName()} with {genericsStr}!");
                            Log.Warning(e);
                        }
                    });
            });
        Entries.Sort();
    }
}