using System;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using System.Linq;
using REST_JSON_API;

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)FactRecorder.AllFacts[func_id];

    [JsonIgnore]
    public FunctionFact Function_args
        => (FunctionFact)FactRecorder.AllFacts[arg_func_id];


    public FunctionCallFact() : base() { }

    public FunctionCallFact(string func_id, string arg_func_id, (float t_0, float t_n) Domain, FactRecorder 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[] GetDependentFactIds()
        => new[] { func_id, arg_func_id };

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

    public override MMTFact 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, FactRecorder 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;

    [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, FactRecorder organizer) : base(organizer)
    {
        this.Function_SOMDoc = Function_SOMDoc;

        this.Function = this.Function_SOMDoc.PartialInvokeCastingLambdaExpression(out Signature);

        //ducktaped:
        _URI = this.Function_SOMDoc.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, FactRecorder organizer) : base(organizer)
    {
        this.Function_SOMDoc = Function_SOMDoc;

        this.Function = Function_SOMDoc.PartialInvokeCastingLambdaExpression(out Signature);

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

    protected override void RecalculateTransform() { }

    /// \copydoc Fact.parseFact(ScrollFact)
    public new static FunctionFact parseFact(MMTFact fact)
    {// TODO Correctness

        if (((MMTGeneralFact)fact).defines is not FUN fun
            && (((MMTGeneralFact)fact).defines is not FallbackWrapper SFW 
            || (fun = SFW.SFunction as FUN) == null)
        )
            return null;

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

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

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

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

    /// \copydoc Fact.GetHashCode
    public override int GetHashCode()
        => Function.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, FactRecorder organizer)
        => new FunctionFact(this.Function_SOMDoc.MapURIs(old_to_new), organizer);

    public override MMTFact MakeMMTDeclaration() 
        => new MMTGeneralFact(
            Label, 
            SOMDoc.SOMDocType(FuncExtensions.CreateFuncType(Signature)), 
            Function_SOMDoc);
}

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

    public string[] func_calls_ids;

    //public string func_calls_list_id;

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

    //[JsonIgnore]
    //public ListFact FunctionCallFactsList => (ListFact) FactOrganizer.AllFacts[func_calls_list_id];

    //[JsonIgnore]
    //public string[] FuncCallIds => FunctionCallFactsList.lids;

    [JsonIgnore]
    public FunctionCallFact[] FunctionCallFacts
        => func_calls_ids.Select(f => FactRecorder.AllFacts[f] as FunctionCallFact).ToArray();
    //=> FuncCallIds.Select(f => FactOrganizer.AllFacts[f] as FunctionCallFact).ToArray();

    /// <summary>\copydoc Fact.Fact()</summary>
    public AttachedPositionFunction() : base()
    {
        func_calls_ids = new string[0];
    }

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

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

    private void init(string fid, string[] func_calls_ids, FactRecorder organizer)
    {
        this.fid = fid;
        this.func_calls_ids = func_calls_ids;
    }

    //private void init(string fid, string[] funcids, FactOrganizer organizer)
    //{
    //    this.fid = fid;
    //    func_calls_list_id = organizer.Add(new ListFact(funcids, typeof(FunctionCallFact), organizer), out bool _, false, null, null);
    //}

    //private void init(string fid, string func_calls_list_id)
    //{
    //    this.fid = fid;
    //    this.func_calls_list_id = func_calls_list_id;
    //}

    protected AttachedPositionFunction(string fid, string[] funcids, string uri, FactRecorder organizer) : base(organizer)
    {
        init(fid, funcids, organizer);
        _URI = uri;
    }

    //protected AttachedPositionFunction(string fid, string func_calls_list_id, string uri, FactOrganizer organizer) : base(organizer)
    //{
    //    init(fid, func_calls_list_id);
    //    _URI = uri;
    //}

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

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

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

    public override MMTFact MakeMMTDeclaration()
    {
        //SOMDoc[] defines  ??
        //SOMDoc type = new OMS(MMT_OMS_URI.Tuple);
        throw new NotImplementedException();
    }

    public override bool HasDependentFacts
        => true;

    protected override string[] GetDependentFactIds()
        => new string[] { fid }.ShallowCloneAppend(func_calls_ids);
    //=> new string[] { fid, func_calls_list_id };

    public override int GetHashCode()
        => Fact.GetHashCode() ^ FunctionCallFacts.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, FactRecorder organizer)
        // => new AttachedPositionFunction(old_to_new[this.fid], old_to_new[func_calls_list_id], organizer);
        => new AttachedPositionFunction(old_to_new[this.fid], this.func_calls_ids.Select(id => old_to_new[id]).ToArray(), organizer);
}