using System.Collections.Generic;
using UnityEditor;

namespace UnityEngine.ProBuilder.Shapes
{
    [Shape("Arch")]
    public class Arch : Shape
    {
        [Min(0.01f)]
        [SerializeField]
        float m_Thickness = .1f;

        [Range(3, 200)]
        [SerializeField]
        int m_NumberOfSides = 5;

        [Range(1, 360)]
        [SerializeField]
        float m_ArchDegrees = 180;

        [SerializeField]
        bool m_EndCaps = true;

        [SerializeField]
        bool m_Smooth = true;

        public override void CopyShape(Shape shape)
        {
            if(shape is Arch)
            {
                Arch arch = ( (Arch) shape );
                m_Thickness = arch.m_Thickness;
                m_NumberOfSides = arch.m_NumberOfSides;
                m_ArchDegrees = arch.m_ArchDegrees;
                m_EndCaps = arch.m_EndCaps;
                m_Smooth = arch.m_Smooth;
            }
        }

        Vector3[] GetFace(Vector2 vertex1, Vector2 vertex2, float depth)
        {
            return new Vector3[4]
            {
                new Vector3(vertex1.x, vertex1.y, depth),
                new Vector3(vertex2.x,  vertex2.y, depth),
                new Vector3(vertex1.x, vertex1.y, -depth),
                new Vector3(vertex2.x, vertex2.y, -depth)
            };
        }

        public override Bounds RebuildMesh(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
        {
            var upDir = Vector3.Scale(rotation * Vector3.up, size) ;
            var rightDir = Vector3.Scale(rotation * Vector3.right, size) ;
            var forwardDir = Vector3.Scale(rotation * Vector3.forward, size) ;

            var xRadius = rightDir.magnitude / 2f;
            var yRadius = upDir.magnitude;
            var depth = forwardDir.magnitude / 2f;

            var radialCuts = m_NumberOfSides + 1;
            var angle = m_ArchDegrees;
            var templateOut = new Vector2[radialCuts];
            var templateIn = new Vector2[radialCuts];

            if(angle < 90f)
                xRadius *= 2f;
            else if(angle < 180f)
                xRadius *= 1f+ Mathf.Lerp(1f, 0f, Mathf.Abs(Mathf.Cos(angle * Mathf.Deg2Rad)));
            else if(angle > 180f)
                yRadius /= 1f+ Mathf.Lerp(0f, 1f, (angle - 180f)/90f);

            for (int i = 0; i < radialCuts; i++)
            {
                var currentAngle = i * ( angle / ( radialCuts - 1 ) );
                Vector2 tangent;
                templateOut[i] = Math.PointInEllipseCircumference(xRadius, yRadius, currentAngle, Vector2.zero, out tangent);
                templateIn[i] = Math.PointInEllipseCircumference(xRadius - m_Thickness, yRadius - m_Thickness, currentAngle, Vector2.zero, out tangent);
            }

            List<Vector3> v = new List<Vector3>();

            Vector2 tmp, tmp2, tmp3, tmp4;

            float y = -depth;
            int smoothedFaceCount = 0;
            for (int n = 0; n < radialCuts - 1; n++)
            {
                // outside faces
                tmp = templateOut[n];
                tmp2 = n < (radialCuts - 1) ? templateOut[n + 1] : templateOut[n];

                Vector3[] qvo = GetFace(tmp, tmp2, -depth);

                // inside faces
                tmp = templateIn[n];
                tmp2 = n < (radialCuts - 1) ? templateIn[n + 1] : templateIn[n];

                Vector3[] qvi = GetFace(tmp2, tmp, -depth);

                // left side bottom face
                if(angle < 360f && m_EndCaps)
                {
                    if(n == 0)
                        v.AddRange(GetFace(templateOut[n], templateIn[n], depth));
                }

                v.AddRange(qvo);
                v.AddRange(qvi);
                smoothedFaceCount += 2;

                if(angle < 360f && m_EndCaps)
                {
                    // right side bottom face
                    if (n == radialCuts - 2)
                        v.AddRange(GetFace(templateIn[n+1], templateOut[n+1], depth));
                }
            }

            // build front and back faces
            for (int i = 0; i < radialCuts - 1; i++)
            {
                tmp = templateOut[i];
                tmp2 = (i < radialCuts - 1) ? templateOut[i + 1] : templateOut[i];
                tmp3 = templateIn[i];
                tmp4 = (i < radialCuts - 1) ? templateIn[i + 1] : templateIn[i];

                // front
                Vector3[] tpb = new Vector3[4]
                {
                    new Vector3(tmp.x, tmp.y, depth),
                    new Vector3(tmp2.x, tmp2.y, depth),
                    new Vector3(tmp3.x, tmp3.y, depth),
                    new Vector3(tmp4.x, tmp4.y, depth),
                };

                // back
                Vector3[] tpt = new Vector3[4]
                {
                    new Vector3(tmp2.x, tmp2.y, y),
                    new Vector3(tmp.x,  tmp.y, y),
                    new Vector3(tmp4.x, tmp4.y, y),
                    new Vector3(tmp3.x, tmp3.y, y)
                };

                v.AddRange(tpb);
                v.AddRange(tpt);
            }

            var sizeSigns = Math.Sign(size);
            for(int i = 0; i < v.Count; i++)
                v[i] = Vector3.Scale(rotation * v[i], sizeSigns);

            mesh.GeometryWithPoints(v.ToArray());

            if(m_Smooth)
            {
                for(int i = ( angle < 360f && m_EndCaps ) ? 1 : 0; i < smoothedFaceCount; i++)
                    mesh.facesInternal[i].smoothingGroup = 1;
            }

            var sizeSign = sizeSigns.x * sizeSigns.y * sizeSigns.z;
            if(sizeSign < 0)
            {
                var faces = mesh.facesInternal;
                foreach(var face in faces)
                    face.Reverse();
            }

            mesh.TranslateVerticesInWorldSpace(mesh.mesh.triangles, mesh.transform.TransformDirection(-mesh.mesh.bounds.center));
            mesh.Refresh();

            return UpdateBounds(mesh, size, rotation, new Bounds());
        }
    }

#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(Arch))]
    public class ArchDrawer : PropertyDrawer
    {
        static bool s_foldoutEnabled = true;

        const bool k_ToggleOnLabelClick = true;

        static readonly GUIContent k_ThicknessContent = new GUIContent("Thickness", L10n.Tr("Thickness of the arch borders. Larger value creates a smaller opening."));
        static readonly GUIContent k_SidesContent = new GUIContent("Sides Count", L10n.Tr("Number of sides of the arch."));
        static readonly GUIContent k_CircumferenceContent = new GUIContent("Arch Circumference", L10n.Tr("Circumference of the arch in degrees."));
        static readonly GUIContent k_EndCapsContent = new GUIContent("End Caps", L10n.Tr("Whether to generate faces for the ends of the arch."));
        static readonly GUIContent k_SmoothContent = new GUIContent("Smooth", L10n.Tr("Whether to smooth the edges of the arch."));

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            EditorGUI.BeginProperty(position, label, property);

            s_foldoutEnabled = EditorGUI.Foldout(position, s_foldoutEnabled, "Arch Settings", k_ToggleOnLabelClick);

            EditorGUI.indentLevel++;

            if(s_foldoutEnabled)
            {
                EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Thickness"), k_ThicknessContent);
                EditorGUILayout.PropertyField(property.FindPropertyRelative("m_NumberOfSides"), k_SidesContent);
                EditorGUILayout.PropertyField(property.FindPropertyRelative("m_ArchDegrees"), k_CircumferenceContent);
                EditorGUILayout.PropertyField(property.FindPropertyRelative("m_EndCaps"), k_EndCapsContent);
                EditorGUILayout.PropertyField(property.FindPropertyRelative("m_Smooth"), k_SmoothContent);
            }

            EditorGUI.indentLevel--;
            EditorGUI.EndProperty();
        }
    }
#endif
}