Code/Util/Math/BinaryOperator.cs
namespace Nodebox.Util.Math;

public abstract class BinaryOperator {
    public static Dictionary<Type, BinaryOperator> LazyStore { get; set; } = [];
    public static T Get<T>() where T : BinaryOperator, new() {
        if (LazyStore.TryGetValue(typeof(T), out var op)) {
            return (T)op;
        }

        var instance = new T();
        LazyStore[typeof(T)] = instance;
        return instance;
    }

    public Dictionary<(Type, Type), Func<object, object, object>> Lookup;
    public BinaryOperator() {
        Lookup = GenerateLookup();
    }

    public virtual Dictionary<(Type, Type), Func<object, object, object>> GenerateLookup() {
        var lookup = BaseLookup;
        var seq = TypeLibrary.GetMethodsWithAttribute<ModifierAttribute>().OrderBy(x => x.Method.GetCustomAttribute<OrderAttribute>()?.Value ?? 0);
        foreach (var (func, attr) in seq) {
            func.Invoke(null, [attr.Type, lookup]);
        }

        return lookup;
    }

    public abstract Dictionary<(Type, Type), Func<object, object, object>> BaseLookup { get; }
    public object Apply(object a, object b) => Lookup[(a.GetType(), b.GetType())](a, b);
    public bool IsValid<TSelf, TOther>() => Lookup.ContainsKey((typeof(TSelf), typeof(TOther)));
    public bool IsValid(Type a, Type b) => Lookup.ContainsKey((a, b));
    public bool IsValid(object a, object b) => IsValid(a.GetType(), b.GetType());


    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class ModifierAttribute(Type type) : Attribute {
        public Type Type { get; set; } = type;
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class ModifierAttribute<T> : ModifierAttribute {
        public ModifierAttribute() : base(typeof(T)) { }
    }
}


public static class BinaryOperatorExtensions {
    extension<T>(T binaryOperator) where T : BinaryOperator, new() {
        public static T Global => BinaryOperator.Get<T>();
    }
}