using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;


namespace X2Engine
{
    public class AtmosphereEnvComponent : EnvironmentComponent
    {
        public static bool isEnableAtmosphericScattering = true;

        Material precomputeMat;

        static readonly int sp_planetRadiusKm = Shader.PropertyToID("planetRadiusKm");
        static readonly int sp_atmosphereThicknessKm = Shader.PropertyToID("atmosphereThicknessKm");
        static readonly int sp_minOutputAltKm = Shader.PropertyToID("minOutputAltKm");
        static readonly int sp_maxOutputAltKm = Shader.PropertyToID("maxOutputAltKm");
        static readonly int sp_maxDistanceKm = Shader.PropertyToID("maxDistanceKm");
        static readonly int sp_rayleigh = Shader.PropertyToID("rayleigh");
        static readonly int sp_hazeMie = Shader.PropertyToID("hazeMie");
        static readonly int sp_mieRayleighRatio = Shader.PropertyToID("mieRayleighRatio");
        static readonly int sp_ozone = Shader.PropertyToID("ozone");
        static readonly int sp_emissivity = Shader.PropertyToID("emissivity");
        static readonly int sp_mieScaleHeightKm = Shader.PropertyToID("mieScaleHeightKm");
        static readonly int sp_mieBaseHeightKm = Shader.PropertyToID("mieBaseHeightKm");
        static readonly int sp_sunIrradiance = Shader.PropertyToID("sunIrradiance");
        static readonly int sp_sunBrightness = Shader.PropertyToID("sunBrightness");
        static readonly int sp_radiusRadians = Shader.PropertyToID("radiusRadians");
        static readonly int sp_hazeEccentricity = Shader.PropertyToID("hazeEccentricity");
        static readonly int sp_altitudeTexCoord = Shader.PropertyToID("altitudeTexCoord");

        static readonly int sp_fogColor = Shader.PropertyToID("heightFogColor");
        static readonly int sp_fogDensity = Shader.PropertyToID("heightFogDensity");
        static readonly int sp_fogHalfDensityHeight = Shader.PropertyToID("heightFalloff");
        static readonly int sp_fogHeightOffset = Shader.PropertyToID("groundOffset");
        static readonly int sp_fogStartDistance = Shader.PropertyToID("heightFogStart");
        static readonly int sp_fogRange = Shader.PropertyToID("heightFogEnd");


        static readonly int sp_moonColor = Shader.PropertyToID("moonColor");
        static readonly int sp_moonBrightnessContrast = Shader.PropertyToID("moonBrightnessContrast");
        //static readonly int sp_directionToMoon = Shader.PropertyToID("directionToMoon");
        static readonly int sp_moonRadiusRadians = Shader.PropertyToID("moonRadiusRadians");
        static readonly int sp_moonIrradianceFactor = Shader.PropertyToID("moonIrradianceFactor");
        static readonly int sp_moonLightSrcDir = Shader.PropertyToID("moonLightSrcDir");

        static readonly int sp_starlight = Shader.PropertyToID("starlight");
        static readonly int sp_starBrightness = Shader.PropertyToID("starBrightness");
        static readonly int sp_backgroudBrightness = Shader.PropertyToID("backgourdBrightness");

        static readonly int sp_blendToColours = Shader.PropertyToID("blendToColours");
        static readonly int sp_zenith = Shader.PropertyToID("zenith");
        static readonly int sp_horizon = Shader.PropertyToID("horizon");

        //static readonly int sp_ = Shader.PropertyToID("");


        //static readonly int INSCATTER_ALTITUDE_SAMPLE = 4;
        static readonly int NUM_ALTITUDES = 4;
        static readonly int NUM_ELEVATIONS = 32;
        static readonly int NUM_DISTANCES = 32;

        static readonly int sp_Loss_3d = Shader.PropertyToID("loss_texture");
        static readonly int sp_Insc_3d = Shader.PropertyToID("insc_texture");
        static readonly int sp_Loss_2d = Shader.PropertyToID("loss_2d_texture");
        static readonly int sp_Insc_2d = Shader.PropertyToID("insc_2d_texture");
        static readonly int sp_Skyl_2d = Shader.PropertyToID("skyl_2d_texture");

        bool needPrecompute = true;

        CommandBuffer m_commandBuffer;
        public override void OnInit()
        {
            m_commandBuffer = new CommandBuffer();
            m_commandBuffer.name = "PrecomputeAtmosphere";
            //if (Parent.m_precomuteShader)
            //precomputeMat = new Material(Parent.m_precomuteShader);
            if (Parent.m_precomuteMaterial)
                precomputeMat = Parent.m_precomuteMaterial;

            needPrecompute = true;
            SetupRenderTexture();
            var profile = Parent.profile;
            if (profile)
            {
                var atmosphereProfile = profile.atmosphericScattering;
                SetupShaderUniform(ref atmosphereProfile);
                StartPrecompute(precomputeMat);
            }

            if (Parent.m_skyMaterial)
                RenderSettings.skybox = Parent.m_skyMaterial;
        }

        public override void OnDestory()
        {
            Shader.SetGlobalTexture(sp_Loss_3d, null);
            Shader.SetGlobalTexture(sp_Insc_3d, null);

            Shader.SetGlobalTexture(sp_Loss_2d, null);
            Shader.SetGlobalTexture(sp_Insc_2d, null);
            Shader.SetGlobalTexture(sp_Skyl_2d, null);
            ReleaseRenderTexture();
            if (null != m_commandBuffer)
            {
                m_commandBuffer.Dispose();
                m_commandBuffer = null;
            }

        }


        public override void UpdateData()
        {
            var profile = Parent.profile;
            if (profile && profile.atmosphericScattering.enable == true)
            {
                var atmosphereProfile = profile.atmosphericScattering;

                CheckProfileIfNeedPrecompute(ref atmosphereProfile);

                SetupShaderUniform(ref atmosphereProfile);

                StartPrecompute(precomputeMat);
            }

        }

        private AtmosphericScatteringSettings scatteringSetting;
        void CheckProfileIfNeedPrecompute(ref AtmosphericScatteringSettings properties)
        {
            var profile = Parent.profile;
            if (null == profile) return;
            if (!scatteringSetting.CompareScatteringParam(ref properties))
            {
                needPrecompute = true;
                scatteringSetting = properties;
            }
        }

        public override void OnEvent(EnvironmentEvent _event)
        {
            if (_event == EnvironmentEvent.EnvLightDirDirty)
            {
                needPrecompute = true;
                //SetupShaderUniform();
                //StartPrecompute(precomputeMat);
            }
        }

        RenderTexture m_Loss3dRT = null;
        RenderTexture m_Inscatter3dRT = null;
        RenderTexture m_Skyl3dRT = null;

        RenderTexture m_Loss2dRT = null;
        RenderTexture m_Inscatter2dRT = null;
        RenderTexture m_Skyl2dRT = null;
        void SetupRenderTexture()
        {
            RenderTextureDescriptor desc = new RenderTextureDescriptor(NUM_ALTITUDES * NUM_DISTANCES, NUM_ELEVATIONS, 
                RenderTextureFormat.ARGBHalf, 0);
            desc.autoGenerateMips = false;
            desc.sRGB = false;

            m_Loss3dRT = RenderTexture.GetTemporary(desc);
            m_Loss3dRT.name = "Loss3dRT";
            m_Loss3dRT.wrapMode = TextureWrapMode.Clamp;

            m_Inscatter3dRT = RenderTexture.GetTemporary(desc);
            m_Inscatter3dRT.name = "Inscatter3dRT";
            m_Inscatter3dRT.wrapMode = TextureWrapMode.Clamp;

            m_Skyl3dRT = RenderTexture.GetTemporary(desc);
            m_Skyl3dRT.name = "Skyl3dRT";
            m_Skyl3dRT.wrapMode = TextureWrapMode.Clamp;


            desc.width = NUM_DISTANCES;
            m_Loss2dRT = RenderTexture.GetTemporary(desc);
            m_Loss2dRT.name = "Loss2dRT";
            m_Loss2dRT.wrapMode = TextureWrapMode.Clamp;

            m_Inscatter2dRT = RenderTexture.GetTemporary(desc);
            m_Inscatter2dRT.name = "Inscatter2dRT";
            m_Inscatter2dRT.wrapMode = TextureWrapMode.Clamp;

            m_Skyl2dRT = RenderTexture.GetTemporary(desc);
            m_Skyl2dRT.name = "Skyl2dRT";
            m_Skyl2dRT.wrapMode = TextureWrapMode.Clamp;
        }

        void ReleaseRenderTexture()
        {
            RenderTexture.ReleaseTemporary(m_Loss3dRT);
            RenderTexture.ReleaseTemporary(m_Inscatter3dRT);
            RenderTexture.ReleaseTemporary(m_Skyl3dRT);
            RenderTexture.ReleaseTemporary(m_Loss2dRT);
            RenderTexture.ReleaseTemporary(m_Inscatter2dRT);
            RenderTexture.ReleaseTemporary(m_Skyl2dRT);
            m_Loss3dRT = m_Inscatter3dRT = m_Skyl3dRT = m_Loss2dRT = m_Inscatter2dRT = m_Skyl2dRT = null;
        }

        void SetRenderTargetDontCareColor(CommandBuffer cmd, RenderTargetIdentifier colorAttachment)
        {
            cmd.SetRenderTarget(colorAttachment);
        }

        void StartPrecompute(Material pMaterial)
        {
            if (!pMaterial || 
                m_Skyl3dRT == null || 
                m_Skyl2dRT == null || 
                m_Inscatter3dRT == null || 
                m_Inscatter2dRT == null ||
                m_Loss3dRT == null ||
                m_Loss2dRT == null)
                return;
            Shader.SetGlobalTexture(sp_Loss_3d, m_Loss3dRT);
            Shader.SetGlobalTexture(sp_Insc_3d, m_Inscatter3dRT);
            Shader.SetGlobalTexture(sp_Loss_2d, m_Loss2dRT);
            Shader.SetGlobalTexture(sp_Insc_2d, m_Inscatter2dRT);
            Shader.SetGlobalTexture(sp_Skyl_2d, m_Skyl2dRT);

            bool bLastSRGBWrite = GL.sRGBWrite;
            GL.sRGBWrite = false;

            m_commandBuffer.Clear();
            if (needPrecompute)
            {
                SetRenderTargetDontCareColor(m_commandBuffer, m_Loss3dRT);
                m_commandBuffer.Blit(null, m_Loss3dRT, pMaterial, 0);
                SetRenderTargetDontCareColor(m_commandBuffer, m_Inscatter3dRT);
                m_commandBuffer.Blit(null, m_Inscatter3dRT, pMaterial, 1);
                SetRenderTargetDontCareColor(m_commandBuffer, m_Skyl3dRT);
                m_commandBuffer.Blit(null, m_Skyl3dRT, pMaterial, 2);
                needPrecompute = false;
            }
            SetRenderTargetDontCareColor(m_commandBuffer, m_Loss2dRT);
            m_commandBuffer.Blit(m_Loss3dRT, m_Loss2dRT, pMaterial, 3);
            SetRenderTargetDontCareColor(m_commandBuffer, m_Inscatter2dRT);
            m_commandBuffer.Blit(m_Inscatter3dRT, m_Inscatter2dRT, pMaterial, 3);
            SetRenderTargetDontCareColor(m_commandBuffer, m_Skyl2dRT);
            m_commandBuffer.Blit(m_Skyl3dRT, m_Skyl2dRT, pMaterial, 3);

            Graphics.ExecuteCommandBuffer(m_commandBuffer);


            // Apply Precomputed Textures to Material
            GL.sRGBWrite = bLastSRGBWrite;
        }


        void SetupShaderUniform(ref AtmosphericScatteringSettings properties)
        {
            Shader.SetGlobalFloat(sp_planetRadiusKm, properties.planetRadiusKm);
            Shader.SetGlobalFloat(sp_atmosphereThicknessKm, properties.atmosphereThicknessKm);
            Shader.SetGlobalFloat(sp_minOutputAltKm, 0f);
            Shader.SetGlobalFloat(sp_maxOutputAltKm, properties.maxAltitudeKm);
            Shader.SetGlobalFloat(sp_maxDistanceKm, properties.maxDistanceKm);
            Shader.SetGlobalVector(sp_rayleigh, properties.rayleigh);
            Shader.SetGlobalVector(sp_hazeMie, properties.mie * properties.haze / 1000f);
            Vector3 mieRayleighRatio = properties.mie;
            mieRayleighRatio.x /= properties.rayleigh.x * 1000f;
            mieRayleighRatio.y /= properties.rayleigh.y * 1000f;
            mieRayleighRatio.z /= properties.rayleigh.z * 1000f;
            Shader.SetGlobalVector(sp_mieRayleighRatio, mieRayleighRatio);
            Shader.SetGlobalFloat(sp_mieScaleHeightKm, /*properties.hazeBaseKm+*/properties.hazeScaleKm);
            Shader.SetGlobalFloat(sp_mieBaseHeightKm, properties.hazeBaseKm);
            Shader.SetGlobalVector(sp_sunIrradiance, Vector3.Max(properties.sunIrradiance, Vector3.zero));
            Shader.SetGlobalFloat(sp_sunBrightness, properties.sunBrightness);
            Shader.SetGlobalFloat(sp_radiusRadians, properties.sunDiameter * 0.0003f);
            Shader.SetGlobalFloat(sp_hazeEccentricity, Mathf.Min(properties.hazeEccentricity, 0.99f));
            Shader.SetGlobalVector(sp_ozone, properties.baseOzone*properties.ozoneStrength);
            Shader.SetGlobalFloat(sp_emissivity, properties.emissivity);

            Shader.SetGlobalColor(sp_moonColor, properties.moonAlbedo * properties.moonLightColor);
            Shader.SetGlobalFloat(sp_moonBrightnessContrast, properties.moonContrast);
            Shader.SetGlobalFloat(sp_moonRadiusRadians, properties.moonDiameter * 0.0003f);
            //Shader.SetGlobalVector(sp_directionToMoon, new Vector3(0.810f, 0.338f, -0.478f));
            Shader.SetGlobalVector(sp_moonIrradianceFactor, properties.moonLightColor*properties.moonIrradiance / 10f);
            Shader.SetGlobalVector(sp_starlight, new Vector3(properties.starLight, properties.starLight, properties.starLight));
            Shader.SetGlobalFloat(sp_starBrightness, properties.starBrightness);
            Shader.SetGlobalFloat(sp_backgroudBrightness, properties.backgroundBrightness);
            Shader.SetGlobalFloat(sp_blendToColours, properties.blendToColours);
            Shader.SetGlobalVector(sp_zenith, properties.zenith);
            Shader.SetGlobalVector(sp_horizon, properties.horizon);

            DefaultFogSettings defaltFogSetting = new DefaultFogSettings();

            if( Parent.GetDefaultFogSetting(ref defaltFogSetting) )
            {
                
                Shader.SetGlobalColor(sp_fogColor, defaltFogSetting.fogColor);
                Shader.SetGlobalFloat(sp_fogDensity, defaltFogSetting.fogDensity);
                float ln2 = 0.69314718055994530941723212145818f;
                Shader.SetGlobalFloat(sp_fogHalfDensityHeight, ln2 / Mathf.Max(0.01f, defaltFogSetting.fogHalfDensityHeight));
                float heightOffset = Mathf.Min(defaltFogSetting.fogHeightOffset, defaltFogSetting.fogHalfDensityHeight);
                Shader.SetGlobalFloat(sp_fogHeightOffset, heightOffset);
                Shader.SetGlobalFloat(sp_fogStartDistance, defaltFogSetting.fogStartDistance);
                Shader.SetGlobalFloat(sp_fogRange, defaltFogSetting.fogStartDistance + defaltFogSetting.fogRange);
            }
            else
            {
                Shader.SetGlobalColor(sp_fogColor, properties.fogColor);
                Shader.SetGlobalFloat(sp_fogDensity, properties.fogDensity);
                float ln2 = 0.69314718055994530941723212145818f;
                Shader.SetGlobalFloat(sp_fogHalfDensityHeight, ln2 / Mathf.Max(0.01f, properties.fogHalfDensityHeight));
                float heightOffset = Mathf.Min(properties.fogHeightOffset, properties.fogHalfDensityHeight);
                Shader.SetGlobalFloat(sp_fogHeightOffset, heightOffset);
                Shader.SetGlobalFloat(sp_fogStartDistance, properties.fogStartDistance);
                Shader.SetGlobalFloat(sp_fogRange, properties.fogStartDistance + properties.fogRange);
            }
            


            Quaternion moonSrcRot = Quaternion.Euler(0, 0, 360f * properties.moonLightSource.x);
            moonSrcRot *= Quaternion.Euler(0, 180f * properties.moonLightSource.y, 0);
            
            Vector3 moonSrcDir = moonSrcRot * Vector3.back;
            Shader.SetGlobalVector(sp_moonLightSrcDir, moonSrcDir);


            float altitudeTexCoord = 0;
            if (CameraCache.main)
            {
                altitudeTexCoord = CameraCache.main.transform.position.y / 1000.0f;
                altitudeTexCoord = (altitudeTexCoord - 0) / (properties.maxAltitudeKm - 0);
                altitudeTexCoord = Mathf.Sqrt(Mathf.Clamp(altitudeTexCoord, 0, 1));
            }
            altitudeTexCoord = (altitudeTexCoord * (NUM_ALTITUDES-1) + 0.5f) / (float)(NUM_ALTITUDES * NUM_DISTANCES);
            Shader.SetGlobalFloat(sp_altitudeTexCoord, altitudeTexCoord);


        }



    }

}

