#if UNITY_EDITOR
using UnityEditor;
#endif

using System;

namespace UnityEngine.Rendering
{
    /// <summary>
    /// Data-Driven Lens Flare can be added on any gameobeject
    /// </summary>
    [ExecuteAlways]
    [AddComponentMenu("Rendering/Lens Flare (SRP)")]
    public sealed class LensFlareComponentSRP : MonoBehaviour
    {
        [SerializeField]
        private LensFlareDataSRP m_LensFlareData = null;

        /// <summary>
        /// Lens flare asset used on this component
        /// </summary>
        public LensFlareDataSRP lensFlareData
        {
            get
            {
                return m_LensFlareData;
            }
            set
            {
                m_LensFlareData = value;
                OnValidate();
            }
        }
        /// <summary>
        /// Intensity
        /// </summary>
        [Min(0.0f)]
        public float intensity = 1.0f;
        /// <summary>
        /// Distance used to scale the Distance Attenuation Curve
        /// </summary>
        [Min(1e-5f)]
        public float maxAttenuationDistance = 100.0f;
        /// <summary>
        /// Distance used to scale the Scale Attenuation Curve
        /// </summary>
        [Min(1e-5f)]
        public float maxAttenuationScale = 100.0f;
        /// <summary>
        /// Attenuation by distance
        /// </summary>
        public AnimationCurve distanceAttenuationCurve = new AnimationCurve(new Keyframe(0.0f, 1.0f), new Keyframe(1.0f, 0.0f));
        /// <summary>
        /// Scale by distance, use the same distance as distanceAttenuationCurve
        /// </summary>
        public AnimationCurve scaleByDistanceCurve = new AnimationCurve(new Keyframe(0.0f, 1.0f), new Keyframe(1.0f, 0.0f));
        /// <summary>
        /// If component attached to a light, attenuation the lens flare per light type
        /// </summary>
        public bool attenuationByLightShape = true;
        /// <summary>
        /// Attenuation used radially, which allow for instance to enable flare only on the edge of the screen
        /// </summary>
        public AnimationCurve radialScreenAttenuationCurve = new AnimationCurve(new Keyframe(0.0f, 1.0f), new Keyframe(1.0f, 1.0f));

        /// <summary>
        /// Enable Occlusion feature
        /// </summary>
        public bool useOcclusion = false;
        /// <summary>
        /// Radius around the light used to occlude the flare (value in world space)
        /// </summary>
        [Min(0.0f)]
        public float occlusionRadius = 0.1f;
        /// <summary>
        /// Random Samples Count used inside the disk with 'occlusionRadius'
        /// </summary>
        [Range(1, 64)]
        public uint sampleCount = 32;
        /// <summary>
        /// Z Occlusion Offset allow us to offset the plane where the disc of occlusion is place (closer to camera), value on world space.
        /// Useful for instance to sample occlusion outside a light bulb if we place a flare inside the light bulb
        /// </summary>
        public float occlusionOffset = 0.05f;
        /// <summary>
        /// Global Scale
        /// </summary>
        [Min(0.0f)]
        public float scale = 1.0f;
        /// <summary>
        /// If allowOffScreen is true then If the lens flare is outside the screen we still emit the flare on screen
        /// </summary>
        public bool allowOffScreen = false;

        /// Our default celestial body will have an angular radius of 3.3 degrees. This is an arbitrary number, but must be kept constant
        /// so the occlusion radius for direct lights is consistent regardless of near / far clip plane configuration.
        private static float sCelestialAngularRadius = 3.3f * Mathf.PI / 180.0f;

        /// <summary>
        /// Retrieves the projected occlusion radius from a particular celestial in the infinity plane with an angular radius.
        /// This is used for directional lights which require to have consistent occlusion radius regardless of the near/farplane configuration.
        /// <param name="mainCam">The camera utilized to calculate the occlusion radius</param>
        /// <return>The value, in world units, of the occlusion angular radius.</return>
        /// </summary>
        public float celestialProjectedOcclusionRadius(Camera mainCam)
        {
            float projectedRadius = (float)Math.Tan(sCelestialAngularRadius) * mainCam.farClipPlane;
            return occlusionRadius * projectedRadius;
        }

        /// <summary>
        /// Add or remove the lens flare to the queue of PostProcess
        /// </summary>
        void OnEnable()
        {
            if (lensFlareData)
                LensFlareCommonSRP.Instance.AddData(this);
            else
                LensFlareCommonSRP.Instance.RemoveData(this);
        }

        /// <summary>
        /// Remove the lens flare from the queue of PostProcess
        /// </summary>
        void OnDisable()
        {
            LensFlareCommonSRP.Instance.RemoveData(this);
        }

        /// <summary>
        /// Add or remove the lens flare from the queue of PostProcess
        /// </summary>
        void OnValidate()
        {
            if (isActiveAndEnabled && lensFlareData != null)
            {
                LensFlareCommonSRP.Instance.AddData(this);
            }
            else
            {
                LensFlareCommonSRP.Instance.RemoveData(this);
            }
        }

#if UNITY_EDITOR
        private float sDebugClippingSafePercentage = 0.9f; //for debug gizmo, only push 90% further so we avoid clipping of debug lines.
        void OnDrawGizmosSelected()
        {
            Camera mainCam = Camera.current;
            if (mainCam != null && useOcclusion)
            {
                Vector3 positionWS;
                float adjustedOcclusionRadius = occlusionRadius;
                Light light = GetComponent<Light>();
                if (light != null && light.type == LightType.Directional)
                {
                    positionWS = -transform.forward * (mainCam.farClipPlane * sDebugClippingSafePercentage) + mainCam.transform.position;
                    adjustedOcclusionRadius = celestialProjectedOcclusionRadius(mainCam);
                }
                else
                {
                    positionWS = transform.position;
                }

                Color previousH = Handles.color;
                Color previousG = Gizmos.color;
                Handles.color = Color.red;
                Gizmos.color = Color.red;
                Vector3 dir = (mainCam.transform.position - positionWS).normalized;
                Handles.DrawWireDisc(positionWS + dir * occlusionOffset, dir, adjustedOcclusionRadius, 1.0f);
                Gizmos.DrawWireSphere(positionWS, occlusionOffset);
                Gizmos.color = previousG;
                Handles.color = previousH;
            }
        }

#endif
    }
}