using System.Collections.Generic;

using UnityEngine;

using UPG.Extensions;
using UPG.Debugging;

namespace BR.BattleRoyale.UI
{
    public class ParticleEmitterUtility : MonoBehaviour
    {
        #region ENUMS
        public enum ParticleTransparencyType
        {
            Opaque,
            Fade,
            Cutout,
            Additive
        }
        #endregion

        #region INSPECTOR FIELDS
        [Header("Particle")]
        [SerializeField] Material m_particleMaterial = null;
        [SerializeField] ParticleTransparencyType m_transparencyMode = ParticleTransparencyType.Fade;
        [SerializeField] ParticleSystemSimulationSpace m_simulationSpace = ParticleSystemSimulationSpace.World;

        [Header("Rate")]
        [SerializeField] bool m_burst = false;
        [SerializeField] [Range(0, 100)] float m_particleCount = 10;

        [Header("Play Options")]
        [SerializeField] bool m_playOnce = false;
        [SerializeField] bool m_destroyOnStop = false;

        [Header("Duration")]
        [SerializeField] [Range(0, 30)] float m_duration = 3;
        [SerializeField] [Range(0, 30)] float m_particleLifetime = 3;

        [Header("Size")]
        [SerializeField] [Range(0, 50)] float m_startSize = 1;
        [SerializeField] AnimationCurve m_sizeOverTime = AnimationCurve.EaseInOut(1, 1, 0, 0);
        [SerializeField] ParticleSystemScalingMode m_sizeScaling = ParticleSystemScalingMode.Hierarchy;

        [Header("Speed")]
        [SerializeField] [Range(0, 3)] float m_startSpeed = 1;
        [SerializeField] AnimationCurve m_speedOverTime = AnimationCurve.EaseInOut(1, 1, 0, 0);
        [SerializeField] bool m_fromSystemCenter = false;
        [SerializeField] Vector3 m_direction = Vector3.zero;

        [Header("Rotation")]
        [SerializeField] ParticleSystemRenderSpace m_alignment = ParticleSystemRenderSpace.Facing;
        [SerializeField] Vector2 m_initialRotationBounds = Vector2.zero;
        [SerializeField] Vector2 m_constantRotationBounds = Vector2.zero;
        [SerializeField] [Range(-10, 10)] float m_systemRotation = 0;

        [Header("Color")]
        [SerializeField] Gradient m_colorOverTime = new Gradient();
        [SerializeField] AnimationCurve m_opacityOverTime = AnimationCurve.EaseInOut(1, 1, 0, 0);

        [Header("Spawn Area")]
        [SerializeField] Vector3 m_radius = Vector3.one;
        [SerializeField] ParticleSystemShapeType m_shape = ParticleSystemShapeType.Sphere;
        [SerializeField] [DrawIf("m_shape", ParticleSystemShapeType.Mesh, ComparisonType.Equals)] Mesh m_mesh = null;

        [Header("Trails")]
        [SerializeField] bool m_enableTrails = false;
        [SerializeField] [DrawIf("m_enableTrails", true, ComparisonType.Equals)] Material m_trailMaterial = null;
        [SerializeField] [DrawIf("m_enableTrails", true, ComparisonType.Equals)] [Range(0, 10)] float m_trailWidth = 1;
        [SerializeField] [DrawIf("m_enableTrails", true, ComparisonType.Equals)] AnimationCurve m_trailShape = new AnimationCurve();

        [Header("Debug")]
        [SerializeField] DebugChannel m_debugChannel = null;
        #endregion

        #region PRIVATE FIELDS
        ParticleSystem[] m_particleSystems = new ParticleSystem[0];
        ParticleSystemRenderer[] m_particleRenderers = new ParticleSystemRenderer[0];
        #endregion

        #region DEFAULT METHODS
        void OnValidate()
        {
#if UNITY_EDITOR
            if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this))
                return;
#endif

            m_opacityOverTime.Clamp01();
            m_trailShape.Clamp01();

            EnforceParticleSystems();
        }
        void Awake()
        {
            if (Application.platform == RuntimePlatform.WebGLPlayer)
                gameObject.SelfDestruct();
        }
        #endregion

        #region SET METHODS
        public void SetEmissionRate(float _rate)
        {
            ParticleSystem.EmissionModule[] _emissionModules = GetComponents<ParticleSystem.EmissionModule>();

            if (_emissionModules.Length == 0)
                return;

            for (int i = 0; i < _emissionModules.Length; i++)
                _emissionModules[i].rateOverTime = new ParticleSystem.MinMaxCurve(Mathf.Max(0, _rate));
        }
        public void SetEmissionColor(Color _color)
        {
            ParticleSystem.ColorOverLifetimeModule[] _colorModules = GetComponents<ParticleSystem.ColorOverLifetimeModule>();

            if (_colorModules.Length == 0)
                return;

            for (int i = 0; i < _colorModules.Length; i++)
                _colorModules[i].color = _color;
        }
        public void Burst()
        {
            ParticleSystem.EmissionModule[] _emissionModules = GetComponents<ParticleSystem.EmissionModule>();

            if (_emissionModules.Length == 0)
                return;

            foreach (ParticleSystem _system in m_particleSystems)
                _system.Stop();

            foreach (ParticleSystem.EmissionModule _emissionModule in _emissionModules)
                SetBurstActive(_emissionModule, true);

            foreach (ParticleSystem _system in m_particleSystems)
                _system.Play();

            foreach (ParticleSystem.EmissionModule _emissionModule in _emissionModules)
                SetBurstActive(_emissionModule, false);
        }
        void SetBurstActive(ParticleSystem.EmissionModule _emissionModule, bool _active)
        {
            if (!_active)
            {
                if (_emissionModule.burstCount > 0)
                    _emissionModule.SetBursts(new ParticleSystem.Burst[0]);

                return;
            }

            _emissionModule.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0, new ParticleSystem.MinMaxCurve(m_particleCount), 1, 0) });
        }
        #endregion

        #region ENFORCE METHODS
        void EnforceParticleSystems()
        {
            m_particleSystems = GetComponents<ParticleSystem>();
            m_particleRenderers = GetComponents<ParticleSystemRenderer>();

            if (m_particleSystems.Length == 0)
            {
                m_particleSystems = m_particleSystems.Append(gameObject.AddComponent<ParticleSystem>());
                m_particleRenderers = m_particleRenderers.Append(gameObject.GetComponent<ParticleSystemRenderer>());
            }

            for (int i = 0; i < m_particleSystems.Length; i++)
            {
                if (!m_particleSystems[i])
                    continue;

                m_particleSystems[i].useAutoRandomSeed = true;

                ParticleSystem.MainModule _mainModule = m_particleSystems[i].main;

                _mainModule.playOnAwake = true;
                _mainModule.startSizeMultiplier = m_startSize / 10;
                _mainModule.startSpeedMultiplier = m_startSpeed;
                _mainModule.startRotation = new ParticleSystem.MinMaxCurve(m_initialRotationBounds.x, m_initialRotationBounds.y);
                _mainModule.scalingMode = m_sizeScaling;
                _mainModule.simulationSpace = m_simulationSpace;
                _mainModule.loop = !m_playOnce;
                _mainModule.startLifetime = m_particleLifetime;
                _mainModule.stopAction = m_destroyOnStop ? ParticleSystemStopAction.Destroy : ParticleSystemStopAction.None;

                if (m_duration != _mainModule.duration)
                {
                    m_particleSystems[i].Stop();

                    _mainModule.duration = m_duration;

                    if (m_particleSystems[i].isPlaying)
                        DebugChannel.Raise("Default", this,
                            new string[]
                            {
                            "This is a harmless error. Please stop any active particle preview before changing the Particle Utility's Lifetime parameter.",
                            "Click the Clear button at the top-left corner of the console to remove these messages."
                            },
                            DebugChannel.Severity.Error);
                }

                ParticleSystem.EmissionModule _emissionModule = m_particleSystems[i].emission;

                if (!_emissionModule.enabled)
                    _emissionModule.enabled = true;

                _emissionModule.rateOverTime = m_burst ? 0 : new ParticleSystem.MinMaxCurve(m_particleCount);
                SetBurstActive(_emissionModule, m_burst);

                ParticleSystem.VelocityOverLifetimeModule _velocityOverTimeModule = m_particleSystems[i].velocityOverLifetime;

                if (!_velocityOverTimeModule.enabled)
                    _velocityOverTimeModule.enabled = true;

                _velocityOverTimeModule.speedModifier = new ParticleSystem.MinMaxCurve(m_startSpeed, m_speedOverTime);
                _velocityOverTimeModule.x = m_direction.x;
                _velocityOverTimeModule.y = m_direction.y;
                _velocityOverTimeModule.z = m_direction.z;

                _velocityOverTimeModule.orbitalY = m_systemRotation;
                _velocityOverTimeModule.radial = m_fromSystemCenter ? 1 : 0;

                ParticleSystem.RotationOverLifetimeModule _rotationOverTimeModule = m_particleSystems[i].rotationOverLifetime;

                if (!_rotationOverTimeModule.enabled)
                    _rotationOverTimeModule.enabled = true;

                if (m_constantRotationBounds.y < m_constantRotationBounds.x)
                    m_constantRotationBounds = Vector2.one * m_constantRotationBounds.x;

                _rotationOverTimeModule.z = new ParticleSystem.MinMaxCurve(m_constantRotationBounds.x, m_constantRotationBounds.y);

                ParticleSystem.SizeOverLifetimeModule _sizeOverTimeModule = m_particleSystems[i].sizeOverLifetime;

                if (!_sizeOverTimeModule.enabled)
                    _sizeOverTimeModule.enabled = true;

                _sizeOverTimeModule.size = new ParticleSystem.MinMaxCurve(m_startSize, m_sizeOverTime);

                ParticleSystem.ColorOverLifetimeModule _colorOverTimeModule = m_particleSystems[i].colorOverLifetime;

                if (!_colorOverTimeModule.enabled)
                    _colorOverTimeModule.enabled = true;

                List<GradientAlphaKey> _newAlphaKeys = new List<GradientAlphaKey>();

                _newAlphaKeys.Add(new GradientAlphaKey(m_opacityOverTime.Evaluate(0), 0));

                foreach (Keyframe _key in m_opacityOverTime.keys)
                    if (_key.time > 0 && _key.time < 1)
                        _newAlphaKeys.Add(new GradientAlphaKey(_key.value, _key.time));

                _newAlphaKeys.Add(new GradientAlphaKey(m_opacityOverTime.Evaluate(1), 1));

                Gradient _newGradient = new Gradient();

                _newGradient.SetKeys(m_colorOverTime.colorKeys, _newAlphaKeys.ToArray());

                _colorOverTimeModule.color = new ParticleSystem.MinMaxGradient(_newGradient);

                ParticleSystem.ShapeModule _shapeModule = m_particleSystems[i].shape;

                if (m_shape == ParticleSystemShapeType.MeshRenderer)
                {
                    if (!_shapeModule.meshRenderer)
                        _shapeModule.meshRenderer = GetComponent<MeshRenderer>();

                    if (!_shapeModule.meshRenderer)
                    {
                        m_debugChannel?.Raise(this,
                            new string[] {
                            "Particle Emitter Utility has no Mesh Renderer on the same GameObject.",
                            "Please add a Mesh Renderer to use the Mesh Renderer spawn shape." });

                        m_shape = _shapeModule.shapeType;
                    }
                    else
                        _shapeModule.shapeType = m_shape;
                }
                else if (m_shape == ParticleSystemShapeType.SkinnedMeshRenderer)
                {
                    if (!_shapeModule.skinnedMeshRenderer)
                        _shapeModule.skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();

                    if (!_shapeModule.skinnedMeshRenderer)
                    {
                        m_debugChannel?.Raise(this,
                            new string[] {
                            "Particle Emitter Utility has no Skinned Mesh Renderer on the same GameObject.",
                            "Please add a Skinned Mesh Renderer to use the Skinned Mesh Renderer spawn shape." });

                        m_shape = _shapeModule.shapeType;
                    }
                    else
                        _shapeModule.shapeType = m_shape;
                }
                else
                    _shapeModule.shapeType = m_shape;

                _shapeModule.mesh = m_mesh;

                _shapeModule.scale = m_radius;

                if (m_particleRenderers[i])
                {
                    if (!Application.isPlaying && m_particleRenderers[i].sharedMaterial == null)
                        m_particleRenderers[i].sharedMaterial = m_particleMaterial;
                    else if (Application.isPlaying && m_particleRenderers[i].material == null)
                        m_particleRenderers[i].material = m_particleMaterial;

                    if (m_particleRenderers[i].trailMaterial == null)
                        m_particleRenderers[i].trailMaterial = m_trailMaterial;

                    if (Application.isPlaying && m_particleRenderers[i].material != null)
                    {
                        m_particleRenderers[i].material.SetFloat("_Cull",
                            m_alignment != ParticleSystemRenderSpace.View ? 0f : 2.0f);

                        switch (m_transparencyMode)
                        {
                            case (ParticleTransparencyType.Opaque):
                                m_particleRenderers[i].material.ToOpaqueMode();
                                break;
                            case (ParticleTransparencyType.Fade):
                                m_particleRenderers[i].material.ToFadeMode();
                                break;
                            case (ParticleTransparencyType.Cutout):
                                m_particleRenderers[i].material.ToCutoutMode();
                                break;
                            case (ParticleTransparencyType.Additive):
                                m_particleRenderers[i].material.ToAdditiveMode();
                                break;
                        }
                    }
                    else if (!Application.isPlaying && m_particleRenderers[i].sharedMaterial != null)
                    {
                        m_particleRenderers[i].sharedMaterial.SetFloat("_Cull",
                            m_alignment != ParticleSystemRenderSpace.View ? 0f : 2.0f);

                        switch (m_transparencyMode)
                        {
                            case (ParticleTransparencyType.Opaque):
                                m_particleRenderers[i].sharedMaterial.ToOpaqueMode();
                                break;
                            case (ParticleTransparencyType.Fade):
                                m_particleRenderers[i].sharedMaterial.ToFadeMode();
                                break;
                            case (ParticleTransparencyType.Cutout):
                                m_particleRenderers[i].sharedMaterial.ToCutoutMode();
                                break;
                            case (ParticleTransparencyType.Additive):
                                m_particleRenderers[i].sharedMaterial.ToAdditiveMode();
                                break;
                        }
                    }

                    if (m_particleRenderers[i].trailMaterial != null)
                    {
                        m_particleRenderers[i].trailMaterial.SetFloat("_Cull",
                            m_alignment != ParticleSystemRenderSpace.View ? 0f : 2.0f);

                        switch (m_transparencyMode)
                        {
                            case (ParticleTransparencyType.Opaque):
                                m_particleRenderers[i].trailMaterial.ToOpaqueMode();
                                break;
                            case (ParticleTransparencyType.Fade):
                                m_particleRenderers[i].trailMaterial.ToFadeMode();
                                break;
                            case (ParticleTransparencyType.Cutout):
                                m_particleRenderers[i].trailMaterial.ToCutoutMode();
                                break;
                            case (ParticleTransparencyType.Additive):
                                m_particleRenderers[i].trailMaterial.ToAdditiveMode();
                                break;
                        }
                    }

                    m_particleRenderers[i].alignment = m_alignment;
                }

                ParticleSystem.TrailModule _trailModule = m_particleSystems[i].trails;

                _trailModule.enabled = m_enableTrails;

                _trailModule.widthOverTrail = new ParticleSystem.MinMaxCurve(m_trailWidth, m_trailShape);
            }
        }
        #endregion
    }
}