using System;
using UnityEngine;
using UnityEngine.Rendering;

namespace DayNightCycle
{
    [ExecuteInEditMode]
    public class LightingManager : MonoBehaviour
    {
        private static LightingManager _instance;
        public static LightingManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = FindObjectOfType<LightingManager>();

                    if (_instance == null)
                    {
                        GameObject obj = new GameObject();
                        obj.name = typeof(LightingManager).Name;
                        _instance = obj.AddComponent<LightingManager>();
                    }
                }
                return _instance;
            }
        }

        public class HourPassedEventArgs : EventArgs
        {
            public int Hour { get; set; }
        }
        public static event EventHandler<HourPassedEventArgs> HourPassed;

        [Header("Objects References")]
        [SerializeField][Tooltip("The Directional Light used for simulating the Sun.")] private Light _directionalLightSun;
        [SerializeField][Tooltip("The Lighting Preset that controls the appearance of the lighting.")] private LightingPreset _lightingPreset;

        [Header("Renderer Options")]
        [SerializeField][Tooltip("Ambient Light Mode.")] private AmbientMode _ambientMode;
        [SerializeField][Tooltip("Enable or disable fog.")] private bool _useFog;

        [Header("Variables")]
        [SerializeField][Tooltip("The number of hours in a day.")][Range(1, 48)] private float _hoursPerDay = 24f;
        [SerializeField][Tooltip("The current time of day in hours.")][Range(0, 24)] private float _timeOfDay = 12f;
        [SerializeField][Tooltip("The time in seconds it takes for a full day-night cycle.")] private float _secondsPerRotation = 20f;

        private float _timeFlowSpeed;
        private bool _canPassDay = true;
        private bool _hasError;

        public int CurrentHour { get; private set; }
        public int CurrentMinute { get; private set; }
        public int CurrentDay { get; private set; }

        void Start()
        {
            UpdateRenderSettings();
            _hasError = ValidateObjectReferences();
        }

        private void Update()
        {
            if (Application.isPlaying)
            {
                HandleTimeUpdate();
            }
            else
            {
                UpdateLightingEditor(_timeOfDay / _hoursPerDay);
                UpdateRenderSettings();
            }
        }

        private bool ValidateObjectReferences()
        {
            if (_lightingPreset == null)
            {
                Debug.LogError("No Light Preset was assigned.");
                return true;
            }
            if (_directionalLightSun == null)
            {
                Debug.LogError("No Directional Light was assigned.");
                return true;
            }
            return false;
        }

        private void HandleTimeUpdate()
        {
            if (_hasError) return;

            if (CurrentHour != (int)_timeOfDay && HourPassed != null)
            {
                HourPassed(null, new HourPassedEventArgs() { Hour = (int)_timeOfDay });
            }

            UpdateTimeProperties();
            UpdateDayProperty();

            _timeFlowSpeed = _hoursPerDay / _secondsPerRotation;
            _timeOfDay += _timeFlowSpeed * Time.deltaTime;
            _timeOfDay %= _hoursPerDay;
            float timePercentage = _timeOfDay / _hoursPerDay;
            UpdateLighting(timePercentage);
        }

        private void UpdateTimeProperties()
        {
            CurrentHour = (int)_timeOfDay;
            CurrentMinute = (int)(_timeOfDay % 1 * 60f);
        }

        private void UpdateDayProperty()
        {
            if (CurrentHour == 1) _canPassDay = true;
            if (CurrentHour == 0 && _canPassDay)
            {
                CurrentDay++;
                _canPassDay = false;
            }
        }

        private void UpdateLighting(float timePercent)
        {
            RenderSettings.ambientLight = _lightingPreset.AmbientColor.Evaluate(timePercent);
            RenderSettings.fogColor = _lightingPreset.FogColor.Evaluate(timePercent);

            if (_directionalLightSun != null)
            {
                _directionalLightSun.color = _lightingPreset.DirectionalColor.Evaluate(timePercent);
                _directionalLightSun.transform.localRotation = Quaternion.Euler(new Vector3((timePercent * 360f) - 90f, 170f, 0));
            }
        }

        private void UpdateLightingEditor(float timePercent)
        {
            if (!ValidateObjectReferences()) UpdateLighting(timePercent);
        }

        private void UpdateRenderSettings()
        {
            RenderSettings.ambientMode = _ambientMode;
            RenderSettings.fog = _useFog;
        }

        public void SetDirectionalLightSun(Light directionalLight)
        {
            _directionalLightSun = directionalLight;
        }

        public Light GetDirectionalLightSun()
        {
            return _directionalLightSun;
        }

        public void SetLightingPreset(LightingPreset preset)
        {
            _lightingPreset = preset;
        }

        public LightingPreset GetLightingPreset()
        {
            return _lightingPreset;
        }

        public void SetAmbientMode(AmbientMode mode)
        {
            _ambientMode = mode;
            UpdateRenderSettings();
        }

        public AmbientMode GetAmbientMode()
        {
            return _ambientMode;
        }

        public void SetUseFog(bool useFog)
        {
            _useFog = useFog;
            UpdateRenderSettings();
        }

        public bool GetUseFog()
        {
            return _useFog;
        }

        public float GetTimeOfDay()
        {
            return _timeOfDay;
        }

        public void SetTimeOfDay(float time)
        {
            _timeOfDay = Mathf.Clamp(time, 0f, _hoursPerDay);
        }
    }
}
