using System.Collections;

using UnityEngine;
using UnityEngine.SceneManagement;

using BR.BattleRoyale;
using UPG.Extensions;
using UPG.Debugging;

namespace BR
{
	[SelectionBase]
	[DisallowMultipleComponent]
	public class ColorChanger : MonoBehaviour, IDrainable
	{
        #region STATIC FIELDS
        static Material m_colorChangeMat;
		static Material m_colorChangeMatTransparent;
        static Material m_colorChangeMatEmissive;
		static Material m_colorChangeMatEmissiveTransparent;
        #endregion

		#region COMPONENTS
		MeshFilter m_meshFilter;
		MeshRenderer m_renderer;
		SkinnedMeshRenderer m_skinnedMeshRenderer;
		LODGroup m_lodGroup;
		#endregion

        #region CONSTANTS
        const string ColorProperty = "_Color";
        const string EmissiveProperty = "_EmissionColor";
        #endregion

        #region PRIVATE FIELDS
        BoundedFloat m_energyExhaustion;

        [SerializeField]
        Color[] m_colors = new Color[]
		{
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white,
			Color.white
		};

        [SerializeField] bool[] m_emissives = new bool[20];

		[Header("Draining")]
		[SerializeField] bool m_canBeDrained = true;
		[Header("Debug")]
		[SerializeField] DebugChannel m_debugChannel = null;
		#endregion

		#region READABLES
		MeshFilter Filter =>
			m_meshFilter ?? (m_meshFilter = GetComponent<MeshFilter>());
		MeshRenderer MeshRenderer =>
			m_renderer ?? (m_renderer = GetComponent<MeshRenderer>());
		SkinnedMeshRenderer SkinnedMeshRenderer =>
			m_skinnedMeshRenderer ?? (m_skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>());
		LODGroup LODGroup =>
			m_lodGroup ?? (m_lodGroup = GetComponent<LODGroup>());

		public static Material ColorChangerMat =>
				m_colorChangeMat ?? (m_colorChangeMat = Resources.Load<Material>("Materials/ColorChangerMat"));
		public static Material ColorChangerMatTransparent =>
				m_colorChangeMatTransparent ?? (m_colorChangeMatTransparent = Resources.Load<Material>("Materials/ColorChangerMatTransparent"));
		public static Material ColorChangerMatEmissive =>
				m_colorChangeMatEmissive ?? (m_colorChangeMatEmissive = Resources.Load<Material>("Materials/ColorChangerMatEmissive"));
		public static Material ColorChangerMatEmissiveTransparent =>
				m_colorChangeMatEmissiveTransparent ?? (m_colorChangeMatEmissiveTransparent = Resources.Load<Material>("Materials/ColorChangerMatEmissiveTransparent"));
		public bool CanBeDrained =>
			m_canBeDrained;
		public float DrainRate =>
			Mathf.Sqrt(GetLargestBounds().magnitude) / 50;
		public bool IsDepleted =>
			m_energyExhaustion.Percentage == 1;
		#endregion

		#region DEFAULT METHODS
		void OnValidate()
        {
			Initialize();
		}
		void Awake()
		{
			m_energyExhaustion = new BoundedFloat(DrainRate * 10);
			m_energyExhaustion.OnValueUpdated.AddListener((BoundedFloat _value) => { UpdateColorsAtGrayscale(1 - m_energyExhaustion.Percentage); });
		}
		void Start()
		{
			if (!ConfirmCustomMaterials())
				return;

			SetUpRenderers();

			UpdateColorsAtGrayscale(1);
		}
		#endregion

		#region INITIALIZATION METHODS
		[ContextMenu("Initialize")]
		public void Initialize()
        {
			if (gameObject.scene.name == null && SceneManager.GetActiveScene() != gameObject.scene)
				return;

			EnforceStoredColorSize();

			if (!ConfirmCustomMaterials())
				return;

			SetUpRenderers();

			UpdateColorsAtGrayscale(1);
		}
		#endregion

		#region DATA ENFORCEMENT METHODS
		void EnforceStoredColorSize()
		{
			if (m_colors.Length > 20)
			{
				Color[] _newColors = new Color[20];

				for (int i = 0; i < 20; i++)
					_newColors[i] = m_colors[i];

				m_colors = _newColors;
			}

			while (m_colors.Length < 20)
				m_colors = m_colors.Append(Color.white);

			if (m_emissives.Length > 20)
			{
				bool[] _newColors = new bool[20];

				for (int i = 0; i < 20; i++)
					_newColors[i] = m_emissives[i];

				m_emissives = _newColors;
			}

			while (m_emissives.Length < 20)
				m_emissives = m_emissives.Append(false);
		}
		bool ConfirmCustomMaterials()
		{
			if (!ColorChangerMat)
			{
				m_debugChannel?.Raise(this,
					new string[] {
						"Failed to locate ColorChangerMat",
						"Please make sure that the ColorChangerMat asset is placed in the Assets/Do Not Edit Or Delete/Resources/Materials/ folder." },
					DebugChannel.Severity.Error);

				return false;
			}

			if (!ColorChangerMatTransparent)
			{
				m_debugChannel?.Raise(this,
					new string[] {
					"Failed to locate ColorChangerMatTransparent",
					"Please make sure that the ColorChangerMatTransparent asset is placed in the Assets/Do Not Edit Or Delete/Resources/Materials/ folder." },
					DebugChannel.Severity.Error);

				return false;
			}
			
			if (!ColorChangerMatEmissive)
			{
				m_debugChannel?.Raise(this,
					new string[] {
						"Failed to locate ColorChangerMatEmissive",
						"Please make sure that the ColorChangerMatEmissive asset is placed in the Assets/Do Not Edit Or Delete/Resources/Materials/ folder." },
					DebugChannel.Severity.Error);

				return false;
			}

			if (!ColorChangerMatEmissiveTransparent)
			{
				m_debugChannel?.Raise(this,
					new string[] {
					"Failed to locate ColorChangerMatEmissiveTransparent",
					"Please make sure that the ColorChangerMatEmissiveTransparent asset is placed in the Assets/Do Not Edit Or Delete/Resources/Materials/ folder." },
					DebugChannel.Severity.Error);

				return false;
			}

			return true;
		}
		public void SetUpRenderers()
		{
			if (MeshRenderer)
            {
				SetRendererMaterials(MeshRenderer);

				for (int i = 0; i < MeshRenderer.GetActiveMaterials().Length; i++)
					SetRendererMaterialColor(MeshRenderer, m_colors[i], m_emissives[i], i);
            }

			if (SkinnedMeshRenderer)
            {
				SetRendererMaterials(SkinnedMeshRenderer);

				for (int i = 0; i < SkinnedMeshRenderer.GetActiveMaterials().Length; i++)
					SetRendererMaterialColor(SkinnedMeshRenderer, m_colors[i], m_emissives[i], i);
			}

			if (LODGroup)
				foreach (LOD _lod in LODGroup.GetLODs())
					foreach (Renderer _renderer in _lod.renderers)
					{
						if (!_renderer)
							continue;

						SetRendererMaterials(_renderer);

						for (int i = 0; i < _renderer.GetActiveMaterials().Length; i++)
							SetRendererMaterialColor(_renderer, m_colors[i], m_emissives[i], i);
					}
		}
		#endregion

		#region MESH METHODS
		Vector3 GetLargestBounds()
		{
			Vector3 _largestBounds = Vector3.zero;

			if (Filter && Filter.sharedMesh.bounds.size.VolumeOf() > _largestBounds.VolumeOf())
				_largestBounds = Filter.sharedMesh.bounds.size;

			if (LODGroup)
				foreach (Renderer _renderer in LODGroup.GetLODs()?[0].renderers)
				{
					if (!_renderer)
						continue;

					if (_renderer is SkinnedMeshRenderer)
					{
						if (((SkinnedMeshRenderer)_renderer).sharedMesh.bounds.size.VolumeOf() > _largestBounds.VolumeOf())
							_largestBounds = ((SkinnedMeshRenderer)_renderer).sharedMesh.bounds.size;

						continue;
					}

					MeshFilter _filter = _renderer.GetComponent<MeshFilter>();

					if (!_filter)
						continue;

					if (_filter.sharedMesh.bounds.size.VolumeOf() > _largestBounds.VolumeOf())
						_largestBounds = _filter.sharedMesh.bounds.size;
				}

			return _largestBounds;
		}
		#endregion

		#region MATERIAL METHODS
		Material GetMaterialByColor(Color _color, bool _emissive) =>
			_color.a < 1 ? (!_emissive ? ColorChangerMatTransparent : ColorChangerMatEmissiveTransparent) : (!_emissive ? ColorChangerMat : ColorChangerMatEmissive);
		Color GetColorAtGrayscale(int _index, float _percentage) =>
			Color.Lerp(m_colors[_index], Color.white * m_colors[_index].grayscale, _percentage);
		void UpdateColorsAtGrayscale(float _percentage)
		{
			if (MeshRenderer)
				for (int _materialIndex = 0; _materialIndex < MeshRenderer.GetActiveMaterials().Length; _materialIndex++)
				{
					if (!MeshRenderer.GetActiveMaterials()[_materialIndex] || MeshRenderer.GetActiveMaterials()[_materialIndex] != GetMaterialByColor(m_colors[_materialIndex], m_emissives[_materialIndex]))
						MeshRenderer.GetActiveMaterials()[_materialIndex] = GetMaterialByColor(m_colors[_materialIndex], m_emissives[_materialIndex]);

					SetRendererMaterialColor(MeshRenderer, GetColorAtGrayscale(_materialIndex, 1 - _percentage), m_emissives[_materialIndex], _materialIndex);
                }


			if (SkinnedMeshRenderer)
				for (int _materialIndex = 0; _materialIndex < SkinnedMeshRenderer.GetActiveMaterials().Length; _materialIndex++)
				{
					if (!SkinnedMeshRenderer.GetActiveMaterials()[_materialIndex] || SkinnedMeshRenderer.GetActiveMaterials()[_materialIndex] != GetMaterialByColor(m_colors[_materialIndex], m_emissives[_materialIndex]))
						SkinnedMeshRenderer.GetActiveMaterials()[_materialIndex] = GetMaterialByColor(m_colors[_materialIndex], m_emissives[_materialIndex]);

					SetRendererMaterialColor(SkinnedMeshRenderer, GetColorAtGrayscale(_materialIndex, 1 - _percentage), m_emissives[_materialIndex], _materialIndex);
				}
			
			if (LODGroup)
				foreach (LOD _lod in LODGroup.GetLODs())
					for (int _rendererIndex = 0; _rendererIndex < _lod.renderers.Length; _rendererIndex++)
					{
						if (!_lod.renderers[_rendererIndex] )
							continue;

						for (int _materialIndex = 0; _materialIndex < _lod.renderers[_rendererIndex].GetActiveMaterials().Length; _materialIndex++)
						{
							if (_lod.renderers[_rendererIndex].GetActiveMaterials()[_materialIndex] != GetMaterialByColor(m_colors[_materialIndex], m_emissives[_materialIndex]))
								_lod.renderers[_rendererIndex].GetActiveMaterials()[_materialIndex] = GetMaterialByColor(m_colors[_materialIndex], m_emissives[_materialIndex]);

							SetRendererMaterialColor(_lod.renderers[_rendererIndex], GetColorAtGrayscale(_materialIndex, 1 - _percentage), m_emissives[_materialIndex], _materialIndex);
						}
                    }
		}
		void SetRendererMaterials(Renderer _renderer)
		{
			if (!_renderer)
				return;

			if (_renderer is ParticleSystemRenderer)
				return;

			Material[] _activeMaterials = _renderer.GetActiveMaterials();

			for (int i = 0; i < _activeMaterials.Length; i++)
				_activeMaterials[i] = GetMaterialByColor(m_colors[i], m_emissives[i]);

			_renderer.SetActiveMaterials(_activeMaterials);
		}
		void SetRendererMaterialColor(Renderer _renderer, Color _color, bool _emissive, int _index)
		{
			MaterialPropertyBlock _propBlock = new MaterialPropertyBlock();

			_renderer.GetPropertyBlock(_propBlock, _index);

			if (_emissive)
				_renderer.GetActiveMaterials()[_index].EnableKeyword("_EMISSION");
			else
				_renderer.GetActiveMaterials()[_index].DisableKeyword("_EMISSION");

			_propBlock.SetColor(ColorProperty, _color);
			_propBlock.SetColor(EmissiveProperty, _color);

			_renderer.SetPropertyBlock(_propBlock, _index);
		}
		#endregion

#if UNITY_EDITOR
		#region EDITOR METHODS
        [ContextMenu("Test Clear Materials")]
		void ClearMats()
		{
			m_colorChangeMat = null;
			m_colorChangeMatTransparent = null;
		}
        [ContextMenu("Reset Material Block")]
		void ResetBlock()
        {
			if (MeshRenderer)
				for (int i = 0; i < MeshRenderer.GetActiveMaterials().Length; i++)
					SetRendererMaterialColor(MeshRenderer, Color.white, false, i);
			
			if (SkinnedMeshRenderer)
				for (int i = 0; i < SkinnedMeshRenderer.GetActiveMaterials().Length; i++)
					SetRendererMaterialColor(SkinnedMeshRenderer, Color.white, false, i);

			if (LODGroup)
				foreach (LOD _lod in LODGroup.GetLODs())
					foreach (Renderer _renderer in _lod.renderers)
						for (int i = 0; i < _renderer.GetActiveMaterials().Length; i++)
							SetRendererMaterialColor(_renderer, Color.white, false, i);
		}
		[ContextMenu("Set Grayscale 0%")]
		void TestZeroPercent() =>
			UpdateColorsAtGrayscale(0);
		[ContextMenu("Set Grayscale 50%")]
		void TestFiftyPercent() =>
			UpdateColorsAtGrayscale(0.5f);
		[ContextMenu("Set Grayscale 100%")]
		void TestFullPercent() =>
			UpdateColorsAtGrayscale(1);
		public void SetColorAtIndex(Color _color, bool _emissive, int _index)
		{
			if (m_colors[_index] == _color && m_emissives[_index] == _emissive)
				return;

			m_colors[_index] = _color;
			m_emissives[_index] = _emissive;

			SetUpRenderers();
		}
		#endregion
#endif

		#region DRAIN METHODS
		public float TryDrain()
        {
			if (!CanBeDrained || IsDepleted)
				return 0;

			m_energyExhaustion += DrainRate * 2 * Time.deltaTime;

			if (co_recoverEnergyExhaustion != null)
				StopCoroutine(co_recoverEnergyExhaustion);

			StartCoroutine(co_recoverEnergyExhaustion = RecoverEnergyExhaustion());

			return 2 * (DrainRate - (DrainRate * m_energyExhaustion.Percentage));
        }
		IEnumerator co_recoverEnergyExhaustion;
		IEnumerator RecoverEnergyExhaustion()
        {
			yield return new WaitForSeconds(1);

			m_debugChannel?.Raise(this, gameObject.name + " is starting to recover from energy exhaustion!");

			while (m_energyExhaustion.Percentage > 0)
            {
				m_energyExhaustion -= Time.deltaTime / (DrainRate * 20);

				yield return null;
            }

			m_debugChannel?.Raise(this, gameObject.name + " is fully recovered from energy exhaustion!");

			co_recoverEnergyExhaustion = null;
        }
		#endregion
    }
}