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

/// <summary>
/// Point in 3D Space
/// </summary>
public class SquareFact : FactWrappedCRTP<SquareFact>
{


    //used points
    public string PidA, PidB, PidC;

    public Vector3 A, B, C, D;



    public PointFact GetA {get =>  (PointFact)FactRecorder.AllFacts[PidA];}
    public PointFact GetB {get =>  (PointFact)FactRecorder.AllFacts[PidB];}
    public PointFact GetC {get =>  (PointFact)FactRecorder.AllFacts[PidC];}
    protected void calculate_vectors(){

        A = ((PointFact)FactRecorder.AllFacts[PidA]).Point + Vector3.zero;
        B = ((PointFact)FactRecorder.AllFacts[PidB]).Point + Vector3.zero;
        C = ((PointFact)FactRecorder.AllFacts[PidC]).Point + Vector3.zero;
        D = (A - B) + C;

        Vector3 scale = new Vector3(Vector3.Distance(B, C), Vector3.Distance(A, B), 1.0F);
        
        LocalScale = scale * 0.5F;


        Position =  B + 0.5F*((A-B) + (C-B));

        //Vector3 normal = Vector3.Cross((A-B), (C-B)); s
        /*
         Vector3 arbitary_not_normal = normal == Vector3.forward ? Vector3.right : Vector3.forward;
        Vector3 forward = Vector3.Cross(arbitary_not_normal, normal);

        Rotation = Quaternion.LookRotation(forward, normal);
        */

        /*
        //angle rotation around y axis
        Vector3 AB = A - B;
        Vector3 BC = C - B;

        // Calculate the angle between AB and BC around the Y-axis
        float yAxisRotation = Mathf.Atan2(AB.z, AB.x) * Mathf.Rad2Deg;
        float xAxisRotation = Mathf.Atan2(BC.z, BC.y) * Mathf.Rad2Deg;
        float zAxisRotation = Mathf.Atan2(AB.y, AB.x) * Mathf.Rad2Deg;

        Vector3 rotationVector = new Vector3(0, yAxisRotation, 0);
        */

        //angle rotation around y axis

        /*
        float distABx = (A.x - B.x);
        float distABy = (A.y - B.y);
        float distABz = (A.z - B.z);

        float distBCx = (C.x - B.x);
        float distBCy = (C.y - B.y);
        float distBCz = (C.z - B.z);

        float ERROR = 1E-5F;

        float xAxisRotation = Mathf.Rad2Deg* - Mathf.Atan(distBCy / distBCz);
        float yAxisRotation = -(Mathf.Abs((distABx - distABz)) < ERROR ? 45.0F :  Mathf.Rad2Deg*Mathf.Atan(distABx / distABz));
        float zAxisRotation = Mathf.Rad2Deg*Mathf.Atan(distABy / distABx);

        Vector3 rotationVector = new Vector3(xAxisRotation, yAxisRotation, zAxisRotation);

        Rotation = Quaternion.Euler(rotationVector);

        */

        /*
        Vector3 forward = (from + to).normalized;

        if (up.sqrMagnitude < Math3d.vectorPrecission)
        { //Angle is 180° (or 0°)
            Vector3 from_arbitary = from.normalized == Vector3.forward ? Vector3.right : Vector3.forward;
            up = Vector3.Cross(from_arbitary, to);
            forward = Vector3.Cross(up, to);
        }

        Vector3 upy0 = new Vector3(up.x, 0, up.z);
        Rotation = Quaternion.LookRotation(upy0, up);

        Vector3 from = (A - B).normalized;
        Vector3 to = (C - B).normalized;
        Vector3 up = Vector3.Cross(from, to);

        Rotation = Quaternion.LookRotation(up, new Vector3(0.0F, 0.0F, 1.0F));

        Vector3 normal = Vector3.Cross((A-B), (B-C));
        lookObject.transform.position = normal + Position;
        */

        Rotation = Quaternion.LookRotation(Vector3.Cross((A-B), (C-B)), Vector3.up);
        //Rotation = Quaternion.LookRotation(forward, new Vector3(1.0F, 0.0F, 0.0F));
        
    }

    public SquareFact() : base(){
        this.PidA = null;
        this.PidB = null;
        this.PidC = null;
    }
    [JsonConstructor]
    public SquareFact( string PidA, string PidB, string PidC) : base()
    {

        this.PidA = PidA;
        this.PidB = PidB;
        this.PidC = PidC;

        calculate_vectors();

    }

    /// <summary>
    /// Bypasses initialization of new MMT %Fact by using existend URI, _which is not checked for existence_.
    /// <see cref="Normal"/> set to <c>Vector3.up</c>
    /// </summary>
    /// <param name="Point">sets <see cref="Point"/></param>
    /// <param name="ServerDefinition">MMT URI as OMS</param>
    public SquareFact(string PidA, string PidB, string PidC, SOMDoc ServerDefinition) : base()
    {
  

        this.PidA = PidA;
        this.PidB = PidB;
        this.PidC = PidC;
        
        this.ServerDefinition = ServerDefinition;

        calculate_vectors();
    
    }

    /// \copydoc Fact.parseFact(ScrollFact)
    public new static IEnumerator parseFact(List<Fact> ret, MMTFact fact)
    {
        if (((MMTGeneralFact)fact).defines is not OMA df)
            yield break;

        OMS pointA, pointB, pointC;

        pointA = (OMS)df.arguments[0];
        pointB = (OMS)df.arguments[1];
        pointC = (OMS)df.arguments[2];

        string PidA = pointA.uri;
        string PidB = pointB.uri;
        string PidC = pointC.uri;

        

        ret.Add(new SquareFact(PidA, PidB, PidC, fact.@ref));

        //ParsingDictionary.parseTermsToId.TryAdd(defines.ToString(), fact.@ref.uri);
        //ret.Add(new PointFact(SOMDoc.MakeVector3(defines), fact.@ref));
    }


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

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

    /// \copydoc Fact.GetHashCode
    /* public override int GetHashCode()
        => this.Point.GetHashCode();
    */
    protected override void RecalculateTransform()
    {
        calculate_vectors();
    }
    /// \copydoc Fact.Equivalent(Fact, Fact)
    protected override bool EquivalentWrapped(SquareFact f1, SquareFact f2){
        
        return (

                    Math3d.IsApproximatelyEqual(f1.A, f2.A)
                && Math3d.IsApproximatelyEqual(f1.B, f2.B)
                && Math3d.IsApproximatelyEqual(f1.C, f2.C)
                && Math3d.IsApproximatelyEqual(f1.D, f2.D)

        );

    }

    protected override Fact _ReInitializeMe(Dictionary<string, string> old_to_new){
        
        return new SquareFact(this.PidA, this.PidB, this.PidC);

    }

    public override MMTFact MakeMMTDeclaration()
    {
        SOMDoc tp = new OMS(MMTConstants.SquareType);

        return new MMTGeneralFact(_LastLabel, tp, Defines());
    }

    public override SOMDoc Defines()
        => new OMA(
                new OMS(MMTConstants.SquareCons),
                new[] {
                        new OMS(PidA),
                        new OMS(PidB),
                        new OMS(PidC),
                }
            );
}