// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using HoloToolkit.Unity;
using System.Collections.Generic;
using UnityEngine;

namespace HoloToolkit.Unity.UX
{
    [UseWith(typeof(LineBase))]
    public class LineStripMesh : LineRendererBase
    {
        [Header("Strip Mesh Settings")]
        public Material LineMaterial;

        public Vector3 Forward;

        [SerializeField]
        private float uvOffset = 0f;

        private Mesh stripMesh;
        private MeshRenderer stripMeshRenderer;
        private Material lineMatInstance;
        private List<Vector3> positions = new List<Vector3>();
        private List<Vector3> forwards = new List<Vector3>();
        private List<Color> colors = new List<Color>();
        private List<float> widths = new List<float>();

        private GameObject meshRendererGameObject;

        protected void OnEnable()
        {
            if (LineMaterial == null)
            {
                Debug.LogError("Line material cannot be null.");
                enabled = false;
                return;
            }

            lineMatInstance = new Material(LineMaterial);

            // Create a mesh
            if (stripMesh == null)
            {
                stripMesh = new Mesh();
            }

            if (stripMeshRenderer == null)
            {
                meshRendererGameObject = new GameObject("Strip Mesh Renderer");
                stripMeshRenderer = meshRendererGameObject.AddComponent<MeshRenderer>();
            }

            stripMeshRenderer.sharedMaterial = lineMatInstance;
            stripMeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
            stripMeshRenderer.receiveShadows = false;
            stripMeshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;

            MeshFilter stripMeshFilter = stripMeshRenderer.EnsureComponent<MeshFilter>();
            stripMeshFilter.sharedMesh = stripMesh;
        }

        private void OnDisable()
        {
            if (lineMatInstance != null)
            {
                GameObject.Destroy(lineMatInstance);
            }

            if (meshRendererGameObject != null)
            {
                GameObject.Destroy(meshRendererGameObject);
                stripMeshRenderer = null;
            }
        }

        public void Update()
        {
            if (!Source.enabled)
            {
                stripMeshRenderer.enabled = false;
                return;
            }

            stripMeshRenderer.enabled = true;
            positions.Clear();
            forwards.Clear();
            colors.Clear();
            widths.Clear();

            for (int i = 0; i <= NumLineSteps; i++)
            {
                float normalizedDistance = (1f / (NumLineSteps - 1)) * i;
                positions.Add(Source.GetPoint(normalizedDistance));
                colors.Add(GetColor(normalizedDistance));
                widths.Add(GetWidth(normalizedDistance));
                forwards.Add(Source.GetRotation(normalizedDistance) * Vector3.down);
            }

            GenerateStripMesh(positions, colors, widths, uvOffset, forwards, stripMesh);
        }

        public static void GenerateStripMesh(List<Vector3> positionList, List<Color> colorList, List<float> thicknessList, float uvOffsetLocal, List<Vector3> forwardList, Mesh mesh)
        {
            int vertexCount = positionList.Count * 2;
            int colorCount = colorList.Count * 2;
            int uvCount = positionList.Count * 2;

            if (stripMeshVertices == null || stripMeshVertices.Length != vertexCount)
            {
                stripMeshVertices = new Vector3[vertexCount];
            }
            if (stripMeshColors == null || stripMeshColors.Length != colorCount)
            {
                stripMeshColors = new Color[colorCount];
            }
            if (stripMeshUvs == null || stripMeshUvs.Length != uvCount)
            {
                stripMeshUvs = new Vector2[uvCount];
            }

            for (int x = 0; x < positionList.Count; x++)
            {
                Vector3 forward = forwardList[x / 2];
                Vector3 right = Vector3.Cross(forward, Vector3.up).normalized;
                float thickness = thicknessList[x / 2] / 2;
                stripMeshVertices[2 * x] = positionList[x] - right * thickness;
                stripMeshVertices[2 * x + 1] = positionList[x] + right * thickness;
                stripMeshColors[2 * x] = colorList[x];
                stripMeshColors[2 * x + 1] = colorList[x];

                float uv = uvOffsetLocal;
                if (x == positionList.Count - 1 && x > 1)
                {
                    float dist_last = (positionList[x - 2] - positionList[x - 1]).magnitude;
                    float dist_cur = (positionList[x] - positionList[x - 1]).magnitude;
                    uv += 1 - dist_cur / dist_last;
                }

                stripMeshUvs[2 * x] = new Vector2(0, x - uv);
                stripMeshUvs[2 * x + 1] = new Vector2(1, x - uv);
            }

            int numTriangles = ((positionList.Count * 2 - 2) * 3);
            if (stripMeshTriangles == null || stripMeshTriangles.Length != numTriangles)
            {
                stripMeshTriangles = new int[numTriangles];
            }
            int j = 0;
            for (int i = 0; i < positionList.Count * 2 - 3; i += 2, j++)
            {
                stripMeshTriangles[i * 3] = j * 2;
                stripMeshTriangles[i * 3 + 1] = j * 2 + 1;
                stripMeshTriangles[i * 3 + 2] = j * 2 + 2;

                stripMeshTriangles[i * 3 + 3] = j * 2 + 1;
                stripMeshTriangles[i * 3 + 4] = j * 2 + 3;
                stripMeshTriangles[i * 3 + 5] = j * 2 + 2;
            }

            mesh.Clear();
            mesh.vertices = stripMeshVertices;
            mesh.uv = stripMeshUvs;
            mesh.triangles = stripMeshTriangles;
            mesh.colors = stripMeshColors;
            mesh.RecalculateBounds();
            mesh.RecalculateNormals();
        }

        private static Vector3[] stripMeshVertices = null;
        private static Color[] stripMeshColors = null;
        private static Vector2[] stripMeshUvs = null;
        private static int[] stripMeshTriangles = null;

#if UNITY_EDITOR
        [UnityEditor.CustomEditor(typeof(LineStripMesh))]
        public class CustomEditor : MRTKEditor { }
#endif
    }
}