using UnityEngine.ProBuilder.MeshOperations;
namespace UnityEngine.ProBuilder.Shapes
{
    [AddComponentMenu(""), DisallowMultipleComponent]
    sealed class ProBuilderShape : MonoBehaviour
    {
        [SerializeReference]
        Shape m_Shape = new Cube();
        [SerializeField]
        Vector3 m_Size = Vector3.one;
        [SerializeField]
        Quaternion m_Rotation = Quaternion.identity;
        ProBuilderMesh m_Mesh;
        [SerializeField]
        PivotLocation m_PivotLocation;
        [SerializeField]
        Vector3 m_PivotPosition;
        [SerializeField]
        internal ushort m_UnmodifiedMeshVersion;
        public Shape shape
        {
            get => m_Shape;
            set => m_Shape = value;
        }
        public PivotLocation pivotLocation
        {
            get => m_PivotLocation;
            set => m_PivotLocation = value;
        }
        public Vector3 pivotLocalPosition
        {
            get => m_PivotPosition;
            set => m_PivotPosition = value;
        }
        public Vector3 pivotGlobalPosition
        {
            get => mesh.transform.TransformPoint(m_PivotPosition);
            set => pivotLocalPosition = mesh.transform.InverseTransformPoint(value);
        }
        public Vector3 size
        {
            get => m_Size;
            set
            {
                m_Size.x = System.Math.Abs(value.x) == 0 ? Mathf.Sign(m_Size.x) * 0.001f: value.x;
                m_Size.y = value.y;
                m_Size.z = System.Math.Abs(value.z) == 0 ? Mathf.Sign(m_Size.z) * 0.001f: value.z;
            }
        }
        public Quaternion rotation
        {
            get => m_Rotation;
            set => m_Rotation = value;
        }
        Bounds m_EditionBounds;
        public Bounds editionBounds
        {
            get
            {
                m_EditionBounds.center = m_ShapeBox.center;
                m_EditionBounds.size = m_Size;
                if(Mathf.Abs(m_ShapeBox.size.y) < Mathf.Epsilon)
                    m_EditionBounds.size = new Vector3(m_Size.x, 0f, m_Size.z);
                return m_EditionBounds;
            }
        }
        [SerializeField]
        Bounds m_ShapeBox;
        public Bounds shapeBox => m_ShapeBox;
        public bool isEditable => m_UnmodifiedMeshVersion == mesh.versionIndex;
        /// 
        /// Reference to the  that this component is creating.
        /// 
        public ProBuilderMesh mesh
        {
            get
            {
                if(m_Mesh == null)
                    m_Mesh = GetComponent();
                if(m_Mesh == null)
                    m_Mesh = gameObject.AddComponent();
                return m_Mesh;
            }
        }
        void OnValidate()
        {
            //Ensure the size in X and Z is not set to 0 otherwise PhysX
            //is throwing errors as it cannot create a collider
            m_Size.x = System.Math.Abs(m_Size.x) == 0 ? 0.001f: m_Size.x;
            m_Size.z = System.Math.Abs(m_Size.z) == 0 ? 0.001f: m_Size.z;
        }
        internal void UpdateComponent()
        {
            //Recenter shape
            ResetPivot(mesh, size, rotation);
            Rebuild();
        }
        internal void UpdateBounds(Bounds bounds)
        {
            var centerLocalPos = mesh.transform.InverseTransformPoint(bounds.center);
            Bounds shapeBB = m_ShapeBox;
            shapeBB.center = centerLocalPos;
            m_ShapeBox = shapeBB;
            //Recenter shape
            ResetPivot(mesh, m_Size, m_Rotation);
            size = bounds.size;
            Rebuild();
        }
        internal void Rebuild(Bounds bounds, Quaternion rotation, Vector3 cornerPivot)
        {
            var trs = transform;
            trs.position = bounds.center;
            trs.rotation = rotation;
            size = bounds.size;
            pivotGlobalPosition = pivotLocation == PivotLocation.Center ? bounds.center : cornerPivot;
            Rebuild();
        }
        void Rebuild()
        {
            if(gameObject == null || gameObject.hideFlags == HideFlags.HideAndDontSave)
                return;
            m_ShapeBox = m_Shape.RebuildMesh(mesh, size, rotation);
            RebuildPivot(mesh, size, rotation);
            Bounds bounds = m_ShapeBox;
            bounds.size = Math.Abs(m_ShapeBox.size);
            MeshUtility.FitToSize(mesh, bounds, size);
            m_UnmodifiedMeshVersion = mesh.versionIndex;
        }
        internal void SetShape(Shape shape, PivotLocation location)
        {
            m_PivotLocation = location;
            m_Shape = shape;
            if(m_Shape is Plane || m_Shape is Sprite)
            {
                Bounds bounds = m_ShapeBox;
                var newCenter = bounds.center;
                var newSize = bounds.size;
                newCenter.y = 0;
                newSize.y = 0;
                bounds.center = newCenter;
                bounds.size = newSize;
                m_ShapeBox = bounds;
                m_Size.y = 0;
            }
            //Else if coming from a 2D-state and being back to a 3D shape
            //No changes is pivot is centered
            else if(pivotLocation == PivotLocation.FirstCorner
                    && m_ShapeBox.size.y == 0 && size.y != 0)
            {
                Bounds bounds = m_ShapeBox;
                var newCenter = bounds.center;
                var newSize = bounds.size;
                newCenter.y += size.y / 2f;
                newSize.y = size.y;
                bounds.center = newCenter;
                bounds.size = newSize;
                m_ShapeBox = bounds;
            }
            ResetPivot(mesh, size, rotation);
            Rebuild();
        }
        /// 
        /// Rotates the Shape by a given quaternion while respecting the bounds
        /// 
        internal void RotateInsideBounds(Quaternion deltaRotation)
        {
            ResetPivot(mesh, size, rotation);
            rotation = deltaRotation * rotation;
            Rebuild();
        }
        void ResetPivot(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
        {
            if(mesh != null && mesh.mesh != null)
            {
                var bbCenter = mesh.transform.TransformPoint(m_ShapeBox.center);
                var pivotWorldPos = mesh.transform.TransformPoint(m_PivotPosition);
                mesh.SetPivot(bbCenter);
                m_PivotPosition = mesh.transform.InverseTransformPoint(pivotWorldPos);
                m_ShapeBox = m_Shape.UpdateBounds(mesh, size, rotation, m_ShapeBox);
            }
        }
        void RebuildPivot(ProBuilderMesh mesh, Vector3 size, Quaternion rotation)
        {
            if(mesh != null && mesh.mesh != null)
            {
                var bbCenter = mesh.transform.TransformPoint(m_ShapeBox.center);
                var pivotWorldPos = mesh.transform.TransformPoint(m_PivotPosition);
                mesh.SetPivot(pivotWorldPos);
                m_ShapeBox.center = mesh.transform.InverseTransformPoint(bbCenter);
                m_PivotPosition = mesh.transform.InverseTransformPoint(pivotWorldPos);
                m_ShapeBox = m_Shape.UpdateBounds(mesh, size, rotation, m_ShapeBox);
            }
        }
    }
}