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

/// <summary>
/// Represents a function taking Parameters (<typeparam name="T0">) and Returning <typeparam name="TResult">
/// </summary>
public class FunctionFact<T0, TResult> : FactWrappedCRTP<FunctionFact<T0, TResult>>
{
    /// \copydoc Fact.s_type
    [JsonProperty]
    protected new string s_type = "FunctionFact<" + typeof(T0) + ", " + typeof(TResult) + ">";

    public SOMDoc Function_SOMDoc;

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

    [JsonIgnore]
    public Func<T0, TResult> Function;

    //TODO
    public (T0 t_0, T0 t_n) Domain = (default, default);

    /// <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, (T0, T0) domain, FactOrganizer organizer) : base(organizer)
    {
        this.Function_SOMDoc = Function_SOMDoc;
        this.Domain = domain;

        this.Function_expression = this.Function_SOMDoc.GetReducedLambdaExpression();
        ////TODO: catch
        //string debug_tostring = Function_expression.ToString();
        //dynamic debug_function = Function_expression.Compile();
        this.Function = this.Function_expression.Compile() as Func<T0, TResult>;

        //ducktaped:
        _URI = this.Function_expression.ToString() + domain.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;

        RecalculateTransform();
        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, (T0, T0) domain, string uri, FactOrganizer organizer) : base(organizer)
    {
        this.Function_SOMDoc = Function_SOMDoc;
        this.Domain = domain;

        this.Function_expression = Function_SOMDoc.GetReducedLambdaExpression();
        ////TODO: catch
        //string debug_tostring = Function_expression.ToString();
        //dynamic debug_function = Function_expression.Compile();
        this.Function = this.Function_expression.Compile() as Func<T0, TResult>;

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

        RecalculateTransform();
    }

    protected override void RecalculateTransform() { }

    /// \copydoc Fact.parseFact(Scroll.ScrollFact)
    public new static FunctionFact<T0, TResult> parseFact(Scroll.ScrollFact fact)
    {// TODO Correctness
        string uri = fact.@ref.uri;
        OMA df = (OMA)((Scroll.ScrollSymbolFact)fact).df;

        if (df == null)
            return null;

        string parse_id = df.ToString();
        if (!ParsingDictionary.parseTermsToId.ContainsKey(parse_id))
            ParsingDictionary.parseTermsToId[parse_id] = uri;

        return new FunctionFact<T0, TResult>(df, (default, default), uri, StageStatic.stage.factState);
    }

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

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

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

    /// \copydoc Fact.instantiateDisplay(GameObject, Transform)
    public override GameObject instantiateDisplay(GameObject prefab, Transform transform)
    {
        var obj = GameObject.Instantiate(prefab, Vector3.zero, Quaternion.identity, transform);
        //obj.transform.GetChild(0).gameObject.GetComponent<TextMeshProUGUI>().text = this.Label;
        //obj.GetComponent<FactWrapper>().fact = this;
        return obj;
    }

    /// \copydoc Fact.EquivalentWrapped
    protected override bool EquivalentWrapped(FunctionFact<T0, TResult> f1, FunctionFact<T0, TResult> f2)
        //=> f1.Function_SOMDoc.Equivalent(f2.Function_SOMDoc);
        //  && f1.domain.Equals(f2.domain); // no! => exact instead of similar => CRTP
        => false;

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

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

/// <summary>
/// Implements CRTP for <see cref="FunctionFact"/>; Escalates constructors;
/// </summary>
/// <typeparam name="CRTP">class, which inherits from FunctionFactCRTP</typeparam>
public abstract class FunctionFactCRTP<CRTP, T0, TResult> : FunctionFact<T0, TResult> where CRTP : FunctionFactCRTP<CRTP, T0, TResult>
{
    /// <summary> \copydoc FunctionFact.FunctionFact </summary>
    protected FunctionFactCRTP() : base() { }

    /// <summary> \copydoc FunctionFact.FunctionFact(SOMDoc, Tuple{T0, T0}, FactOrganizer) </summary>
    protected FunctionFactCRTP(SOMDoc Function_MMT, (T0, T0) domain, FactOrganizer organizer) : base(Function_MMT, domain, organizer) { }

    /// <summary> \copydoc FunctionFact.FunctionFact(LambdaExpression, string, FactOrganizer) </summary>
    protected FunctionFactCRTP(SOMDoc Function_MMT, (T0, T0) domain, string uri, FactOrganizer organizer) : base(Function_MMT, domain, uri, organizer) { }

    /// \copydoc Fact.Equivalent(Fact, Fact)
    protected override bool EquivalentWrapped(FunctionFact<T0, TResult> f1, FunctionFact<T0, TResult> f2)
        => EquivalentWrapped((CRTP)f1, (CRTP)f2)
        && base.EquivalentWrapped(f1, f2);

    /// <summary>CRTP step of <see cref="EquivalentWrapped(FunctionFact{T0, TResult}, FunctionFact{T0, TResult})"/></summary>
    protected abstract bool EquivalentWrapped(CRTP f1, CRTP f2);
}

/// <summary>
/// Used to overrite FunctionFact.EquivalentWrapped for special case T0 = float
/// </summary>
public class FunctionFactFloat<TResult> : FunctionFactCRTP<FunctionFactFloat<TResult>, float, TResult>
{
    /// <summary> \copydoc FunctionFact.FunctionFact </summary>
    public FunctionFactFloat() : base() { }

    /// <summary> \copydoc FunctionFact.FunctionFact(SOMDoc, Tuple{T0, T0}, FactOrganizer) </summary>
    public FunctionFactFloat(SOMDoc Function_MMT, (float, float) domain, FactOrganizer organizer) : base(Function_MMT, domain, organizer) { }

    /// <summary> \copydoc FunctionFact.FunctionFact(LambdaExpression, string, FactOrganizer) </summary>
    public FunctionFactFloat(SOMDoc Function_MMT, (float, float) domain, string uri, FactOrganizer organizer) : base(Function_MMT, domain, uri, organizer) { }

    /// \copydoc Fact.EquivalentWrapped
    protected override bool EquivalentWrapped(FunctionFactFloat<TResult> f1, FunctionFactFloat<TResult> f2)
        => f1.Function_SOMDoc.Equivalent(f2.Function_SOMDoc)
        && f1.Domain.t_0.IsApproximatelyEqual(f2.Domain.t_0)
        && f1.Domain.t_n.IsApproximatelyEqual(f2.Domain.t_n);

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

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

    public string[] funcids;

    [JsonIgnore]
    public Fact Fact { get => FactOrganizer.AllFacts[fid]; }
    [JsonIgnore]
    public FunctionFact<float, Vector3>[] FunctionFacts { 
        get => funcids.Select(f => FactOrganizer.AllFacts[f] as FunctionFact<float, Vector3>).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);

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

        this.funcids = new string[funcids.Length];
        funcids.CopyTo(this.funcids, 0);

        //TODO: call MMT, set URI
        _URI = Fact.Id + "{" + string.Join(", ", FunctionFacts.Select(f => f.Id)) + "}";

        RecalculateTransform();
    }

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

        this.funcids = new string[funcids.Length];
        funcids.CopyTo(this.funcids, 0);

        _URI = uri;

        RecalculateTransform();
    }

    public new static AttachedPositionFunction parseFact(Scroll.ScrollFact fact)
    {// TODO Correctness
        string uri = fact.@ref.uri;
        OMA df = (OMA)((Scroll.ScrollSymbolFact)fact).df;

        if (df == null)
            return null;

        string parse_id = df.ToString();
        if (!ParsingDictionary.parseTermsToId.ContainsKey(parse_id))
            ParsingDictionary.parseTermsToId[parse_id] = uri;

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

    public override string[] getDependentFactIds()
    {
        string[] ret = new string[1 + funcids.Length];
        funcids.CopyTo(ret, 1);
        ret[0] = fid;

        return ret;
    }

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

    public override bool hasDependentFacts()
        => true;

    public override GameObject instantiateDisplay(GameObject prefab, Transform transform)
    {
        var obj = GameObject.Instantiate(prefab, Vector3.zero, Quaternion.identity, transform);
        //obj.transform.GetChild(0).gameObject.GetComponent<TextMeshProUGUI>().text = this.Label;
        //obj.GetComponent<FactWrapper>().fact = this;
        return obj;
    }

    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.funcids.Select(id => old_to_new[id]).ToArray(), organizer);

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