using System.Collections.Generic;
using UnityEngine;

namespace RothenburgAR.UI
{
    public class ProjectionEffectBehaviour : MonoBehaviour
    {
        public GameObject SelectedPOI { get; set; }
        public Material LineMaterial;
        public LineRenderer[] LineRenderers { get; private set; }

        public ProjectionEffectBehaviour()
        {
            LineRenderers = new LineRenderer[2];
        }

        public void Start()
        {
            InitializeLineRenderers();
        }

        public void SetActive(bool newIsActive)
        {
            enabled = newIsActive;
            gameObject.SetActive(newIsActive);
        }

        private void InitializeLineRenderers()
        {
            for (var n = 0; n < 2; n++)
            {
                var lineRendererContainer = new GameObject("LineRendererContainer." + n);
                var lineRenderer = lineRendererContainer.AddComponent<LineRenderer>();

                lineRenderer.gameObject.layer = LayerMask.NameToLayer("UI");
                lineRenderer.useWorldSpace = false;
                lineRenderer.positionCount = 0;
                lineRenderer.startWidth = 0.0005f;
                lineRenderer.endWidth = 0.0005f;

                lineRenderer.materials = new[]
                {
                    new Material(Shader.Find("Particles/Alpha Blended Premultiply")),
                    LineMaterial
                };

                LineRenderers[n] = lineRenderer;

                lineRendererContainer.transform.parent = gameObject.transform;
                lineRendererContainer.transform.localScale = Vector3.one;
                lineRendererContainer.transform.localPosition = Vector3.zero;
                lineRendererContainer.transform.localRotation = Quaternion.identity;
            }
        }

        private enum CornerIndex
        {
            BottomLeft = 0,
            TopLeft = 1,
            TopRight = 2,
            BottomRight = 3,
            Center = 4
        }

        /*
         cases for localPosition in relation to panel, e.g. 0 = above and to the left of the panel, 4 = inside the panel, 5 straight to the right of the panel
         0 1 2 
         3 4 5
         6 7 8
         */
        private enum PositionType
        {
            TopLeft = 0,
            Top = 1,
            TopRight = 2,
            Left = 3,
            Center = 4,
            Right = 5,
            BottomLeft = 6,
            Bottom = 7,
            BottomRight = 8
        }

        // Update is called once per frame
        void LateUpdate()
        {
            if (!enabled || SelectedPOI == null)
                return;

            var scale = DisplayManager.Instance.GetComponent<RectTransform>().localScale.x;
            Vector3 screenMiddle = new Vector3(Screen.width / scale, Screen.height / scale, 0) * 0.5f;
            transform.localPosition = Camera.main.WorldToScreenPoint(SelectedPOI.transform.position) / scale - screenMiddle;

            RectTransform rt = UIManager.Instance.ARViewBehaviour.DetailsPanel.GetComponent<RectTransform>();
            Vector3 panelCenter = rt.transform.localPosition;
            Vector3[] panelCorners = new Vector3[4];
            rt.GetLocalCorners(panelCorners);

            for (int i = 0; i < panelCorners.Length; i++)
            {
                var localScale = rt.localScale;
                var corner = panelCorners[i];
                corner.Scale(localScale);
                panelCorners[i] = corner;
            }

            List<Vector3> vertices = GenerateVertices(panelCenter, panelCorners);
            List<Vector2> uvs = GenerateUVs(vertices.Count);
            List<Vector3> normals = GenerateNormals(vertices.Count);

            PositionType positionType = CalculatePositionType(transform.localPosition, panelCenter, panelCorners);
            int[] triangles = GenerateTriangles(positionType);

            ManipulateCornerCoordinates(positionType, vertices);
            UpdateMesh(vertices, normals, uvs, triangles);
            HandleLineRenderers(triangles, vertices);
        }

        private void UpdateMesh(List<Vector3> vertices, List<Vector3> normals, List<Vector2> uvs, int[] triangles)
        {
            Mesh mesh = gameObject.GetComponent<MeshFilter>().mesh;
            mesh.Clear();
            mesh.SetVertices(vertices);
            mesh.SetNormals(normals);
            mesh.SetUVs(0, uvs);
            mesh.SetTriangles(triangles, 0);
        }

        private void HandleLineRenderers(int[] triangles, List<Vector3> vertices)
        {
            if (triangles.Length > 0)
            {
                LineRenderers[0].positionCount = 3;
                LineRenderers[0].SetPosition(0, vertices[triangles[1]]);
                LineRenderers[0].SetPosition(1, vertices[triangles[0]]);
                LineRenderers[0].SetPosition(2, vertices[triangles[2]]);
            }
            else
            {
                LineRenderers[0].positionCount = 0;
            }

            if (triangles.Length > 3)
            {
                LineRenderers[1].positionCount = 2;
                LineRenderers[1].SetPosition(0, vertices[triangles[3]]);
                LineRenderers[1].SetPosition(1, vertices[triangles[5]]);
            }
            else
            {
                LineRenderers[1].positionCount = 0;
            }
        }

        private void ManipulateCornerCoordinates(PositionType positionType, List<Vector3> vertices)
        {
            if (positionType == PositionType.Right || positionType == PositionType.Left)
            {
                var addedVector = new Vector3(0, 2);
                vertices[(int)CornerIndex.TopLeft] -= addedVector;
                vertices[(int)CornerIndex.TopRight] -= addedVector;
                vertices[(int)CornerIndex.BottomLeft] += addedVector;
                vertices[(int)CornerIndex.BottomRight] += addedVector;
            }
            else if (positionType == PositionType.Top || positionType == PositionType.Bottom)
            {
                var addedVector = new Vector3(2, 0);
                vertices[(int)CornerIndex.BottomLeft] += addedVector;
                vertices[(int)CornerIndex.TopLeft] += addedVector;
                vertices[(int)CornerIndex.TopRight] -= addedVector;
                vertices[(int)CornerIndex.BottomRight] -= addedVector;
            }
        }

        private PositionType CalculatePositionType(Vector3 localPosition, Vector3 panelCenter, Vector3[] panelCorners)
        {
            Vector3[] absoluteCorners = new Vector3[4];
            for (int i = 0; i < panelCorners.Length; i++)
            {
                absoluteCorners[i] = panelCenter + panelCorners[i];
            }

            PositionType result = PositionType.Center;

            if (localPosition.y > absoluteCorners[(int)CornerIndex.TopLeft].y)
            {
                if (localPosition.x < absoluteCorners[(int)CornerIndex.TopLeft].x)
                {
                    result = PositionType.TopLeft;
                }
                else if (localPosition.x > absoluteCorners[(int)CornerIndex.TopRight].x)
                {
                    result = PositionType.TopRight;
                }
                else
                {
                    result = PositionType.Top;
                }
            }
            else if (localPosition.y <= absoluteCorners[(int)CornerIndex.TopLeft].y
                     && localPosition.y >= absoluteCorners[(int)CornerIndex.BottomLeft].y)
            {
                if (localPosition.x < absoluteCorners[(int)CornerIndex.TopLeft].x)
                {
                    result = PositionType.Left;
                }
                else if (localPosition.x > absoluteCorners[(int)CornerIndex.TopRight].x)
                {
                    result = PositionType.Right;
                }
                else
                {
                    result = PositionType.Center;
                }
            }
            else if (localPosition.y < absoluteCorners[(int)CornerIndex.BottomLeft].y)
            {
                if (localPosition.x < absoluteCorners[(int)CornerIndex.TopLeft].x)
                {
                    result = PositionType.BottomLeft;
                }
                else if (localPosition.x > absoluteCorners[(int)CornerIndex.TopRight].x)
                {
                    result = PositionType.BottomRight;
                }
                else
                {
                    result = PositionType.Bottom;
                }
            }
            return result;
        }

        private int[] GenerateTriangles(PositionType positionType)
        {
            List<int> triangles = new List<int>();

            switch (positionType)
            {
                case PositionType.Left:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopLeft, (int)CornerIndex.BottomLeft });
                    break;
                case PositionType.Right:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopRight, (int)CornerIndex.BottomRight });
                    break;
                case PositionType.Top:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopLeft, (int)CornerIndex.TopRight });
                    break;
                case PositionType.TopLeft:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopLeft, (int)CornerIndex.TopRight });
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopLeft, (int)CornerIndex.BottomLeft });
                    break;
                case PositionType.TopRight:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopLeft, (int)CornerIndex.TopRight });
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopRight, (int)CornerIndex.BottomRight });
                    break;
                case PositionType.Bottom:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.BottomLeft, (int)CornerIndex.BottomRight });
                    break;
                case PositionType.BottomLeft:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.BottomLeft, (int)CornerIndex.BottomRight });
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.TopLeft, (int)CornerIndex.BottomLeft });
                    break;
                case PositionType.BottomRight:
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.BottomLeft, (int)CornerIndex.BottomRight });
                    triangles.AddRange(new int[] { (int)CornerIndex.Center, (int)CornerIndex.BottomRight, (int)CornerIndex.TopRight });
                    break;
            }
            return triangles.ToArray();
        }

        private List<Vector3> GenerateVertices(Vector3 panelCenter, Vector3[] panelCorners)
        {
            var vertices = new List<Vector3>();

            //BottomLeft = 0,
            //TopLeft = 1,
            //TopRight = 2,
            //BottomRight = 3

            for (int i = 0; i < 4; i++)
            {
                vertices.Add(new Vector3(panelCenter.x + panelCorners[i].x - transform.localPosition.x,
                                        panelCenter.y + panelCorners[i].y - transform.localPosition.y));
            }
            vertices.Add(Vector3.zero);

            // trying to get rid of the 1px wide space between projection effect and DetailsPanel
            for (int i = 0; i < vertices.Count; i++)
            {
                vertices[i] += new Vector3(Mathf.Sign(vertices[i].x), Mathf.Sign(vertices[i].y), 0) * 1.0f;
            }

            return vertices;
        }

        private List<Vector2> GenerateUVs(int vertexCount)
        {
            var uvs = new List<Vector2>();
            for (int i = 0; i < vertexCount - 1; i++)
            {
                uvs.Add(Vector2.zero);
            }
            uvs.Add(new Vector2(1, 0));

            return uvs;
        }

        private List<Vector3> GenerateNormals(int vertexCount)
        {
            var normals = new List<Vector3>();
            for (int i = 0; i < vertexCount; i++)
            {
                normals.Add(Vector3.back);
            }
            return normals;
        }
    }
}