﻿//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Displays the arc lines for teleporting and does the traces
//
//=============================================================================

using UnityEngine;

namespace Valve.VR.InteractionSystem
{
    //-------------------------------------------------------------------------
    public class TeleportArc : MonoBehaviour
    {
        public int segmentCount = 60;
        public float thickness = 0.01f;

        [Tooltip("The amount of time in seconds to predict the motion of the projectile.")]
        public float arcDuration = 3.0f;

        [Tooltip("The amount of time in seconds between each segment of the projectile.")]
        public float segmentBreak = 0.025f;

        [Tooltip("The speed at which the line segments of the arc move.")]
        public float arcSpeed = 0.2f;

        public Material material;

        [HideInInspector]
        public int traceLayerMask = 0;

        //Private data
        private LineRenderer[] lineRenderers;
        private float arcTimeOffset = 0.0f;
        private float prevThickness = 0.0f;
        private int prevSegmentCount = 0;
        private bool showArc = true;
        private Vector3 startPos;
        private Vector3 projectileVelocity;
        private bool useGravity = true;
        private Transform arcObjectsTransfrom;
        private bool arcInvalid = false;
        private float scale = 1;


        //-------------------------------------------------
        void Start()
        {
            arcTimeOffset = Time.time;
        }


        //-------------------------------------------------
        void Update()
        {
            //scale arc to match player scale
            scale = Player.instance.transform.lossyScale.x;
            if (thickness != prevThickness || segmentCount != prevSegmentCount)
            {
                CreateLineRendererObjects();
                prevThickness = thickness;
                prevSegmentCount = segmentCount;
            }
        }



        //-------------------------------------------------
        private void CreateLineRendererObjects()
        {
            //Destroy any existing line renderer objects
            if (arcObjectsTransfrom != null)
            {
                Destroy(arcObjectsTransfrom.gameObject);
            }

            GameObject arcObjectsParent = new GameObject("ArcObjects");
            arcObjectsTransfrom = arcObjectsParent.transform;
            arcObjectsTransfrom.SetParent(this.transform);

            //Create new line renderer objects
            lineRenderers = new LineRenderer[segmentCount];
            for (int i = 0; i < segmentCount; ++i)
            {
                GameObject newObject = new GameObject("LineRenderer_" + i);
                newObject.transform.SetParent(arcObjectsTransfrom);

                lineRenderers[i] = newObject.AddComponent<LineRenderer>();

                lineRenderers[i].receiveShadows = false;
                lineRenderers[i].reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
                lineRenderers[i].lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
                lineRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
                lineRenderers[i].material = material;
#if (UNITY_5_4)
                lineRenderers[i].SetWidth(thickness, thickness);
#else
				lineRenderers[i].startWidth = thickness * scale;
				lineRenderers[i].endWidth = thickness * scale;
#endif
                lineRenderers[i].enabled = false;
            }
        }


        //-------------------------------------------------
        public void SetArcData(Vector3 position, Vector3 velocity, bool gravity, bool pointerAtBadAngle)
        {
            startPos = position;
            projectileVelocity = velocity;
            useGravity = gravity;

            if (arcInvalid && !pointerAtBadAngle)
            {
                arcTimeOffset = Time.time;
            }
            arcInvalid = pointerAtBadAngle;
        }


        //-------------------------------------------------
        public void Show()
        {
            showArc = true;
            if (lineRenderers == null)
            {
                CreateLineRendererObjects();
            }
        }


        //-------------------------------------------------
        public void Hide()
        {
            //Hide the line segments if they were previously being shown
            if (showArc)
            {
                HideLineSegments(0, segmentCount);
            }
            showArc = false;
        }


        //-------------------------------------------------
        // Draws each segment of the arc individually
        //-------------------------------------------------
        public bool DrawArc(out RaycastHit hitInfo)
        {
            float timeStep = arcDuration / segmentCount;

            float currentTimeOffset = (Time.time - arcTimeOffset) * arcSpeed;

            //Reset the arc time offset when it has gone beyond a segment length
            if (currentTimeOffset > (timeStep + segmentBreak))
            {
                arcTimeOffset = Time.time;
                currentTimeOffset = 0.0f;
            }

            float segmentStartTime = currentTimeOffset;

            float arcHitTime = FindProjectileCollision(out hitInfo);

            if (arcInvalid)
            {
                //Only draw first segment
                lineRenderers[0].enabled = true;
                lineRenderers[0].SetPosition(0, GetArcPositionAtTime(0.0f));
                lineRenderers[0].SetPosition(1, GetArcPositionAtTime(arcHitTime < timeStep ? arcHitTime : timeStep));

                HideLineSegments(1, segmentCount);
            }
            else
            {
                //Draw the first segment outside the loop if needed
                int loopStartSegment = 0;
                if (segmentStartTime > segmentBreak)
                {
                    float firstSegmentEndTime = currentTimeOffset - segmentBreak;
                    if (arcHitTime < firstSegmentEndTime)
                    {
                        firstSegmentEndTime = arcHitTime;
                    }
                    DrawArcSegment(0, 0.0f, firstSegmentEndTime);

                    loopStartSegment = 1;
                }

                bool stopArc = false;
                int currentSegment = 0;
                if (segmentStartTime < arcHitTime)
                {
                    for (currentSegment = loopStartSegment; currentSegment < segmentCount; ++currentSegment)
                    {
                        //Clamp the segment end time to the arc duration
                        float segmentEndTime = segmentStartTime + timeStep;
                        if (segmentEndTime >= arcDuration)
                        {
                            segmentEndTime = arcDuration;
                            stopArc = true;
                        }

                        if (segmentEndTime >= arcHitTime)
                        {
                            segmentEndTime = arcHitTime;
                            stopArc = true;
                        }

                        DrawArcSegment(currentSegment, segmentStartTime, segmentEndTime);

                        segmentStartTime += timeStep + segmentBreak;

                        //If the previous end time or the next start time is beyond the duration then stop the arc
                        if (stopArc || segmentStartTime >= arcDuration || segmentStartTime >= arcHitTime)
                        {
                            break;
                        }
                    }
                }
                else
                {
                    currentSegment--;
                }

                //Hide the rest of the line segments
                HideLineSegments(currentSegment + 1, segmentCount);
            }

            return arcHitTime != float.MaxValue;
        }


        //-------------------------------------------------
        private void DrawArcSegment(int index, float startTime, float endTime)
        {
            lineRenderers[index].enabled = true;
            lineRenderers[index].SetPosition(0, GetArcPositionAtTime(startTime));
            lineRenderers[index].SetPosition(1, GetArcPositionAtTime(endTime));
        }


        //-------------------------------------------------
        public void SetColor(Color color)
        {
            for (int i = 0; i < segmentCount; ++i)
            {
#if (UNITY_5_4)
                lineRenderers[i].SetColors(color, color);
#else
				lineRenderers[i].startColor = color;
				lineRenderers[i].endColor = color;
#endif
            }
        }


        //-------------------------------------------------
        private float FindProjectileCollision(out RaycastHit hitInfo)
        {
            float timeStep = arcDuration / segmentCount;
            float segmentStartTime = 0.0f;

            hitInfo = new RaycastHit();

            Vector3 segmentStartPos = GetArcPositionAtTime(segmentStartTime);
            for (int i = 0; i < segmentCount; ++i)
            {
                float segmentEndTime = segmentStartTime + timeStep;
                Vector3 segmentEndPos = GetArcPositionAtTime(segmentEndTime);

                if (Physics.Linecast(segmentStartPos, segmentEndPos, out hitInfo, traceLayerMask))
                {
                    if (hitInfo.collider.GetComponent<IgnoreTeleportTrace>() == null)
                    {
                        Util.DrawCross(hitInfo.point, Color.red, 0.5f);
                        float segmentDistance = Vector3.Distance(segmentStartPos, segmentEndPos);
                        float hitTime = segmentStartTime + (timeStep * (hitInfo.distance / segmentDistance));
                        return hitTime;
                    }
                }

                segmentStartTime = segmentEndTime;
                segmentStartPos = segmentEndPos;
            }

            return float.MaxValue;
        }


        //-------------------------------------------------
        public Vector3 GetArcPositionAtTime(float time)
        {
            Vector3 gravity = useGravity ? Physics.gravity : Vector3.zero;

            Vector3 arcPos = startPos + ((projectileVelocity * time) + (0.5f * time * time) * gravity) * scale;
            return arcPos;
        }


        //-------------------------------------------------
        private void HideLineSegments(int startSegment, int endSegment)
        {
            if (lineRenderers != null)
            {
                for (int i = startSegment; i < endSegment; ++i)
                {
                    lineRenderers[i].enabled = false;
                }
            }
        }
    }
}
