using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using static CommunicationEvents;
using System;

public class ShinyThings : MonoBehaviour
{
    public WorldCursor Cursor;
    //Attributes for Highlighting of Facts when Mouse-Over
    private string selectableTag = "Selectable";
    private Transform lastFactSelection;
    public Material defaultMaterial;
    public Material highlightMaterial;


    //Attributes for simulating the drawing of a line/curve
    public LineRenderer lineRenderer;
    private List<Vector3> linePositions = new List<Vector3>();

    private bool lineDrawingActivated;
    private bool curveDrawingActivated;
    //These are only the vertices for the Curve
    private int curveDrawingVertexCount = 36;
    private LineFact curveDrawingStartLine;
    private Vector3 curveEndPoint;
    private Vector3 angleMiddlePoint;
    private float curveRadius;

    //Variables for Pushout-Highlighting
    private Fact highlightedPushoutFact;
    private GameObject extraHighlight;
    private bool timerActive { get; set; }
    private float timer { get; set; }
    private float timerDuration = 5.0f;
    private float timerDurationEnd = 2.5f;
    private float speedSlowDown;
    public Material pushoutMaterial;
    private Material tempMaterial;

    // Start is called before the first frame update
    public void Start()
    {
        if (Cursor == null) Cursor = GetComponent<WorldCursor>();
        CommunicationEvents.StartLineDrawingEvent.AddListener(ActivateLineDrawing);
        CommunicationEvents.StopLineDrawingEvent.AddListener(DeactivateLineDrawing);
        CommunicationEvents.StartCurveDrawingEvent.AddListener(ActivateCurveDrawing);
        CommunicationEvents.StopCurveDrawingEvent.AddListener(DeactivateCurveDrawing);
        CommunicationEvents.StopPreviewsEvent.AddListener(StopPreviews);
        CommunicationEvents.PushoutFactEvent.AddListener(StartPushoutFactHighlighting);
        speedSlowDown = timerDurationEnd * 10;

        this.timerActive = false;
        this.timer = 0;
    }

    // Update is called once per frame
    public void Update()
    {
        //SELECTION-HIGHLIGHTING-PART
        //Check if a Fact was Hit

        RaycastHit Hit = Cursor.Hit;

        Highlighting(Hit);

       
        //LineRendering-Part

        //@John before:  hit.point

        //Debug.Log(this.transform.position);

        if (this.lineDrawingActivated)
            UpdateLineDrawing(this.transform.position);
        else if (this.curveDrawingActivated)
            UpdateCurveDrawing(this.transform.position);

        //If the Timer is Active, check if timerDuration is reached and stop Pushout-Highlighting
        if (this.timerActive)
        {
            this.timer += Time.deltaTime;
            if (this.timer >= this.timerDuration)
            {
                if (this.timer >= this.timerDuration + this.timerDurationEnd)
                {
                    this.timerActive = false;
                    this.timer = 0;
                    StopPushoutFactHighlighting();
                }
                else {
                    var main = this.extraHighlight.transform.GetChild(0).GetComponent<ParticleSystem>().main;
                    speedSlowDown -= Time.deltaTime * 10;
                    main.startSpeed = speedSlowDown;
                    main = this.extraHighlight.transform.GetChild(1).GetComponent<ParticleSystem>().main;
                    main.startSpeed = speedSlowDown;
                }
            }
        }
    }

    private void Highlighting(RaycastHit hit)
    {
        if (hit.transform != null)
        {
            Transform selection = hit.transform;

            //only do stuff if selection changes


            //Set the last Fact unselected
            if (this.lastFactSelection != null)
            {
                if (selection == this.lastFactSelection) return;
                //Invoke the EndHighlightEvent that will be handled in FactSpawner
                // CommunicationEvents.EndHighlightEvent.Invoke(this.lastFactSelection);
                if (this.lastFactSelection.CompareTag(selectableTag))
                    OnMouseOverFactEnd(lastFactSelection);
                else
                    OnMouseOverSnapZoneEnd(lastFactSelection);
                this.lastFactSelection = null;
            }

            //Set the Fact that was Hit as selected
            if (selection.CompareTag(selectableTag))
            {
                //Invoke the HighlightEvent that will be handled in FactSpawner
                this.lastFactSelection = selection;
                //CommunicationEvents.HighlightEvent.Invoke(selection);
                OnMouseOverFact(lastFactSelection);
            }
            else if (selection.CompareTag("SnapZone"))
            {
                this.lastFactSelection = selection;
                OnMouseOverSnapZone(lastFactSelection);

            }
        }


        //SELECTION-HIGHLIGHTING-PART-END
    }

    private void OnMouseOverSnapZoneEnd(Transform selection)
    {
        Renderer selectionRenderer;

        if (selection != null)
        {
            selectionRenderer = selection.GetComponent<Renderer>();
            if (selectionRenderer != null)
            {
                //Add transparency
                var oldCol = selectionRenderer.material.color;
                oldCol.a = .25f;
                selectionRenderer.material.color = oldCol;
                //Unhide Mouse cursor
                UnityEngine.Cursor.visible = true;
            }
        }
    }

    private void OnMouseOverSnapZone(Transform selection)
    {
        Renderer selectionRenderer;
        selectionRenderer = selection.GetComponent<Renderer>();
        if (selectionRenderer != null)
        {
            //Remove transparency
            var oldCol = selectionRenderer.material.color;
            oldCol.a = .75f;
            selectionRenderer.material.color = oldCol;

            //Hide Mouse cursor
           UnityEngine.Cursor.visible = false;

        }
    }

    public void OnMouseOverFact(Transform selection)
    {
        Renderer selectionRenderer;
        selectionRenderer = selection.GetComponent<Renderer>();
        if (selectionRenderer != null)
        {
            //Set the Material of the Fact, where the mouse is over, to a special one
            selectionRenderer.material = highlightMaterial;
        }
    }

    public void OnMouseOverFactEnd(Transform selection)
    {
        Renderer selectionRenderer;

        if (selection != null)
        {
            selectionRenderer = selection.GetComponent<Renderer>();
            if (selectionRenderer != null)
            {
                //Set the Material of the fact back to default
                selectionRenderer.material = defaultMaterial;
            }
        }
    }

    public void StartPushoutFactHighlighting(Fact startFact) {

        GameObject fireworksRepresentation = (GameObject)Resources.Load("Prefabs/Fireworks", typeof(GameObject));
        highlightedPushoutFact = startFact;

        if (typeof(PointFact).IsInstanceOfType(highlightedPushoutFact))
        {
            PointFact fact = (PointFact)highlightedPushoutFact;
            tempMaterial = fact.Representation.transform.GetChild(0).GetComponent<MeshRenderer>().material;
            fact.Representation.transform.GetChild(0).GetComponent<MeshRenderer>().material = pushoutMaterial;
            this.extraHighlight = GameObject.Instantiate(fireworksRepresentation);
            this.extraHighlight.transform.position = fact.Representation.transform.position;
        }
        else if (typeof(LineFact).IsInstanceOfType(highlightedPushoutFact))
        {
            LineFact fact = (LineFact)highlightedPushoutFact;
            tempMaterial = fact.Representation.transform.GetChild(0).GetChild(0).GetComponent<MeshRenderer>().material;
            fact.Representation.transform.GetChild(0).GetChild(0).GetComponent<MeshRenderer>().material = pushoutMaterial;
            this.extraHighlight = GameObject.Instantiate(fireworksRepresentation);
            this.extraHighlight.transform.position = fact.Representation.transform.position;
        }
        else if (typeof(AngleFact).IsInstanceOfType(highlightedPushoutFact)) {
            AngleFact fact = (AngleFact)highlightedPushoutFact;
            tempMaterial = fact.Representation.transform.GetChild(0).GetChild(0).GetComponent<MeshRenderer>().material;
            fact.Representation.transform.GetChild(0).GetChild(0).GetComponent<MeshRenderer>().material = pushoutMaterial;
            this.extraHighlight = GameObject.Instantiate(fireworksRepresentation);
            this.extraHighlight.transform.position = fact.Representation.transform.position;
        }

        //Activate Timer
        this.timerActive = true;
    }

    public void StopPushoutFactHighlighting() {

        if (typeof(PointFact).IsInstanceOfType(highlightedPushoutFact))
        {
            PointFact fact = (PointFact)highlightedPushoutFact;
            fact.Representation.transform.GetChild(0).GetComponent<MeshRenderer>().material = tempMaterial;
        }
        else if (typeof(LineFact).IsInstanceOfType(highlightedPushoutFact))
        {
            LineFact fact = (LineFact)highlightedPushoutFact;
            fact.Representation.transform.GetChild(0).GetChild(0).GetComponent<MeshRenderer>().material = tempMaterial;
        }
        else if (typeof(AngleFact).IsInstanceOfType(highlightedPushoutFact))
        {
            AngleFact fact = (AngleFact)highlightedPushoutFact;
            fact.Representation.transform.GetChild(0).GetChild(0).GetComponent<MeshRenderer>().material = tempMaterial;
        }

        GameObject.Destroy(this.extraHighlight);
        this.extraHighlight = null;
    }

    public void ActivateLineDrawing(Fact startFact)
    {
        this.lineRenderer.positionCount = 2;

        lineRenderer.startWidth = 0.095f;
        lineRenderer.endWidth = 0.095f;
        //Set LineDrawing activated
        this.lineDrawingActivated = true;
        //Add the position of the Fact for the start of the Line
        linePositions.Add(startFact.Representation.transform.position);
        //The second point is the same point at the moment
        linePositions.Add(startFact.Representation.transform.position);

        this.lineRenderer.SetPosition(0, linePositions[0]);
        this.lineRenderer.SetPosition(1, linePositions[1]);

    }

    //Updates the second-point of the Line when First Point was selected in LineMode
    public void UpdateLineDrawing(Vector3 currentPosition)
    {
        this.linePositions[1] = currentPosition;
        this.lineRenderer.SetPosition(1, this.linePositions[1]);
    }

    //Deactivate LineDrawing so that no Line gets drawn when Cursor changes
    public void DeactivateLineDrawing(Fact startFact)
    {
        this.lineRenderer.positionCount = 0;
        this.linePositions = new List<Vector3>();
        this.lineDrawingActivated = false;
    }

    //Expect a LineFact here, where Line.Pid2 will be the Basis-Point of the angle
    public void ActivateCurveDrawing(Fact startFact)
    {
        //In AngleMode with 3 Points we want to draw nearly a rectangle so we add a startPoint and an Endpoint to this preview
        this.lineRenderer.positionCount = curveDrawingVertexCount + 2;

        lineRenderer.startWidth = 0.05f;
        lineRenderer.endWidth = 0.05f;

        //Set CurveDrawing activated
        this.curveDrawingActivated = true;

        curveDrawingStartLine = (LineFact)startFact;
        PointFact curveDrawingPoint1 = (PointFact)Facts.Find(x => x.Id == curveDrawingStartLine.Pid1);
        PointFact curveDrawingPoint2 = (PointFact)Facts.Find(x => x.Id == curveDrawingStartLine.Pid2);

        //curveEndPoint is a point on the Line selected, with some distance from point2
        curveEndPoint = curveDrawingPoint2.Point + 0.3f * (curveDrawingPoint1.Point - curveDrawingPoint2.Point).magnitude * (curveDrawingPoint1.Point - curveDrawingPoint2.Point).normalized;
        
        angleMiddlePoint = curveDrawingPoint2.Point;
        curveRadius = (curveEndPoint - curveDrawingPoint2.Point).magnitude;
    }

    public void UpdateCurveDrawing(Vector3 currentPosition)
    {

        //Find the nearest of all potential third points
        PointFact nearestPoint = null;
        foreach (Fact fact in Facts) {
            if (fact is PointFact && fact.Id != curveDrawingStartLine.Pid1 && fact.Id != curveDrawingStartLine.Pid2 && nearestPoint == null)
                nearestPoint = (PointFact)fact;
            else if (fact is PointFact && fact.Id != curveDrawingStartLine.Pid1 && fact.Id != curveDrawingStartLine.Pid2 && (nearestPoint.Point - currentPosition).magnitude > (((PointFact)fact).Point - currentPosition).magnitude)
                nearestPoint = (PointFact)fact;
        }

        Vector3 startPoint = new Vector3(0,0,0);

        if (nearestPoint != null)
        {
            Vector3 planePoint = Vector3.ProjectOnPlane(currentPosition, Vector3.Cross((nearestPoint.Point-angleMiddlePoint), (curveEndPoint-angleMiddlePoint)));

            //Determine the Start-Point for the nearest third-point
            startPoint = angleMiddlePoint + curveRadius * (planePoint - angleMiddlePoint).normalized;
        }
        else
        {
            //Determine the Start-Point
            startPoint = angleMiddlePoint + curveRadius * (currentPosition - angleMiddlePoint).normalized;
        }

        //Determine the Center of Start-Point and End-Point 
        Vector3 tempCenterPoint = Vector3.Lerp(startPoint, curveEndPoint, 0.5f);
        Vector3 curveMiddlePoint = angleMiddlePoint + curveRadius * (tempCenterPoint - angleMiddlePoint).normalized;

        linePositions = new List<Vector3>();
        //Start: AngleMiddlePoint -> FirstPoint of Curve
        linePositions.Add(((PointFact)Facts.Find(x => x.Id == curveDrawingStartLine.Pid2)).Point);

        for (float ratio = 0; ratio <= 1; ratio += 1.0f / this.curveDrawingVertexCount)
        {
            var tangentLineVertex1 = Vector3.Lerp(startPoint, curveMiddlePoint, ratio);
            var tangentLineVertex2 = Vector3.Lerp(curveMiddlePoint, curveEndPoint, ratio);
            var bezierPoint = Vector3.Lerp(tangentLineVertex1, tangentLineVertex2, ratio);
            linePositions.Add(bezierPoint);
        }

        //End: LastPoint of Curve -> AngleMiddlePoint
        linePositions.Add(((PointFact)Facts.Find(x => x.Id == curveDrawingStartLine.Pid2)).Point);

        lineRenderer.positionCount = linePositions.Count;
        lineRenderer.SetPositions(linePositions.ToArray());
        
    }

    public void DeactivateCurveDrawing(Fact startFact)
    {
        
        this.lineRenderer.positionCount = 0;
        this.linePositions = new List<Vector3>();
        this.curveDrawingActivated = false;
    }

    public void StopPreviews(Fact startFact) {
        if (lineDrawingActivated)
            DeactivateLineDrawing(null);
        if (curveDrawingActivated)
            DeactivateCurveDrawing(null);
    }
}