using JsonSubTypes;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System;
using UnityEngine;
using MoreLinq;

namespace REST_JSON_API
{
    [JsonConverter(typeof(JsonSubtypes), "kind")]
    [JsonSubtypes.KnownSubType(typeof(OMA), "OMA")]
    [JsonSubtypes.KnownSubType(typeof(OMS), "OMS")]
    [JsonSubtypes.KnownSubType(typeof(OMSTR), "OMSTR")]
    [JsonSubtypes.KnownSubType(typeof(OMF), "OMF")]
    [JsonSubtypes.KnownSubType(typeof(OMV), "VAR")]
    [JsonSubtypes.KnownSubType(typeof(RAW), "RAW")]
    [JsonSubtypes.KnownSubType(typeof(FUN), "FUN")]
    [JsonSubtypes.KnownSubType(typeof(FUNTYPE), "FUNTYPE")]
    //[JsonSubtypes.KnownSubType(typeof(OMC<T>), "OMC<" + typeof(T) + ">")]
    [JsonSubtypes.KnownSubType(typeof(OMC<Vector3>), "OMC<UnityEngine.Vector3>")]
    [JsonSubtypes.KnownSubType(typeof(OMC<float>), "OMC<System.Single>")]
    [JsonSubtypes.KnownSubType(typeof(OMC<double>), "OMC<System.Double>")]
    [JsonSubtypes.FallBackSubType(typeof(FallbackWrapper))]
    abstract public partial class SOMDoc
    {
        public string kind;

        protected SOMDoc() { kind = this.GetType().Name; }

        protected internal abstract SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params);
        public SOMDoc SOMDocType()
            => SOMDocType(new SOMDoc[0], new FUN.Param[0]);

        public static SOMDoc SOMDocType(Type type)
        {
            SOMDoc[] args = type.IsGenericType
                ? type.GetGenericArguments().Select(t => SOMDocType(t)).ToArray()
                : type.HasElementType ? new[] { SOMDocType(type.GetElementType()) }
                : null;

            if (type == typeof(Vector3) // Isomoprhismus
             || TupleExtensions.IsTupleType(type, out _)) // Dictionary does not like Generics
                type = typeof(Tuple);

            if (type.HasElementType) // pretend its a List
                type = typeof(List<>); // Dictionary does not like Generics


            if (FuncExtensions.IsFuncType(type, out _))
                return new FUNTYPE(args[..^1], args[^1]);

            if (MMTConstants.TYPE_TO_OMS.TryGetValue(type, out string uri))
                return args != null
                    ? new OMA(new OMS(uri), args)
                    : new OMS(uri);

            throw new NotImplementedException($"For Type {type}");
        }

        protected internal abstract Type ToType(Type[] args, (string name, Type type)[] bound_params);
        public Type ToType()
            => ToType(new Type[0], new (string name, Type type)[0]);

        public static bool Equivalent(SOMDoc sd1, SOMDoc sd2)
            => sd1 != null && sd2 != null
            && sd1.Equivalent(sd2);

        public abstract bool Equivalent(SOMDoc sd2);

        public Func<object[], object[]> PartialInvokeCastingLambdaExpression(out Type[] signature, object[] callArgs = null, bool[] useArgs = null)
        {
            Expression to_compile;
            List<Type> signature_list;
            LambdaExpression lambda_orig = GetLambdaExpression();

            if (FuncExtensions.IsFuncType(lambda_orig.ReturnType, out int signature_count))
            {
                signature_list = lambda_orig.ReturnType.GetGenericArguments().ToList();
                to_compile = lambda_orig.Body;
            }
            else
            {
                signature_count = lambda_orig.Parameters.Count + 1;
                signature_list =
                    Enumerable.Append(
                        lambda_orig.Parameters.Select(p => p.Type),
                        lambda_orig.ReturnType)
                    .ToList();
                to_compile = lambda_orig;
            }

            ParameterExpression object_arr = Expression.Parameter(typeof(object[]), "PARAMS_Arr");
            Expression[] cast_new_to_signature = new Expression[signature_count - 1];

            List<int> removed = new();
            for (int i = 0; i < signature_count - 1; i++)
            {
                if (callArgs != null && callArgs.Length < i
                 && (useArgs == null || (useArgs.Length < i && useArgs[i])))
                {
                    cast_new_to_signature[i] =
                        Expression.Constant(callArgs[i], signature_list[i]);

                    removed.Add(i);
                    continue;
                }

                cast_new_to_signature[i] =
                    Expression.Convert(
                        Expression.ArrayIndex(
                            object_arr,
                            Expression.Constant(i)
                        ),
                        signature_list[i]
                    );
            }
            removed.ForEach(signature_list.RemoveAt);
            signature = signature_list.ToArray();

            LambdaExpression final_expression =
                Expression.Lambda(
                    typeof(object[]).IsAssignableFrom(signature[^1])
                    ? Expression.Invoke(to_compile, cast_new_to_signature)
                    : Expression.NewArrayInit(
                        typeof(object),
                        new Expression[] { Expression.Convert(Expression.Invoke(to_compile, cast_new_to_signature), typeof(object)) }),

                    object_arr
                );

            return final_expression.Compile() as Func<object[], object[]>;
        }

        public LambdaExpression GetLambdaExpression()
            => GetLambdaExpression(new LambdaExpression[0], new LambdaExpression[0], new ParameterExpression[0]);

        protected internal abstract LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params);

        public abstract override string ToString();

        public abstract SOMDoc MapURIs(Dictionary<string, string> old_to_new);

        public abstract string[] GetDependentFactIds();

        #region MakeMMT_OMS_URItoSOMDoc
        public static Vector3 MakeVector3(OMA tuple)
        {
            if (tuple.arguments is not OMF[] xyz
             || xyz.Length < 3)
                throw new FormatException("Argument " + nameof(tuple) + "." + nameof(OMA.arguments)
                    + " expected to be: " + nameof(OMF) + "[3]");

            return new Vector3(xyz[0].@float, xyz[1].@float, xyz[2].@float);
        }

        public static OMA MakeVector3(Vector3 vec)
            => MakeTupel(new[] {
                    new OMF(vec.x),
                    new OMF(vec.y),
                    new OMF(vec.z),
                });

        public static OMA MakeTupel(SOMDoc[] args)
            => new(
                    new OMS(MMTConstants.Tuple),
                    args
                );

        public static OMA MakeShallowList(SOMDoc[] args)
            => new(
                    new OMS(MMTConstants.ListApplicant),
                    args
                );

        public static OMA MakeDeepList(SOMDoc[] args, SOMDoc SOMDoc_type)
        {
            SOMDoc[] end_of_list = new SOMDoc[] {
                new OMA(
                    new OMS(MMTConstants.ListEnd),
                    new[] {
                        SOMDoc_type,
                    }
                ),
                args.Length == 0
                    ? null
                    : args[^1]
            };

            if (args.Length == 0)
                end_of_list = end_of_list[..^0];

            SOMDoc defines = new OMA(
                new OMS(MMTConstants.ListLiteral),
                end_of_list
            );

            for (int i = args.Length - 2; i >= 0; i--)
            {
                defines = new OMA(
                    new OMS(MMTConstants.ListLiteral),
                    new[] {
                        defines,
                        args[i],
                });
            }

            SOMDoc type = new OMA(
                new OMS(MMTConstants.ListType),
                new[] { SOMDoc_type }
            );

            return new OMA(type, new[] { defines });
        }
        #endregion MakeMMT_OMS_URItoSOMDoc
    }

    public abstract class SOMDocCRTP<T> : SOMDoc where T : SOMDocCRTP<T>
    {
        protected SOMDocCRTP() : base() { }

        public override bool Equivalent(SOMDoc sd2)
            => this.GetType() == sd2.GetType() && (this as T).EquivalentWrapped(sd2 as T);

        protected abstract bool EquivalentWrapped(T sd2);

        public override SOMDoc MapURIs(Dictionary<string, string> old_to_new)
            => MapURIsWrapped(old_to_new);

        protected abstract T MapURIsWrapped(Dictionary<string, string> old_to_new);
    }

    public class FUNTYPE : SOMDocCRTP<FUNTYPE>
    {
        public new string kind = "FUNTYPE";

        public SOMDoc[] @params;

        public SOMDoc ret;

        [JsonConstructor]
        public FUNTYPE(SOMDoc[] @params, SOMDoc ret)
        {
            this.@params = @params;
            this.ret = ret;
        }

        public override string ToString()
            => "["
            + string.Join(", ", @params.Select(p => p.ToString()))
            + "] => {"
            + ret.ToString()
            + "}";

        protected override bool EquivalentWrapped(FUNTYPE sd2)
            => @params.Length == sd2.@params.Length
            && @params.Zip(sd2.@params, (a, b) => a.Equivalent(b))
                      .All(b => b)
            && ret.Equivalent(sd2.ret);

        protected override FUNTYPE MapURIsWrapped(Dictionary<string, string> old_to_new)
            => new(@params.Select(p => p.MapURIs(old_to_new)).ToArray()
                 , ret.MapURIs(old_to_new));

        public override string[] GetDependentFactIds()
            => new string[0];

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
        {
            throw new NotImplementedException();
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
        {
            throw new NotImplementedException();
        }

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => FuncExtensions.CreateFuncType(Enumerable.Append(@params.Select(p => p.ToType()), ret.ToType()).ToArray());
    }

    public class FUN : SOMDocCRTP<FUN>
    {
        public new string kind = "FUN";

        public Param[] @params;

        public SOMDoc body;

        [JsonConstructor]
        public FUN(Param[] @params, SOMDoc body)
        {
            this.@params = @params;
            this.body = body;
        }

        public class Param
        {
            public string name;
            [JsonProperty("tp")]
            public SOMDoc type;

            [JsonConstructor]
            public Param(string name, SOMDoc tp)
            {
                this.name = name;
                this.type = tp;
            }

            public override string ToString()
                => name + "(" + type.ToString() + ")";
        }

        public override string ToString()
            => "["
            + string.Join(", ", @params.Select(p => p.ToString()))
            + "] => {"
            + body.ToString()
            + "}";

        protected override bool EquivalentWrapped(FUN sd2)
            => @params.Length == sd2.@params.Length
            && @params.Zip(sd2.@params, (a, b) => a.name.Equals(b.name)
                                               && a.type.Equivalent(b.type))
                      .All(b => b)
            && body.Equivalent(sd2.body);

        protected override FUN MapURIsWrapped(Dictionary<string, string> old_to_new)
            => new(@params.Select(p => new Param(p.name, p.type.MapURIs(old_to_new))).ToArray()
                 , body.MapURIs(old_to_new));

        public override string[] GetDependentFactIds()
            => body.GetDependentFactIds();

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
        {
            ParameterExpression[] local_param =
                @params.Select(p => Expression.Parameter(p.type.ToType(), p.name)).ToArray();


            LambdaExpression body_lambda =
                body.GetLambdaExpression(
                    lambda_applicant,
                    lambda_arguments,
                    bound_params.ShallowCloneAppend(local_param)
                );

            return
                Expression.Lambda(
                    Expression.Lambda(
                        body_lambda.Body,
                        local_param
                    ),
                    bound_params
                );
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, Param[] bound_params)
        {
            Param[] bind_me = bound_params.ShallowCloneAppend(@params);

            return new FUNTYPE(
                @params.Select(p => p.type).ToArray(),
                body.SOMDocType(new SOMDoc[0], bind_me)
            );
        }

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
        {
            (string name, Type)[] bind_me = bound_params
                .AppendRange(@params.Select(p => (p.name, p.type.ToType())))
                .ToArray();

            return SOMDocType().ToType(new Type[0], bind_me);
        }
    }

    public class OMV : SOMDocCRTP<OMV>
    {
        public new string kind = "VAR";

        public string name;

        [JsonConstructor]
        public OMV(string name) : base()
        {
            this.name = name;
        }

        protected override bool EquivalentWrapped(OMV sd2)
            => this.name == sd2.name;

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
        {
            FUN.Param p = bound_params.FirstOrDefault(param => param.name.Equals(name))
                ?? throw new FormatException($"Unable to find {nameof(FUN.Param)} for {nameof(OMV)} with name: {name}");

            return p.type;
        }

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
        {
            (string name, Type type) p = bound_params.FirstOrDefault(param => param.name.Equals(name));

            if (p == default)
                throw new FormatException($"Unable to find {nameof(FUN.Param)} for {nameof(OMV)} with name: {name}");

            return p.type;
        }

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
        {
            ParameterExpression v = bound_params.FirstOrDefault(param => param.Name.Equals(name));
            if (v == null)
            {
                Debug.LogErrorFormat("Unable to find {0} for {1} with name: {2}", nameof(FUN.Param), nameof(OMV), name);
                return Expression.Lambda(Expression.Empty(), null);
            }
            else
                return Expression.Lambda(v, new[] { v });
        }

        public override string ToString()
            => "Variable_" + "(" + name + ")";

        protected override OMV MapURIsWrapped(Dictionary<string, string> old_to_new)
            => (OMV)this.MemberwiseClone();

        public override string[] GetDependentFactIds()
            => new string[0];
    }

    public class OMA : SOMDocCRTP<OMA>
    {
        public new string kind = "OMA";

        public SOMDoc applicant;
        public SOMDoc[] arguments;

        [JsonConstructor]
        public OMA(SOMDoc applicant, SOMDoc[] arguments) : base()
        {
            this.applicant = applicant;
            this.arguments = arguments;
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
            => applicant.SOMDocType(
                arguments.Select(a => a.SOMDocType(new SOMDoc[0], bound_params)).ToArray(),
                bound_params
            );

        protected override bool EquivalentWrapped(OMA sd2)
            => Equivalent(this.applicant, sd2.applicant)
            && this.arguments
                .Zip(sd2.arguments, (arg1, arg2) => Equivalent(arg1, arg2))
                .All(b => b);

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_arguments_paps, LambdaExpression[] lambda_arguments_grands, ParameterExpression[] bound_params)
            => applicant.GetLambdaExpression(
                arguments.Select(arg => arg.GetLambdaExpression(new LambdaExpression[0], new LambdaExpression[0], bound_params)).ToArray(),
                lambda_arguments_paps,
                bound_params
            );

        public override string ToString()
            => applicant.ToString() + "(" + string.Join(", ", arguments.Select(a => a.ToString())) + ")";

        protected override OMA MapURIsWrapped(Dictionary<string, string> old_to_new)
            => new OMA(
                applicant.MapURIs(old_to_new),
                arguments.Select(arg => arg.MapURIs(old_to_new)).ToArray()
            );

        public override string[] GetDependentFactIds()
            => applicant.GetDependentFactIds()
            .AppendRange(arguments.SelectMany(arg => arg.GetDependentFactIds()))
            .ToArray();

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => applicant.ToType(
                arguments.Select(arg => arg.ToType(new Type[0], bound_params)).ToArray(),
                bound_params
            );
    }

    public class OMS : SOMDocCRTP<OMS>
    {
        public new string kind = "OMS";

        public string uri;

        [JsonConstructor]
        public OMS(string uri) : base()
        {
            this.uri = uri;
        }

        protected override bool EquivalentWrapped(OMS sd2)
        {
            bool
                b1 = FactRecorder.AllFacts.TryGetValue(uri, out Fact f1),
                b2 = FactRecorder.AllFacts.TryGetValue(sd2.uri, out Fact f2);

            return (b1 == b2 && uri == sd2.uri)
                || (b1 && b2 && f1.Equivalent(f2));
        }


        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_arguments_paps, LambdaExpression[] lambda_arguments_grands, ParameterExpression[] bound_params)
            => SOMDocToLambdaExpression<float>.MakeLambdaExpression(uri, lambda_arguments_paps, lambda_arguments_grands, bound_params);

        public override string ToString()
            => uri;

        protected override OMS MapURIsWrapped(Dictionary<string, string> old_to_new)
        {
            if (!old_to_new.TryGetValue(uri, out string new_uri))
                new_uri = uri;

            return new OMS(new_uri);
        }

        public override string[] GetDependentFactIds()
            => FactRecorder.AllFacts.ContainsKey(uri)
            ? new string[] { uri }
            : new string[0];

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
        {
            if (FactRecorder.AllFacts.TryGetValue(uri, out Fact found))
                return found.GetType();

            if (MMTConstants.HeterogenApplication_TO_TypeOF.TryGetValue(uri, out string s_type))
            {
                Type type = MMTConstants.OMS_TO_TYPE[s_type];

                if (type.Equals(typeof(Tuple)))
                { // authors note: Not a fan, but has to be done somewhwere...
                    if (args.Length == 3
                     && args.All(t => t == typeof(float)))
                        return typeof(Vector3);

                    type = TupleExtensions.GetGenericTupleType(args.Length);
                }
                else
                if (type.Equals(typeof(Func<>)))
                    return FuncExtensions.CreateFuncType(args);

                return type
                    .MakeGenericType(args);
            }

            if (MMTConstants.HomogenApplication_TO_TypeOF.TryGetValue(uri, out s_type))
                return MMTConstants.OMS_TO_TYPE[s_type]
                    .MakeGenericType(args[0]);

            if (MMTConstants.URI_TO_TypeOF.TryGetValue(uri, out s_type))
                return MMTConstants.OMS_TO_TYPE[s_type];

            if (MMTConstants.OMS_TO_TYPE.TryGetValue(uri, out Type t_type))
                return t_type;

            throw new NotImplementedException();
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
        {
            string _uri = uri;
            bool use_uri;
            if (use_uri = MMTConstants.OMS_TO_TYPE.TryGetValue(_uri, out Type tmp_type))
                _uri = MMTConstants.TYPE_TO_OMS[tmp_type];

            if (FactRecorder.AllFacts.TryGetValue(_uri, out Fact found))
                return new OMS(MMTConstants.TYPE_TO_OMS[found.GetType()]);

            if (MMTConstants.HeterogenApplication_TO_TypeOF.TryGetValue(_uri, out string type))
                return new OMA(
                    new OMS(type),
                    args
                );

            if (MMTConstants.HomogenApplication_TO_TypeOF.TryGetValue(_uri, out type))
                return new OMA(
                    new OMS(type),
                    new[] { args[0] }
                );

            if (MMTConstants.URI_TO_TypeOF.TryGetValue(_uri, out type))
                return new OMS(type);

            if (use_uri)
                return SOMDocType(tmp_type);
            //return new OMS(_uri);

            throw new NotImplementedException(uri);
        }
    }

    public class OMSTR : SOMDocCRTP<OMSTR>
    {
        public new string kind = "OMSTR";

        [JsonProperty("float")]
        public string s;

        [JsonConstructor]
        public OMSTR(string s) : base()
        {
            this.s = s;
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
            => throw new NotImplementedException();

        protected override bool EquivalentWrapped(OMSTR sd2)
            => this.s == sd2.s;

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            => Expression.Lambda(Expression.Constant(s, typeof(string)), null);

        public override string ToString()
            => s;

        protected override OMSTR MapURIsWrapped(Dictionary<string, string> old_to_new)
            => (OMSTR)this.MemberwiseClone();

        public override string[] GetDependentFactIds()
            => new string[0];

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => typeof(string);
    }

    public class OMF : SOMDocCRTP<OMF>
    {
        public new string kind = "OMF";

        [JsonProperty("float")]
        public float @float;

        [JsonConstructor]
        public OMF(float f) : base()
        {
            this.@float = f;
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
            => new OMS(MMTConstants.RealLit);

        protected override bool EquivalentWrapped(OMF sd2)
            => Mathf.Approximately(@float, sd2.@float);

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            => Expression.Lambda(Expression.Constant(@float, typeof(float)), null);

        public override string ToString()
            => @float.ToString();

        protected override OMF MapURIsWrapped(Dictionary<string, string> old_to_new)
            => (OMF)this.MemberwiseClone();

        public override string[] GetDependentFactIds()
            => new string[0];

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => typeof(float);
    }

    public class RAW : SOMDocCRTP<RAW>
    {
        public new string kind = "RAW";

        public string xml;

        [JsonConstructor]
        public RAW(string xml) : base()
        {
            this.xml = xml;
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
            => throw new NotSupportedException();

        protected override RAW MapURIsWrapped(Dictionary<string, string> old_to_new)
        {
            string copy = xml;
            foreach (KeyValuePair<string, string> KeyVal in old_to_new)
                copy = copy.Replace(KeyVal.Key, KeyVal.Value);

            return new RAW(copy);
        }

        public override string[] GetDependentFactIds()
            => new string[0];

        public override string ToString()
            => xml;

        protected override bool EquivalentWrapped(RAW sd2)
            => throw new NotImplementedException(); //xml == sd2.xml; // only exact

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            => throw new NotSupportedException();

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => throw new NotSupportedException();
    }

    // temporary fix if Serialzation is broken
    public class FallbackWrapper : SOMDocCRTP<FallbackWrapper>
    {
        public SOMDoc SFunction;

        [JsonConstructor]
        public FallbackWrapper(SOMDoc SFunction)
        {
            this.SFunction = SFunction;
        }

        public override string ToString()
            => SFunction.ToString();

        protected override bool EquivalentWrapped(FallbackWrapper sd2)
            => SFunction.Equivalent(sd2);

        protected override FallbackWrapper MapURIsWrapped(Dictionary<string, string> old_to_new)
            => new(SFunction.MapURIs(old_to_new));

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            => SFunction.GetLambdaExpression(lambda_applicant, lambda_arguments, bound_params);

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
            => SFunction.SOMDocType(args, bound_params);

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => SFunction.ToType(args, bound_params);

        public override string[] GetDependentFactIds()
            => SFunction.GetDependentFactIds();
    }

    // internal use only
    public class OMC<T> : SOMDocCRTP<OMC<T>>
    {
        public new string kind = "OMC<" + typeof(T) + ">";

        public T value;

        [JsonConstructor]
        public OMC(T value) : base()
        {
            this.value = value;
        }

        protected internal override SOMDoc SOMDocType(SOMDoc[] args, FUN.Param[] bound_params)
            => SOMDocType(typeof(T));

        protected override bool EquivalentWrapped(OMC<T> sd2)
        {
            Debug.LogWarning("Cannot check Equivalency for " + this.GetType() + "; only whether it's exact!");
            return this.value.Equals(value);
        }

        protected internal override LambdaExpression GetLambdaExpression(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            => Expression.Lambda(Expression.Constant(value, typeof(T)), null);

        public override string ToString()
            => "C_" + typeof(T) + "(" + value.ToString() + ")";

        protected override OMC<T> MapURIsWrapped(Dictionary<string, string> old_to_new)
            => (OMC<T>)this.MemberwiseClone();

        public override string[] GetDependentFactIds()
            => new string[0];

        protected internal override Type ToType(Type[] args, (string name, Type type)[] bound_params)
            => typeof(T);
    }
}