using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace Enviro
{
    [Serializable]
    public class EnviroTime 
    {
        public bool simulate;
        public DateTime date = new DateTime(1,1,1,0,0,0);
         
        [SerializeField]
        public int secSerial, minSerial, hourSerial, daySerial, monthSerial, yearSerial;
        public float timeOfDay;
        [Range(-90, 90)]
        [Tooltip("-90,  90   Horizontal earth lines")]
        public float latitude;
        [Range(-180, 180)]
        [Tooltip("-180, 180  Vertical earth line")]
        public float longitude;
        [Range(-13, 13)]
        [Tooltip("Time offset for timezones")]
        public int utcOffset;
        [Tooltip("Realtime minutes for a 24h game time cycle.")]
        public float cycleLengthInMinutes = 10f;
        [Tooltip("Day length modifier will increase/decrease time progression speed at daytime.")]
        [Range(0.1f, 10f)]
        public float dayLengthModifier = 1f;
        [Tooltip("Night length modifier will increase/decrease time progression speed at nighttime.")]
        [Range(0.1f, 10f)]
        public float nightLengthModifier = 1f;
    }
    [Serializable]
    [ExecuteInEditMode]
    public class EnviroTimeModule : EnviroModule
    {  
        public Enviro.EnviroTime Settings;
        public EnviroTimeModule preset;
        public bool showTimeControls,showLocationControls;
        private float LST;
        private float internalTimeOverflow;
        ///////// Time
        public void SetDateTime (int sec, int min, int hours, int day, int month, int year)
        {
            if(year == 0)
               year = 1;
            if(month == 0)
               month = 1;
            if(day == 0)
               day = 1;
 
            Settings.secSerial = sec;
            Settings.minSerial = min;
            Settings.hourSerial = hours;
            Settings.daySerial = day;
            Settings.monthSerial = month;
            Settings.yearSerial = year;
            DateTime curTime = new DateTime(1,1,1,0,0,0);
            curTime = curTime.AddYears(Settings.yearSerial-1);
            curTime = curTime.AddMonths(Settings.monthSerial-1);
            curTime = curTime.AddDays(Settings.daySerial-1);
            curTime = curTime.AddHours(Settings.hourSerial);
            curTime = curTime.AddMinutes(Settings.minSerial);
            curTime = curTime.AddSeconds(Settings.secSerial); 
             
            //Events
            if(EnviroManager.instance.Events != null && EnviroManager.instance.notFirstFrame && Application.isPlaying)
            {
                if(Settings.date.Hour != curTime.Hour)
                   EnviroManager.instance.NotifyHourPassed();
                if(Settings.date.Day != curTime.Day)
                   EnviroManager.instance.NotifyDayPassed();
                if(Settings.date.Year != curTime.Year)
                   EnviroManager.instance.NotifyYearPassed();
            }
            Settings.date = curTime;
            Settings.secSerial = Settings.date.Second;
            Settings.minSerial = Settings.date.Minute;
            Settings.hourSerial = Settings.date.Hour;
            Settings.daySerial = Settings.date.Day;
            Settings.monthSerial = Settings.date.Month;
            Settings.yearSerial = Settings.date.Year; 
 
            Settings.timeOfDay = Settings.date.Hour + (Settings.date.Minute * 0.0166667f) + (Settings.date.Second * 0.000277778f);
        }
        //Time
        public int seconds
        {
            get
            {
                return Settings.date.Second;
            }
            set
            {
                //Settings.secSerial = value;
                SetDateTime(value,Settings.minSerial,Settings.hourSerial,Settings.daySerial,Settings.monthSerial,Settings.yearSerial);
            }
        }
        public int minutes
        {
            get
            {
                return Settings.date.Minute;
            }
            set
            {
                //Settings.minSerial = value;
                SetDateTime(Settings.secSerial,value,Settings.hourSerial,Settings.daySerial,Settings.monthSerial,Settings.yearSerial);
            }
        }
        public int hours
        {
            get
            {
                return Settings.date.Hour;
            }
            set
            {
                //Settings.hourSerial = value;
                SetDateTime(Settings.secSerial,Settings.minSerial,value,Settings.daySerial,Settings.monthSerial,Settings.yearSerial);
            }
        }
        public int days
        {
            get
            {
                return Settings.date.Day;
            }
            set
            {
                //Settings.daySerial = value;
                SetDateTime(Settings.secSerial,Settings.minSerial,Settings.hourSerial,value,Settings.monthSerial,Settings.yearSerial);
            }
        }
        public int months
        {
            get
            {
                return Settings.date.Month;
            }
            set
            {
               //Settings.monthSerial = value;
               SetDateTime(Settings.secSerial,Settings.minSerial,Settings.hourSerial,Settings.daySerial,value,Settings.yearSerial);
            }
        }
        public int years
        {
            get
            {
                return Settings.date.Year;
            }
            set
            {
                //Settings.yearSerial = value;
                SetDateTime(Settings.secSerial,Settings.minSerial,Settings.hourSerial,Settings.daySerial,Settings.monthSerial,value);
            }
        }
 
        // Update Method 
        public override void UpdateModule ()
        { 
            if(Settings.simulate && Application.isPlaying)
            {
                float t = 0f;
                float timeProgressionModifier = 1f;
                if(!EnviroManager.instance.isNight)
                {
                    timeProgressionModifier = Settings.dayLengthModifier;
                }
                else
                {                 
                    timeProgressionModifier = Settings.nightLengthModifier;
                }
                t = (24.0f / 60.0f) / (Settings.cycleLengthInMinutes * timeProgressionModifier);           
                t = t * 3600f * Time.deltaTime;
                if(t < 1f)
                {
                   internalTimeOverflow += t;
                }
                else
                {
                   internalTimeOverflow = t;
                }
                seconds += (int)(internalTimeOverflow);
                if(internalTimeOverflow >= 1f)
                   internalTimeOverflow = 0f; 
            } 
 
            SetDateTime(Settings.secSerial,Settings.minSerial,Settings.hourSerial,Settings.daySerial,Settings.monthSerial,Settings.yearSerial);
            UpdateSunAndMoonPosition();
        }
        public void UpdateSunAndMoonPosition()
        {
            float d = 367 * years - 7 * (years + (months / 12 + 9) / 12) / 4 + 275 * months / 9 + days - 730530;
            d += (GetUniversalTimeOfDay() / 24f); //Universal ToD
            float ecl = 23.4393f - 3.563E-7f * d;
            if(EnviroManager.instance.Sky != null)
            {
                if(EnviroManager.instance.Sky.Settings.moonMode == EnviroSky.MoonMode.Simple)
                {
                    CalculateSunPosition(d, ecl, true);
                }
                else
                {
                    CalculateSunPosition(d, ecl, false);
                    CalculateMoonPosition(d, ecl);
                }
            }
            else
            {
                CalculateSunPosition(d, ecl, false);
                CalculateMoonPosition(d, ecl);
            }
            CalculateStarsPosition(LST);
        }
        /// 
        /// Get current time in hours. UTC0 (12.5 = 12:30)
        /// 
        /// The the current time of day in hours.
        public float GetUniversalTimeOfDay()
        { 
            return Settings.timeOfDay - Settings.utcOffset;
        }
        /// 
        /// Get current time in hours with UTC time offset.
        /// 
        /// The the current time of day in hours.
        public float GetTimeOfDay()
        {
            return Settings.timeOfDay;
        }
        /// 
        /// Get current date in hours.
        /// 
        /// The date in hour format
        public double GetDateInHours()
        {
            double dateInHours = Settings.timeOfDay + (days * 24f) + ((years * 365) * 24f);
            return dateInHours;
        }
        /// 
        /// Set the time of day in hours. (12.5 = 12:30)
        /// 
        public void SetTimeOfDay(float tod)
        {
            Settings.timeOfDay = tod;
            hours = (int)(tod);
            tod -= hours;
            minutes = (int)(tod * 60f);
            tod -= minutes * 0.0166667f;
            seconds = (int)(tod * 3600f);
        }
        public Vector3 OrbitalToLocal(float theta, float phi)
        {
            Vector3 pos;
            float sinTheta = Mathf.Sin(theta);
            float cosTheta = Mathf.Cos(theta);
            float sinPhi = Mathf.Sin(phi);
            float cosPhi = Mathf.Cos(phi);
            pos.z = sinTheta * cosPhi;
            pos.y = cosTheta;
            pos.x = sinTheta * sinPhi;
            return pos;
        }
        public float Remap(float value, float from1, float to1, float from2, float to2)
        {
            return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
        }
        
        public void CalculateSunPosition(float d, float ecl, bool simpleMoon)
        {
            /////http://www.stjarnhimlen.se/comp/ppcomp.html#5////
            ///////////////////////// SUN ////////////////////////
            float w = 282.9404f + 4.70935E-5f * d;
            float e = 0.016709f - 1.151E-9f * d;
            float M = 356.0470f + 0.9856002585f * d;
            float E = M + e * Mathf.Rad2Deg * Mathf.Sin(Mathf.Deg2Rad * M) * (1 + e * Mathf.Cos(Mathf.Deg2Rad * M));
            float xv = Mathf.Cos(Mathf.Deg2Rad * E) - e;
            float yv = Mathf.Sin(Mathf.Deg2Rad * E) * Mathf.Sqrt(1 - e * e);
            float v = Mathf.Rad2Deg * Mathf.Atan2(yv, xv);
            float r = Mathf.Sqrt(xv * xv + yv * yv);
            float l = v + w;
            float xs = r * Mathf.Cos(Mathf.Deg2Rad * l);
            float ys = r * Mathf.Sin(Mathf.Deg2Rad * l);
            float xe = xs;
            float ye = ys * Mathf.Cos(Mathf.Deg2Rad * ecl);
            float ze = ys * Mathf.Sin(Mathf.Deg2Rad * ecl);
            float decl_rad = Mathf.Atan2(ze, Mathf.Sqrt(xe * xe + ye * ye));
            float decl_sin = Mathf.Sin(decl_rad);
            float decl_cos = Mathf.Cos(decl_rad);
            float GMST0 = (l + 180);
            float GMST = GMST0 + GetUniversalTimeOfDay() * 15; 
            LST = GMST + Settings.longitude;
            float HA_deg = LST - Mathf.Rad2Deg * Mathf.Atan2(ye, xe);
            float HA_rad = Mathf.Deg2Rad * HA_deg;
            float HA_sin = Mathf.Sin(HA_rad);
            float HA_cos = Mathf.Cos(HA_rad);
            float x = HA_cos * decl_cos;
            float y = HA_sin * decl_cos;
            float z = decl_sin;
            float sin_Lat = Mathf.Sin(Mathf.Deg2Rad * Settings.latitude);
            float cos_Lat = Mathf.Cos(Mathf.Deg2Rad * Settings.latitude);
            float xhor = x * sin_Lat - z * cos_Lat;
            float yhor = y;
            float zhor = x * cos_Lat + z * sin_Lat;
            float azimuth = Mathf.Atan2(yhor, xhor) + Mathf.Deg2Rad * 180;
            float altitude = Mathf.Atan2(zhor, Mathf.Sqrt(xhor * xhor + yhor * yhor));
            float sunTheta = (90 * Mathf.Deg2Rad) - altitude;
            float sunPhi = azimuth;
            //Set SolarTime: 1 = mid-day (sun directly above you), 0.5 = sunset/dawn, 0 = midnight;
            EnviroManager.instance.solarTime = Mathf.Clamp01(Remap(sunTheta, -1.5f, 0f, 1.5f, 1f));
            EnviroManager.instance.Objects.sun.transform.localPosition = OrbitalToLocal(sunTheta, sunPhi);
            EnviroManager.instance.Objects.sun.transform.LookAt(EnviroManager.instance.transform);
            if (simpleMoon)
            {
                EnviroManager.instance.Objects.moon.transform.localPosition = OrbitalToLocal(sunTheta - Mathf.PI, sunPhi);
                EnviroManager.instance.lunarTime = Mathf.Clamp01(Remap(sunTheta - Mathf.PI, -3.0f, 0f, 0f, 1f));
                EnviroManager.instance.Objects.moon.transform.LookAt(EnviroManager.instance.transform);
            }
        }
        public void CalculateMoonPosition(float d, float ecl)
        {
             float N = 125.1228f - 0.0529538083f * d;
        float i = 5.1454f;
        float w = 318.0634f + 0.1643573223f * d;
        float a = 60.2666f;
        float e = 0.054900f;
        float M = 115.3654f + 13.0649929509f * d;
        float rad_M = Mathf.Deg2Rad * M;
        float E = rad_M + e * Mathf.Sin(rad_M) * (1f + e * Mathf.Cos(rad_M));
        float xv = a * (Mathf.Cos(E) - e);
        float yv = a * (Mathf.Sqrt(1f - e * e) * Mathf.Sin(E));
        float v = Mathf.Rad2Deg * Mathf.Atan2(yv, xv);
        float r = Mathf.Sqrt(xv * xv + yv * yv);
        float rad_N = Mathf.Deg2Rad * N;
        float sin_N = Mathf.Sin(rad_N);
        float cos_N = Mathf.Cos(rad_N);
        float l = Mathf.Deg2Rad * (v + w);
        float sin_l = Mathf.Sin(l);
        float cos_l = Mathf.Cos(l);
        float rad_i = Mathf.Deg2Rad * i;
        float cos_i = Mathf.Cos(rad_i);
        float xh = r * (cos_N * cos_l - sin_N * sin_l * cos_i);
        float yh = r * (sin_N * cos_l + cos_N * sin_l * cos_i);
        float zh = r * (sin_l * Mathf.Sin(rad_i));
        float cos_ecl = Mathf.Cos(Mathf.Deg2Rad * ecl);
        float sin_ecl = Mathf.Sin(Mathf.Deg2Rad * ecl);
        float xe = xh;
        float ye = yh * cos_ecl - zh * sin_ecl;
        float ze = yh * sin_ecl + zh * cos_ecl;
        float ra = Mathf.Atan2(ye, xe);
        float decl = Mathf.Atan2(ze, Mathf.Sqrt(xe * xe + ye * ye));
        float HA = Mathf.Deg2Rad * LST - ra;
        float x = Mathf.Cos(HA) * Mathf.Cos(decl);
        float y = Mathf.Sin(HA) * Mathf.Cos(decl);
        float z = Mathf.Sin(decl);
        float latitude = Mathf.Deg2Rad * Settings.latitude;
        float sin_latitude = Mathf.Sin(latitude);
        float cos_latitude = Mathf.Cos(latitude);
        float xhor = x * sin_latitude - z * cos_latitude;
        float yhor = y;
        float zhor = x * cos_latitude + z * sin_latitude;
        float azimuth = Mathf.Atan2(yhor, xhor) + Mathf.Deg2Rad * 180f;
        float altitude = Mathf.Atan2(zhor, Mathf.Sqrt(xhor * xhor + yhor * yhor));
        float MoonTheta = (90f * Mathf.Deg2Rad) - altitude;
        float MoonPhi = azimuth;
        EnviroManager.instance.Objects.moon.transform.localPosition = OrbitalToLocal(MoonTheta, MoonPhi);
        EnviroManager.instance.lunarTime = Mathf.Clamp01(Remap(MoonTheta, -1.5f, 0f, 1.5f, 1f));
        
        EnviroManager.instance.Objects.moon.transform.LookAt(EnviroManager.instance.transform.position);
        }
        public void CalculateStarsPosition(float siderealTime)
        {
            if (siderealTime > 24) siderealTime -= 24;
            else if (siderealTime < 0) siderealTime += 24;
            Quaternion starsRotation = Quaternion.Euler(90 - Settings.latitude, Mathf.Deg2Rad * Settings.longitude, 0);
            starsRotation *= Quaternion.Euler(0, siderealTime, 0);
            EnviroManager.instance.Objects.stars.transform.localRotation = starsRotation;
            Shader.SetGlobalMatrix("_StarsMatrix", EnviroManager.instance.Objects.stars.transform.worldToLocalMatrix);
        }
        //Save and Load
        public void LoadModuleValues ()
        {
            if(preset != null)
            {
                Settings = JsonUtility.FromJson(JsonUtility.ToJson(preset.Settings));
            }
            else
            {
                Debug.Log("Please assign a saved module to load from!");
            }
        }
        public void SaveModuleValues ()
        {
#if UNITY_EDITOR
        EnviroTimeModule t =  ScriptableObject.CreateInstance();
        t.name = "Time Preset";
        t.Settings = JsonUtility.FromJson(JsonUtility.ToJson(Settings));
 
        string assetPathAndName = UnityEditor.AssetDatabase.GenerateUniqueAssetPath("Assets/Enviro 3" + "/New " + t.name + ".asset");
        UnityEditor.AssetDatabase.CreateAsset(t, assetPathAndName);
        UnityEditor.AssetDatabase.SaveAssets();
        UnityEditor.AssetDatabase.Refresh();
#endif
        }
        public void SaveModuleValues (EnviroTimeModule module)
        {
            module.Settings = JsonUtility.FromJson(JsonUtility.ToJson(Settings));
            #if UNITY_EDITOR
            UnityEditor.EditorUtility.SetDirty(module);
            UnityEditor.AssetDatabase.SaveAssets();
            #endif
        }
    }
}