using UnityEngine;

namespace pdinklag.CPW.Common
{
    /// <summary>
    /// Deforms a mesh by shifting affected vertices to a retargeted location
    /// and scaling them according to an influence cylinder.
    /// 
    /// The influence on a vertex depends on its distance from the cylinder.
    /// 
    /// The cylinder can be resized to the target body using a diametrical measure (affecting the radius)
    /// and a vertical measure (affectng the height).
    /// Influenced vertices are then pushed or pulled towards the outer cylinder's surface accordingly.
    /// </summary>
    public class PropDeformCylinder : PropDeform
    {
        [Header("Influence Cylinder")]
        [Tooltip("The radius of the inner cylinder, which fully influences contained vertices.")]
        public float InnerRadius = 0.1f;
        [Tooltip("The height of the inner cylinder, which fully influences contained vertices.")]
        public float InnerHeight = 0.1f;

        [Tooltip("The radius of the outer cylinder, which influences contained vertices based on their distance from the inner cylinder.")]
        public float OuterRadius = 0.2f;
        [Tooltip("The height of the outer cylinder, which influences contained vertices based on their distance from the inner cylinder.")]
        public float OuterHeight = 0.2f;

        [Header("Scale Vertices")]
        [Tooltip("The measure used to determine the diametrical scaling of an influenced vertex; leave null to disable.")]
        public BodyMeasure ScaleDiametrical;

        [Tooltip("The amount of diametrical scaling to apply if any (1 meaning full, 0 meaning none).")]
        [Range(0, 1)]
        public float ScaleDiametricalAmount = 1;

        [Tooltip("The measure used to determine the vertical scaling of an influenced vertex; leave null to disable.")]
        public BodyMeasure ScaleVertical;

        [Tooltip("The amount of vertical scaling to apply if any (1 meaning full, 0 meaning none).")]
        [Range(0, 1)]
        public float ScaleVerticalAmount = 1;

        // precomputed for the current target
        private Vector3 _shift;
        private float _dAspect, _vAspect;

        private float Influence(Vector3 v, Vector3 pos, out Vector3 vPlane)
        {
            vPlane = pos + Vector3.ProjectOnPlane(v - pos, transform.up);
            var hDist = Vector3.Distance(v, vPlane);
            var rDist = Vector3.Distance(vPlane, pos);
            return 1.0f - Mathf.Max(
                Mathf.Clamp01((hDist - 0.5f * InnerHeight) / (0.5f * (OuterHeight - InnerHeight))),
                Mathf.Clamp01((rDist - InnerRadius) / (OuterRadius - InnerRadius)));
        }

        public override float Influence(Vector3 v) => Influence(v, transform.position, out _);

        public override void PrepareDeform(BodyDescription target)
        {
            _shift = GetComponent<BodyLocation>().Target(target) - transform.position;
            _dAspect = ScaleDiametrical ? ScaleDiametrical.Aspect(target) : 1;
            _vAspect = ScaleVertical ? ScaleVertical.Aspect(target) : 1;
        }

        protected override Vector3 Deform(Vector3 v, BodyDescription target)
        {
            var pos = transform.position;
            var influence = Influence(v, pos, out var vPlane);
            if (influence > 0)
            {
                var dScale = ScaleDiametrical ? ScaleDiametricalAmount * (_dAspect - 1.0f) * (vPlane - pos) : Vector3.zero;
                var vScale = ScaleVertical ? ScaleVerticalAmount * (_vAspect - 1.0f) * (v - vPlane) : Vector3.zero;
                return influence * (_shift + dScale + vScale);
            }
            else
            {
                return Vector3.zero;
            }
        }

#if UNITY_EDITOR
        public virtual void OnDrawGizmosSelected()
        {
            var pos = transform.position;
            var norm = transform.up;
            DebugExtension.DrawCylinder(pos - 0.5f * InnerHeight * norm, pos + 0.5f * InnerHeight * norm, Color.cyan, InnerRadius);
            DebugExtension.DrawCylinder(pos - 0.5f * OuterHeight * norm, pos + 0.5f * OuterHeight * norm, Color.blue, OuterRadius);
        }
#endif
    }
}
