using System;
using System.Collections;
using TMPro;
using UnityEngine;
using static CommunicationEvents;
using static GlobalBehaviour;

public class FactSpawner : MonoBehaviour
{
    public GameObject
        Sphere,
        Line,
        Ray,
        Angle;

    void Start()
    {
        AddFactEvent.AddListener(SpawnFactRepresentation_Wrapped);
        RemoveFactEvent.AddListener(DeleteObject);

        AnimateNonExistingFactEvent.AddListener(animateNonExistingFactTrigger);
    }

    private void SpawnFactRepresentation_Wrapped(Fact fact) => SpawnFactRepresentation(fact);

    public Fact SpawnFactRepresentation(Fact fact)
    {
        Func<Fact, Fact> func = fact switch
        {
            PointFact   => SpawnPoint,
            LineFact    => SpawnLine,
            AngleFact   => SpawnAngle,
            RayFact     => SpawnRay,
            _ => null,
        };

        return func?.Invoke(fact);
    }
  

    public Fact SpawnPoint(Fact pointFact)
    {
        PointFact fact = ((PointFact)pointFact);
     
        GameObject point = GameObject.Instantiate(Sphere);
        point.transform.position = fact.Point;
        point.transform.up = fact.Normal;
        point.GetComponentInChildren<TextMeshPro>().text = fact.Label;
        point.GetComponent<FactObject>().URI = fact.Id;
        fact.Representation = point;
        return fact;
    }

    public Fact SpawnLine(Fact fact)
    {
        LineFact lineFact = ((LineFact)fact);

        PointFact pointFact1 = (StageStatic.stage.factState[lineFact.Pid1] as PointFact);
        PointFact pointFact2 = (StageStatic.stage.factState[lineFact.Pid2] as PointFact);
        Vector3 point1 = pointFact1.Point;
        Vector3 point2 = pointFact2.Point;
        //Change FactRepresentation to Line
        GameObject line = GameObject.Instantiate(Line);
        //Place the Line in the centre of the two points
        line.transform.position = Vector3.Lerp(point1, point2, 0.5f);
        //Change scale and rotation, so that the two points are connected by the line
        //Get the Line-GameObject as the first Child of the Line-Prefab -> That's the Collider
        var v3T = line.transform.GetChild(0).localScale;

        //For every Coordinate x,y,z we have to devide it by the LocalScale of the Child,
        //because actually the Child should be of this length and not the parent, which is only the Collider
        v3T.x = (point2 - point1).magnitude / line.transform.GetChild(0).GetChild(0).localScale.x;

        //Change Scale/Rotation of the Line-GameObject without affecting Scale of the Text
        line.transform.GetChild(0).localScale = v3T;
        line.transform.GetChild(0).rotation = Quaternion.FromToRotation(Vector3.right, point2 - point1);

        //string letter = ((Char)(64 + lineFact.Id + 1)).ToString();
        //line.GetComponentInChildren<TextMeshPro>().text = letter;
        line.GetComponentInChildren<TextMeshPro>().text = pointFact1.Label + pointFact2.Label;
        line.GetComponentInChildren<TextMeshPro>().text += " = " + Math.Round((point1-point2).magnitude, 2) + " m";
        line.GetComponentInChildren<FactObject>().URI = lineFact.Id;
        lineFact.Representation = line;
        return lineFact;

    }

    public Fact SpawnRay(Fact fact)
    {
        RayFact rayFact = ((RayFact)fact);

        PointFact pointFact1 = (StageStatic.stage.factState[rayFact.Pid1] as PointFact);
        PointFact pointFact2 = (StageStatic.stage.factState[rayFact.Pid2] as PointFact);

 
        Vector3 point1 = pointFact1.Point;
        Vector3 point2 = pointFact2.Point;

        Vector3 dir = (point2 - point1).normalized;
        point1 -= dir * 100;
        point2 += dir * 100;

        //Change FactRepresentation to Line
        GameObject line = GameObject.Instantiate(Ray);
        //Place the Line in the centre of the two points
        line.transform.position = Vector3.Lerp(point1, point2, 0.5f);
        //Change scale and rotation, so that the two points are connected by the line
        //Get the Line-GameObject as the first Child of the Line-Prefab -> That's the Collider
        var v3T = line.transform.GetChild(0).localScale;

        //For every Coordinate x,y,z we have to devide it by the LocalScale of the Child,
        //because actually the Child should be of this length and not the parent, which is only the Collider
        v3T.x = (point2 - point1).magnitude / line.transform.GetChild(0).GetChild(0).localScale.x;

        //Change Scale/Rotation of the Line-GameObject without affecting Scale of the Text
        line.transform.GetChild(0).localScale = v3T;
        line.transform.GetChild(0).rotation = Quaternion.FromToRotation(Vector3.right, point2 - point1);

        line.GetComponentInChildren<TextMeshPro>().text = rayFact.Label;
        line.GetComponentInChildren<FactObject>().URI = rayFact.Id;

        rayFact.Representation = line;
        return rayFact;
    }
    
    //Spawn an angle: point with id = angleFact.Pid2 is the point where the angle gets applied
    public Fact SpawnAngle(Fact fact)
    {
        AngleFact angleFact = (AngleFact)fact;

        Vector3 point1 = (StageStatic.stage.factState[angleFact.Pid1] as PointFact).Point;
        Vector3 point2 = (StageStatic.stage.factState[angleFact.Pid2] as PointFact).Point;
        Vector3 point3 = (StageStatic.stage.factState[angleFact.Pid3] as PointFact).Point;

        //Length of the Angle relative to the Length of the shortest of the two lines (point2->point1) and (point2->point3)
        float lengthFactor = 0.3f;
        
        float length;
        if ((point1 - point2).magnitude >= (point3 - point2).magnitude)
            length = lengthFactor * (point3 - point2).magnitude;
        else
            length = lengthFactor * (point1 - point2).magnitude;

        //Change FactRepresentation to Angle
        GameObject angle = GameObject.Instantiate(Angle);

        //Calculate Angle:
        Vector3 from = (point3 - point2).normalized;
        Vector3 to = (point1 - point2).normalized;
        float angleValue = Vector3.Angle(from, to); //We always get an angle between 0 and 180° here

        //Change scale and rotation, so that the angle is in between the two lines

        Vector3 forwoard = (from + to).normalized;
        Vector3 up = Vector3.Cross(to, from);

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

            forwoard = to;
        } else
            forwoard = Vector3.Cross(forwoard, up);

        //Place the Angle at position of point2
        angle.transform.SetPositionAndRotation(point2, Quaternion.LookRotation(forwoard, up));

        //Set text of angle
        TextMeshPro[] texts = angle.GetComponentsInChildren<TextMeshPro>();
        foreach (TextMeshPro t in texts) {
            //Change Text not to the id, but to the angle-value (from both sides) AND change font-size relative to length of the angle (from both sides)
            t.text = Math.Round((double) angleValue, 2) + "°";
            t.fontSize = angle.GetComponentInChildren<TextMeshPro>().fontSize * angle.transform.GetChild(0).transform.GetChild(0).localScale.x;
        }

        //Generate angle mesh
        CircleSegmentGenerator[] segments = angle.GetComponentsInChildren<CircleSegmentGenerator>();
        foreach (CircleSegmentGenerator c in segments)
            c.setAngle(angleValue);

        angle.GetComponentInChildren<FactObject>().URI = angleFact.Id;
        angleFact.Representation = angle;
        return angleFact;
    }

    public void DeleteObject(Fact fact)
    {
        GameObject factRepresentation = fact.Representation;
        GameObject.Destroy(factRepresentation);
    }

    public void animateNonExistingFactTrigger(Fact fact) {
        StartCoroutine(animateNonExistingFact(fact));
    }

    public IEnumerator animateNonExistingFact(Fact fact) {
        Fact returnedFact = SpawnFactRepresentation(fact);

        ShinyThings.HighlightFact(returnedFact, FactObject.FactMaterials.Hint);

        yield return new WaitForSeconds(GlobalBehaviour.hintAnimationDuration);

        GameObject.Destroy(returnedFact.Representation);
    }
}