using System;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using static SOMDocManager;
using System.Linq.Expressions;
using System.Linq;

public class FunctionCallFact : FactWrappedCRTP<FunctionCallFact>
{
    public string func_id;

    public string arg_func_id;

    public (float t_0, float t_n) Domain;

    [JsonIgnore]
    public FunctionFact Function_in => (FunctionFact)FactOrganizer.AllFacts[func_id];
    [JsonIgnore]
    public FunctionFact Function_args => (FunctionFact)FactOrganizer.AllFacts[arg_func_id];


    public FunctionCallFact() : base() { }

    public FunctionCallFact(string func_id, string arg_func_id, (float t_0, float t_n) Domain, FactOrganizer organizer) : base(organizer)
    {
        this.func_id = func_id;
        this.arg_func_id = arg_func_id;
        this.Domain = Domain;

        _URI = func_id.ToString() + arg_func_id.ToString() + Domain.ToString();
    }

    public object[] Call(float t)
    {
        if (t < Domain.t_0 || t > Domain.t_n)
            return null;

        return Function_in.Function(Function_args.Function(new object[] { t }));
    }

    public override bool HasDependentFacts
        => true;

    protected override string[] GetGetDependentFactIds()
        => new[] { func_id, arg_func_id };

    protected override bool EquivalentWrapped(FunctionCallFact f1, FunctionCallFact f2)
        => f1.Domain.t_0.IsApproximatelyEqual(f2.Domain.t_0)
        && f1.Domain.t_n.IsApproximatelyEqual(f2.Domain.t_n)
        && (DependentFactsEquivalent(f1, f2)
            || (f1.Function_in.Equivalent(f2.Function_in)
                && f1.Function_args.Equivalent(f2.Function_args)
        ));

    protected override MMTDeclaration MakeMMTDeclaration()
    {
        throw new NotImplementedException();
    }

    protected override void RecalculateTransform()
    {
        Position = Function_in.Position;
        Rotation = Function_in.Rotation;
        LocalScale = Function_in.LocalScale;
    }

    protected override Fact _ReInitializeMe(Dictionary<string, string> old_to_new, FactOrganizer organizer)
        => new FunctionCallFact(old_to_new[this.func_id], old_to_new[this.arg_func_id], this.Domain, organizer);
}

public class FunctionFact : FactWrappedCRTP<FunctionFact>
{
    public SOMDoc Function_SOMDoc;

    //TODO: doc
    [JsonIgnore]
    public LambdaExpression Function_expression;

    [JsonIgnore]
    public Type[] Signature;

    [JsonIgnore]
    public Func<object[], object[]> Function;

    /// <summary> \copydoc Fact.Fact </summary>
    public FunctionFact() : base() { }

    /// <summary>
    /// Standard Constructor:
    /// Initiates members and creates MMT %Fact Server-Side
    /// </summary>
    /// <param name="Function_SOMDoc">sets <see cref="Function_SOMDoc"/> and contains the Abstract Syntax Tree</param>
    /// <param name="organizer">sets <see cref="Fact._Facts"/></param>
    public FunctionFact(SOMDoc Function_SOMDoc, FactOrganizer organizer) : base(organizer)
    {
        this.Function_SOMDoc = Function_SOMDoc;

        this.Function_expression = this.Function_SOMDoc.PartialInvokeCastingLambdaExpression(out Signature);
        ////TODO: catch
        //string debug_tostring = Function_expression.ToString();
        //Delegate debug_function = Function_expression.Compile();
        this.Function = this.Function_expression.Compile() as Func<object[], object[]>;

        //ducktaped:
        _URI = this.Function_expression.ToString();

        ////TODO: Function_SOMDoc+domain

        //MMTTerm tp = new OMS(JSONManager.MMTURIs.Point);
        //MMTTerm df = new OMA(new OMS(JSONManager.MMTURIs.Tuple), arguments);

        //AddFactResponse.sendAdd(new MMTSymbolDeclaration(Label, tp, df), out _URI);

        //ParsingDictionary.parseTermsToId[df.ToString()] = _URI;

        return;
    }

    /// <summary>
    /// Bypasses initialization of new MMT %Fact by using existend URI, _which is not checked for existence_.
    /// </summary>
    /// <param name="function_expression">sets <see cref="Function_expression"/> and contains the Abstract Syntax Tree</param>
    /// <param name="uri">MMT URI</param>
    /// <param name="organizer">sets <see cref="Fact._Facts"/></param>
    public FunctionFact(SOMDoc Function_SOMDoc, string uri, FactOrganizer organizer) : base(organizer)
    {
        this.Function_SOMDoc = Function_SOMDoc;

        this.Function_expression = Function_SOMDoc.PartialInvokeCastingLambdaExpression(out Signature);
        ////TODO: catch
        //string debug_tostring = Function_expression.ToString();
        //dynamic debug_function = Function_expression.Compile();
        this.Function = this.Function_expression.Compile() as Func<object[], object[]>;

        this._URI = uri;
        _ = this.Label;
    }

    protected override void RecalculateTransform() { }

    /// \copydoc Fact.parseFact(ScrollFact)
    public new static FunctionFact parseFact(MMTDeclaration fact)
    {// TODO Correctness
        if (((MMTSymbolDeclaration)fact).defines is not OMA defines)
            return null;

        ParsingDictionary.parseTermsToId.TryAdd(defines.ToString(), fact.@ref.uri);

        return new FunctionFact(defines, fact.@ref.uri, StageStatic.stage.factState);
    }

    /// \copydoc Fact.hasDependentFacts
    public override bool HasDependentFacts
        => false;

    /// \copydoc Fact.getDependentFactIds
    protected override string[] GetGetDependentFactIds()
        => new string[] { };

    /// \copydoc Fact.GetHashCode
    public override int GetHashCode()
        => Function_expression.GetHashCode();

    /// \copydoc Fact.EquivalentWrapped
    protected override bool EquivalentWrapped(FunctionFact f1, FunctionFact f2)
        => f1.Function_SOMDoc.Equivalent(f2.Function_SOMDoc);

    protected override Fact _ReInitializeMe(Dictionary<string, string> old_to_new, FactOrganizer organizer)
        => new FunctionFact(this.Function_SOMDoc.MapURIs(old_to_new), organizer);

    protected override MMTDeclaration MakeMMTDeclaration()
    {
        throw new NotImplementedException();
    }
}

public class AttachedPositionFunction : FactWrappedCRTP<AttachedPositionFunction>
{
    public string fid;

    public string[] func_call_ids;

    [JsonIgnore]
    public Fact Fact => FactOrganizer.AllFacts[fid];

    [JsonIgnore]
    public FunctionCallFact[] FunctionFacts
        => func_call_ids.Select(f => FactOrganizer.AllFacts[f] as FunctionCallFact).ToArray();

    /// <summary>\copydoc Fact.Fact()</summary>
    public AttachedPositionFunction() : base() { }

    /// <summary>\copydoc Fact.Fact(FactOrganizer)</summary>
    public AttachedPositionFunction(string fid, string[] funcids, FactOrganizer organizer) : base(organizer)
    {
        init(fid, funcids);
        //TODO: call MMT, set URI
        _URI = Fact.Id + "{" + string.Join(", ", FunctionFacts.Select(f => f.Id)) + "}";
    }

    private void init(string fid, string[] funcids)
    {
        this.fid = fid;
        this.func_call_ids = funcids;
    }

    protected AttachedPositionFunction(string fid, string[] funcids, string uri, FactOrganizer organizer) : base(organizer)
    {
        init(fid, funcids);

        _URI = uri;
    }

    public new static AttachedPositionFunction parseFact(MMTDeclaration fact)
    {// TODO Correctness
        if (((MMTSymbolDeclaration)fact).defines is not OMA defines)
            return null;

        ParsingDictionary.parseTermsToId.TryAdd(defines.ToString(), fact.@ref.uri);

        return new AttachedPositionFunction(default, default, fact.@ref.uri, StageStatic.stage.factState);
    }

    public override bool HasDependentFacts
        => true;

    protected override string[] GetGetDependentFactIds()
        => new string[] { fid }.ShallowCloneAppend(func_call_ids);

    public override int GetHashCode()
        => Fact.GetHashCode() ^ FunctionFacts.GetHashCode();

    protected override bool EquivalentWrapped(AttachedPositionFunction f1, AttachedPositionFunction f2)
        => DependentFactsEquivalent(f1, f2);

    protected override void RecalculateTransform()
    {
        Position = Fact.Position;
        Rotation = Fact.Rotation;
        LocalScale = Fact.LocalScale;
    }

    protected override Fact _ReInitializeMe(Dictionary<string, string> old_to_new, FactOrganizer organizer)
        => new AttachedPositionFunction(old_to_new[this.fid], this.func_call_ids.Select(id => old_to_new[id]).ToArray(), organizer);

    protected override MMTDeclaration MakeMMTDeclaration()
    {
        throw new NotImplementedException();
    }
}