using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.ProBuilder.Shapes;
using static CommunicationEvents;

public class FactSpawner : MonoBehaviour
{
    public GameObject
        TestPoint,
        Line,
        Ray,
        Angle,
        Ring,
        Prism,
        Cone,
        Rectangle,
        Circle,
        SimpleCircle,
        Triangle,
        Sphere,
        ActualSphere,
        Cuboid,
        TriangularPrism,
        Cylinder,
        Pyramid
        ;

    private void OnEnable()
    {
        AddFactEvent.AddListener(SpawnFactRepresentation);
        AnimateNonExistingFactEvent.AddListener(AnimateNonExistingFactTrigger);
        StartT0Event.AddListener(AnimateFunctionCalls);
    }

    private void OnDisable()
    {
        AddFactEvent.RemoveListener(SpawnFactRepresentation);
        AnimateNonExistingFactEvent.RemoveListener(AnimateNonExistingFactTrigger);
        StartT0Event.RemoveListener(AnimateFunctionCalls);
    }

    public void SpawnFactRepresentation(Fact fact)
    {
        switch (fact)
        {
            case PointFact PointFact:
                SpawnPoint(PointFact); break;
            case TestPointFact TestPointFact:
                SpawnTestPoint(TestPointFact); break;
            case LineFact LineFact:
                SpawnLine(LineFact); break;
            //case RightAngleFact AngleFact: //needed for Hint System
            case AbstractAngleFact AngleFact:
                SpawnAngle(AngleFact); break;
            case RayFact RayFact:
                SpawnRay(RayFact); break;
            case CircleFact CircleFact:
                SpawnRingAndCircle(CircleFact); break;
            case QuadFact QuadFact:
                SpawnQuad(QuadFact); break;
            case TriangleFact TrisFact:
                SpawnTris(TrisFact); break;
            case ConeVolumeFact ConeVolumeFact:
                SpawnCone(ConeVolumeFact); break;
            case TruncatedConeVolumeFact TruncatedConeVolumeFact:
                SpawnTruncatedCone(TruncatedConeVolumeFact); break;
            case RectangleFact rectangleFact:
                SpawnRectangle(rectangleFact); break;
            case SimpleCircleFact simpleCircleFact:
                SpawnSimpleCircle(simpleCircleFact); break;
            case TriangleFact2 triangleFact:
                SpawnTriangle(triangleFact); break;
            case SphereFact sphereFact:
                SpawnSphere(sphereFact); break;
            case CuboidFact cuboidFact:
                SpawnCuboid(cuboidFact); break;
            case PrismFact prismFact:
                SpawnTriangularPrism(prismFact); break;
            case CylinderFact cylinderFact:
                SpawnCylinderFact(cylinderFact); break;
            case PyramidFact pyramidFact:
                SpawnPyramid(pyramidFact); break;
            default: break;
        };
    }

    public void SpawnCuboid(CuboidFact fact){

        GameObject cuboid = GameObject.Instantiate(Cuboid);
        fact.WorldRepresentation = cuboid.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        cuboid.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        cuboid.transform.localScale = Vector3.Scale(cuboid.transform.localScale, fact.LocalScale);

        cuboid.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Volume, 2) + "m³";

    }

    public void SpawnCylinderFact(CylinderFact fact){

        GameObject cylinder = GameObject.Instantiate(Cylinder);
        fact.WorldRepresentation = cylinder.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        cylinder.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        cylinder.transform.localScale = Vector3.Scale(cylinder.transform.localScale, fact.LocalScale);

        cylinder.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Volume, 2) + "m³";
    }
    public void SpawnSphere(SphereFact fact){

        GameObject sphere = GameObject.Instantiate(ActualSphere);
        fact.WorldRepresentation = sphere.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        sphere.transform.SetPositionAndRotation(fact.Position, Quaternion.identity);
        sphere.transform.localScale = Vector3.Scale(sphere.transform.localScale, fact.LocalScale);

        sphere.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Volume, 2) + "m³";


    }
    public void SpawnRectangle(RectangleFact fact){

        GameObject rectangle = GameObject.Instantiate(Rectangle);

        fact.WorldRepresentation = rectangle.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        rectangle.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        rectangle.transform.localScale = Vector3.Scale(rectangle.transform.localScale, fact.LocalScale);
        rectangle.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Area, 2) + "m²";

    }

    public void SpawnSimpleCircle(SimpleCircleFact fact){
        GameObject simpleCircle = GameObject.Instantiate(SimpleCircle);

        simpleCircle.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        simpleCircle.transform.localScale = Vector3.Scale(simpleCircle.transform.localScale, fact.LocalScale);
    }

    public void SpawnTriangle(TriangleFact2 fact)
    {
        GameObject triangle = GameObject.Instantiate(Triangle);
        fact.WorldRepresentation = triangle.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        triangle.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        // triangle.transform.localScale = Vector3.Scale(triangle.transform.localScale, fact.LocalScale);
        triangle.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Area, 2) + "m²";

        TriangleGenerator[] triangelGenerators = triangle.GetComponentsInChildren<TriangleGenerator>();
        foreach (var gen in triangelGenerators)
        {
            gen.ab = Vector3.Distance(fact.A, fact.B);
            gen.height = Vector3.Distance(fact.c, fact.C);
            gen.c = fact.cPosition;
        }
    }

    public void SpawnTriangularPrism(PrismFact fact)
    {
        GameObject prism = GameObject.Instantiate(TriangularPrism);
        fact.WorldRepresentation = prism.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        prism.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        // triangle.transform.localScale = Vector3.Scale(triangle.transform.localScale, fact.LocalScale);
        prism.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Volume, 2) + "m³";

        TriangluarPrismGenerator[] triangelGenerators = prism.GetComponentsInChildren<TriangluarPrismGenerator>();
        foreach (var gen in triangelGenerators)
        {
            gen.triangle_ab = Vector3.Distance(fact.A, fact.B);
            gen.triangle_height = Vector3.Distance(fact.c, fact.C);
            gen.triangle_c = fact.cPosition;
            gen.prism_height = Vector3.Distance(fact.A, fact.D);
        }
    }

    public void SpawnPyramid(PyramidFact fact)
    {
        GameObject pyramid = GameObject.Instantiate(Pyramid);
        fact.WorldRepresentation = pyramid.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        pyramid.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        // triangle.transform.localScale = Vector3.Scale(triangle.transform.localScale, fact.LocalScale);
        pyramid.GetComponentInChildren<TextMeshPro>().text = fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Volume, 2) + "m³";

        PyramidGenerator[] pyramidGenerators = pyramid.GetComponentsInChildren<PyramidGenerator>();
        foreach (var gen in pyramidGenerators)
        {
            gen.ab = fact.ab;
            gen.bc = fact.bc;
            gen.d_offset = fact.D_offset;
        }
    }

    public void SpawnPoint(PointFact fact)
    {
        GameObject point = GameObject.Instantiate(Sphere);
        fact.WorldRepresentation = point.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        point.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
    }

    public void SpawnTestPoint(TestPointFact fact)
    {
        GameObject point = GameObject.Instantiate(TestPoint);
        fact.WorldRepresentation = point.GetComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        point.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
    }

    public void SpawnLine(LineFact fact)
    {
        //Change FactRepresentation to Line
        GameObject line = GameObject.Instantiate(Line);
        fact.WorldRepresentation = line.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        //Place the Line in the centre of the two points
        line.transform.position = fact.Position;

        //Change scale and rotation, so that the two points are connected by the line
        //and without affecting Scale of the Text
        line.transform.GetChild(0).localScale = fact.LocalScale;
        line.transform.GetChild(0).rotation = fact.Rotation;

        line.GetComponentInChildren<TextMeshPro>().text =
            fact.GetLabel(StageStatic.stage.factState) + " = " + System.Math.Round(fact.Distance, 2) + " m";
    }

    public void SpawnRay(RayFact fact)
    {
        //Change FactRepresentation to Line
        GameObject line = GameObject.Instantiate(Ray);
        fact.WorldRepresentation = line.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        //Place the Line in the centre of the two points
        line.transform.position = fact.Position;

        //Change scale and rotation, so that the two points are connected by the line
        //and without affecting Scale of the Text
        line.transform.GetChild(0).localScale = fact.LocalScale;
        line.transform.GetChild(0).rotation = fact.Rotation;
    }

    //Spawn an angle: point with id = angleFact.Pid2 is the point where the angle gets applied
    public void SpawnAngle(AbstractAngleFact fact)
    {
        //Change FactRepresentation to Angle
        GameObject angle = GameObject.Instantiate(Angle);
        fact.WorldRepresentation = angle.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        angle.transform.SetPositionAndRotation(fact.Position, fact.Rotation);

        //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)
            t.text = System.Math.Round((float)fact.angle, 2) + "°";
        }

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

    public void SpawnRingAndCircle(CircleFact fact)
    {
        var ringAndCircleGO = new GameObject("RingAndCircle");
        SpawnRing(fact, ringAndCircleGO.transform);
        SpawnCircle(fact, ringAndCircleGO.transform);

        ringAndCircleGO.transform.SetPositionAndRotation(fact.Position, fact.Rotation);
        fact.WorldRepresentation = ringAndCircleGO.AddComponent<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;
    }

    public void SpawnRing(CircleFact circleFact, Transform parent = null)
    {
        GameObject ring = GameObject.Instantiate(Ring, parent);

        TorusGenerator[] tori = ring.GetComponentsInChildren<TorusGenerator>();
        foreach (var torus in tori)
            torus.torusRadius = circleFact.radius + (float)Math3d.vectorPrecission;
    }

    public void SpawnCircle(CircleFact circleFact, Transform parent = null)
    {
        GameObject circle = Instantiate(Circle, parent);
        //Set radius
        circle.transform.localScale = Vector3.Scale(circle.transform.localScale, circleFact.LocalScale);
    }

    public void SpawnQuad(QuadFact fact)
    {
        GameObject prism = GameObject.Instantiate(Prism);
        fact.WorldRepresentation = prism.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        prism.transform.SetPositionAndRotation(fact.Position, fact.Rotation);

        PrismGenerator[] prisms = prism.GetComponentsInChildren<PrismGenerator>();
        foreach (var gen in prisms)
            gen.Vertices = fact.Points.Select(p => p.Position).ToArray();
    }

    public void SpawnTris(TriangleFact fact)
    {
        GameObject prism = GameObject.Instantiate(Prism);
        fact.WorldRepresentation = prism.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        prism.transform.SetPositionAndRotation(fact.Position, fact.Rotation);

        PrismGenerator[] prisms = prism.GetComponentsInChildren<PrismGenerator>();
        foreach (var gen in prisms)
            gen.Vertices = fact.Verticies;
    }

    public void SpawnCone(ConeVolumeFact fact)
    {
        GameObject prism = GameObject.Instantiate(Cone);
        fact.WorldRepresentation = prism.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        prism.transform.SetPositionAndRotation(fact.Position, fact.Rotation);

        ConeGenerator[] prisms = prism.GetComponentsInChildren<ConeGenerator>();
        foreach (var gen in prisms)
        {
            gen.topPosition = fact.Point.Position - fact.Circle.Position;
            gen.topRadius = 0f;
            gen.bottomRadius = fact.Circle.radius;
        }
    }

    public void SpawnTruncatedCone(TruncatedConeVolumeFact fact)
    {
        GameObject prism = GameObject.Instantiate(Cone);
        fact.WorldRepresentation = prism.GetComponentInChildren<FactObject3D>();
        fact.WorldRepresentation.Fact = fact;

        prism.transform.SetPositionAndRotation(fact.Position, fact.Rotation);

        ConeGenerator[] prisms = prism.GetComponentsInChildren<ConeGenerator>();
        foreach (var gen in prisms)
        {
            gen.topPosition = fact.CircleTop.Position - fact.CircleBase.Position;
            gen.topRadius = fact.CircleTop.radius;
            gen.bottomRadius = fact.CircleBase.radius;
        }
    }

    public void AnimateNonExistingFactTrigger(Fact fact)
    {
        StartCoroutine(_BlossomAndDie(fact));

        IEnumerator _BlossomAndDie(Fact fact)
        {
            SpawnFactRepresentation(fact);

            yield return new WaitForSeconds(GlobalBehaviour.HintAnimationDuration);

            RemoveFactEvent.Invoke(fact);
            fact.freeAutoLabel(StageStatic.stage.factState);
        }
    }

    public void AnimateFunctionCalls()
    {
        float MaxAnimationDuration = 10f;
        StartCoroutine(_AnimateFunctionCalls());

        IEnumerator _AnimateFunctionCalls()
        {
            float trigger_time = Time.time;

            Queue<FunctionCallFact> FCFs = new(
                StageStatic.stage.factState.MyFactSpace.Values
                .Where(f => f is FunctionCallFact)
                .Select(f => f as FunctionCallFact)
                .OrderBy(f => f.Domain.t_0)
            );

            FCFs.TryDequeue(out FunctionCallFact up_next);
            while (up_next != null)
            {
                float curren_time = Time.time - trigger_time;
                while (up_next != null && up_next.Domain.t_0 <= curren_time)
                {
                    if (up_next.Domain.t_n <= curren_time)
                    {
                        FCFs.TryDequeue(out up_next);
                        continue;
                    }
                    StartCoroutine(__BlossomDragAndDie(up_next));
                    FCFs.TryDequeue(out up_next);
                }
                yield return null;
            }

#pragma warning disable CS8321 // Die lokale Funktion ist deklariert, wird aber nie verwendet.
            IEnumerator __BlossomAndDiePerFrame(FunctionCallFact FCF)
            {
                float current_time = Time.time - trigger_time;
                while (current_time <= FCF.Domain.t_n
                    && current_time <= FCF.Domain.t_0 + MaxAnimationDuration)
                {
                    object[] result = FCF.Call(current_time);

                    List<Fact> factlist = new();
                    foreach (object i in result)
                        Fact.MakeFact(factlist, i, null, true);

                    foreach (Fact fact in factlist)
                        SpawnFactRepresentation(fact);

                    yield return null;

                    foreach (Fact fact in factlist)
                    {
                        RemoveFactEvent.Invoke(fact);
                        fact.freeAutoLabel(StageStatic.stage.factState);
                    }

                    current_time = Time.time - trigger_time;
                }
            }
#pragma warning restore CS8321 // Die lokale Funktion ist deklariert, wird aber nie verwendet.

            IEnumerator __BlossomDragAndDie(FunctionCallFact FCF)
            {
                List<Fact> factlist = new();

                object[] result = FCF.Call(FCF.Domain.t_0);
                foreach (object i in result)
                    Fact.MakeFact(factlist, i, null, true);

                foreach (Fact fact in factlist)
                    SpawnFactRepresentation(fact);

                float current_time = Time.time - trigger_time;
                while (current_time <= FCF.Domain.t_n
                    && current_time <= FCF.Domain.t_0 + MaxAnimationDuration)
                {
                    List<Fact> factlist_media = new();

                    object[] result_media = FCF.Call(current_time);
                    foreach (object i in result_media)
                        Fact.MakeFact(factlist_media, i, null, true);

                    for (int i = 0; i < math.min(factlist.Count, factlist_media.Count); i++)
                    {
                        //factlist[i].WorldRepresentation.Fact = factlist_media[i]; //TODO? viable? => FactWrapper.FactUpdated()
                        factlist[i].WorldRepresentation.transform.SetPositionAndRotation(
                            factlist_media[i].Position,
                            factlist_media[i].Rotation
                        );
                        factlist_media[i].freeAutoLabel(StageStatic.stage.factState);
                    }

                    yield return null;
                    current_time = Time.time - trigger_time;
                }

                foreach (Fact fact in factlist)
                {
                    RemoveFactEvent.Invoke(fact);
                    fact.freeAutoLabel(StageStatic.stage.factState);
                }
            }
        }
    }

}