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

public class SkyLight 
{
    enum SHColorChannel
    {
        kColorChannelRed = 0,
        kColorChannelGreen = 1,
        kColorChannelBlue = 2,
        kColorChannelCount,
    }

    public  enum SCENE_STATE
    {
        KCommonScene = 0,
        kAvatarScene = 1,
    }

    protected SkyLight()
    {
        
    }
    public static SkyLight Instance
    {
        get
        {
            if(_Instance == null || _Instance._cubeMap == null)
            {
                _Instance = new SkyLight();
            }
            return _Instance;
        }
    }

    private static SkyLight _Instance = null;

    Cubemap _cubeMap =  new Cubemap(SIZE, TextureFormat.RGBA32, true);

    public static int SIZE = 2;

    Color[] _face = new Color[SIZE * SIZE];

    [HideInInspector]
    // ra,ga,ba,rb,gb,bb,c
    Vector4[] coefficients = new Vector4[7];

#if UNITY_EDITOR
    Vector3[] debugSHDir = new Vector3[2]
    {
        new Vector3(0,1,0),
        new Vector3(1,0,0)
    };
    Color[]  debugSHColor = new Color[2];
#endif

    //Dynamic GI
    Material m_CubeSkyMat = null;

    Material m_OldSkyBoxMat = null;

    UnityEngine.Rendering.AmbientMode m_OldAmbientMode;

    UnityEngine.Rendering.SphericalHarmonicsL2 m_OldAmbientProbe;

    static readonly int sp_FrontColor = Shader.PropertyToID("_FrontColor");
    static readonly int sp_BackColor = Shader.PropertyToID("_BackColor");
    static readonly int sp_LeftColor = Shader.PropertyToID("_LeftColor");
    static readonly int sp_RightColor = Shader.PropertyToID("_RightColor");
    static readonly int sp_UpColor = Shader.PropertyToID("_UpColor");
    static readonly int sp_DownColor = Shader.PropertyToID("_DownColor");

    private SCENE_STATE m_CurrentSceneState = SCENE_STATE.KCommonScene;

    static class Uniforms
    {
        internal static readonly int ambient_SHAr = Shader.PropertyToID("x2mCharacter_SHAr");
        internal static readonly int ambient_SHAg = Shader.PropertyToID("x2mCharacter_SHAg");
        internal static readonly int ambient_SHAb = Shader.PropertyToID("x2mCharacter_SHAb");

        internal static readonly int ambient_SHBr = Shader.PropertyToID("x2mCharacter_SHBr");
        internal static readonly int ambient_SHBg = Shader.PropertyToID("x2mCharacter_SHBg");
        internal static readonly int ambient_SHBb = Shader.PropertyToID("x2mCharacter_SHBb");
        internal static readonly int ambient_SHC  = Shader.PropertyToID("x2mCharacter_SHC");


        internal static readonly int scene_ambient_SHAr = Shader.PropertyToID("x2mScene_SHAr");
        internal static readonly int scene_ambient_SHAg = Shader.PropertyToID("x2mScene_SHAg");
        internal static readonly int scene_ambient_SHAb = Shader.PropertyToID("x2mScene_SHAb");

        internal static readonly int scene_ambient_SHBr = Shader.PropertyToID("x2mScene_SHBr");
        internal static readonly int scene_ambient_SHBg = Shader.PropertyToID("x2mScene_SHBg");
        internal static readonly int scene_ambient_SHBb = Shader.PropertyToID("x2mScene_SHBb");
        internal static readonly int scene_ambient_SHC = Shader.PropertyToID("x2mScene_SHC");

        //internal static readonly int ambient_Color = Shader.PropertyToID("ambient_Color");
    }

    public void SetSceneState(SCENE_STATE state)
    {
        m_CurrentSceneState = state;
    }

    public void BakeAmbientProbe(Color sky, Color ground, Color posX, Color negX, Color posZ, Color negZ, ref UnityEngine.Rendering.SphericalHarmonicsL2 sh)
    {
        if (object.ReferenceEquals(_cubeMap, null)) return;

      
        int sqSize = SIZE * SIZE; 
        for (int i = 0; i < sqSize; i++)
            _face[i] = posX;
        _cubeMap.SetPixels(_face, CubemapFace.PositiveX);

        for (int i = 0; i < sqSize; i++)
            _face[i] = negX;
        _cubeMap.SetPixels(_face, CubemapFace.NegativeX);

        for (int i = 0; i < sqSize; i++)
            _face[i] = sky;
        _cubeMap.SetPixels(_face, CubemapFace.PositiveY);

        for (int i = 0; i < sqSize; i++)
            _face[i] = ground;
        _cubeMap.SetPixels(_face, CubemapFace.NegativeY);

        for (int i = 0; i < sqSize; i++)
            _face[i] = posZ;
        _cubeMap.SetPixels(_face, CubemapFace.PositiveZ);

        for (int i = 0; i < sqSize; i++)
            _face[i] = negZ;
        _cubeMap.SetPixels(_face, CubemapFace.NegativeZ);

        _cubeMap.Apply(true);


        EvaluteEnvironmentL2(ref sh);

      
    }

    public static Color GammaToLinear(Color color)
    {
        Color newColor = new Color();
        newColor.r = Mathf.GammaToLinearSpace(color.r);
        newColor.g = Mathf.GammaToLinearSpace(color.g);
        newColor.b = Mathf.GammaToLinearSpace(color.b);
        newColor.a = Mathf.GammaToLinearSpace(color.a);
        return newColor;
    }


    private void InitCubeSkyMat()
    {
        if (m_CubeSkyMat == null)
        {
            //Shader skyBoxShader = Resources.Load("Assets/URP/Shader/HLSL/Sky&Fog/AmbientSkyBox") as Shader;
            Shader skyBoxShader = Shader.Find("URP/Environment/Sky&Fog/6 Sided Color") as Shader;
            m_CubeSkyMat = new Material(skyBoxShader);
        }

    }

    private void GiveBackAmbientSky()
    {
        //we must set the custom mode first,otherwise the SH will be overwrited by unity
        RenderSettings.ambientMode = m_OldAmbientMode;
        RenderSettings.skybox = m_OldSkyBoxMat;
        m_OldSkyBoxMat = null;
        RenderSettings.ambientProbe = m_OldAmbientProbe;

        //RenderSettings.ambientProbe = m_AibientProbe;
    }

    private void UpdateCubeSkyBox(ref X2Engine.AmbientSettings ambientProfile)
    {
        InitCubeSkyMat();

        m_OldAmbientMode = UnityEngine.RenderSettings.ambientMode;
        m_OldAmbientProbe = RenderSettings.ambientProbe;
        UnityEngine.RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox;
        if (m_CubeSkyMat != null)
        {
            m_CubeSkyMat.SetColor(sp_FrontColor, ambientProfile.frontColor);
            m_CubeSkyMat.SetColor(sp_BackColor, ambientProfile.backColor);
            m_CubeSkyMat.SetColor(sp_LeftColor, ambientProfile.leftColor);
            m_CubeSkyMat.SetColor(sp_RightColor, ambientProfile.rightColor);
            m_CubeSkyMat.SetColor(sp_UpColor, ambientProfile.skyColor);
            m_CubeSkyMat.SetColor(sp_DownColor, ambientProfile.groundColor);

            m_OldSkyBoxMat = RenderSettings.skybox;
            RenderSettings.skybox = m_CubeSkyMat;
            //we need to call updateAmbientSH of unity
            //m_AibientProbe = RenderSettings.ambientProbe;
        }
    }

    private void UpdateCubeSkyBox(Color skyColor, Color eqColor, Color groundColor)
    {
        InitCubeSkyMat();

        m_OldAmbientMode = UnityEngine.RenderSettings.ambientMode;
        m_OldAmbientProbe = RenderSettings.ambientProbe;
        UnityEngine.RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox;
        if (m_CubeSkyMat != null)
        {
            m_CubeSkyMat.SetColor(sp_FrontColor, eqColor);
            m_CubeSkyMat.SetColor(sp_BackColor, eqColor);
            m_CubeSkyMat.SetColor(sp_LeftColor, eqColor);
            m_CubeSkyMat.SetColor(sp_RightColor, eqColor);
            m_CubeSkyMat.SetColor(sp_UpColor, skyColor);
            m_CubeSkyMat.SetColor(sp_DownColor, groundColor);

            m_OldSkyBoxMat = RenderSettings.skybox;
            RenderSettings.skybox = m_CubeSkyMat;
            //we need to call updateAmbientSH of unity
            //m_AibientProbe = RenderSettings.ambientProbe;
        }
    }

    public bool BakeSkyColorTOSH(ref X2Engine.AmbientSettings ambientProfile, out SphericalHarmonicsL2 SH)
    {
        UpdateCubeSkyBox(ref ambientProfile);

        //we need to call updateAmbientSH of unity
        DynamicGI.UpdateEnvironment();

        SH = RenderSettings.ambientProbe;

        GiveBackAmbientSky();

      
        return true;
    }

    public bool BakeSkyColorTOSH(Color skyColor, Color eqColor, Color groundColor, out SphericalHarmonicsL2 SH)
    {
        UpdateCubeSkyBox(skyColor, eqColor, groundColor);

        //we need to call updateAmbientSH of unity
        DynamicGI.UpdateEnvironment();

        SH = RenderSettings.ambientProbe;

        GiveBackAmbientSky();


        return true;
    }

    public void SetupToShader(ref UnityEngine.Rendering.SphericalHarmonicsL2 Probe,   bool bCharacterUse = true, SCENE_STATE sceneState = SCENE_STATE.KCommonScene)
    {
        if (m_CurrentSceneState != sceneState)
            return;

#if UNITY_EDITOR
        if (!bCharacterUse)
        {
            Probe.Evaluate(debugSHDir, debugSHColor);
            if (X2Engine.EnvironmentSystem.instance)
            {
                X2Engine.EnvironmentSystem.instance.debugEnvSkyColor = debugSHColor[0];
                X2Engine.EnvironmentSystem.instance.debugEnvFrontColor = debugSHColor[1];
            }
        }
#endif
        ////const float SqrtPI = 1.7724539f;			//Mathf.Sqrt(Mathf.PI);
        //const float Coefficient0 = 0.282094777f;    // 1.0f / (2.0f * SqrtPI);
        //const float Coefficient1 = 0.325735f;       // Mathf.Sqrt(3) / (3 * SqrtPI);
        //const float Coefficient2 = 0.2731371f;      // Mathf.Sqrt(15) / (8 * SqrtPI);
        //const float Coefficient3 = 0.07884789f;     // Mathf.Sqrt(5) / (16 * SqrtPI);
        //const float Coefficient4 = 0.136568546f;    // 0.5f * Coefficient2;


        //// Pack the SH coefficients in a way that makes applying the lighting use the least shader instructions
        //// This has the diffuse convolution coefficients baked in
        //// See "Stupid Spherical Harmonics (SH) Tricks"
        //ambient_SHAr.x = Coefficient1 * Probe[0, 3];
        //ambient_SHAr.y = Coefficient1 * Probe[0, 1];
        //ambient_SHAr.z = Coefficient1 * Probe[0, 2];
        //ambient_SHAr.w = Coefficient0 * Probe[0, 0] - Coefficient3 * Probe[0, 6];

        //ambient_SHAg.x = Coefficient1 * Probe[1, 3];
        //ambient_SHAg.y = Coefficient1 * Probe[1, 1];
        //ambient_SHAg.z = Coefficient1 * Probe[1, 2];
        //ambient_SHAg.w = Coefficient0 * Probe[1, 0] - Coefficient3 * Probe[1, 6];

        //ambient_SHAb.x = Coefficient1 * Probe[2, 3];
        //ambient_SHAb.y = Coefficient1 * Probe[2, 1];
        //ambient_SHAb.z = Coefficient1 * Probe[2, 2];
        //ambient_SHAb.w = Coefficient0 * Probe[2, 0] - Coefficient3 * Probe[2, 6];


        //ambient_SHBr.x = Coefficient2 * Probe[0, 4];
        //ambient_SHBr.y = Coefficient2 * Probe[0, 5];
        //ambient_SHBr.z = 3 * Coefficient3 * Probe[0, 6];
        //ambient_SHBr.w = Coefficient2 * Probe[0, 7];

        //ambient_SHBg.x = Coefficient2 * Probe[1, 4];
        //ambient_SHBg.y = Coefficient2 * Probe[1, 5];
        //ambient_SHBg.z = 3 * Coefficient3 * Probe[1, 6];
        //ambient_SHBg.w = Coefficient2 * Probe[1, 7];

        //ambient_SHBb.x = Coefficient2 * Probe[2, 4];
        //ambient_SHBb.y = Coefficient2 * Probe[2, 5];
        //ambient_SHBb.z = 3 * Coefficient3 * Probe[2, 6];
        //ambient_SHBb.w = Coefficient2 * Probe[2, 7];

        //ambient_SHC.x = Coefficient4 * Probe[0, 8];
        //ambient_SHC.y = Coefficient4 * Probe[1, 8];
        //ambient_SHC.z = Coefficient4 * Probe[2, 8];
        //ambient_SHC.w = 1;

        //int r g b
        for (int channel = (int)SHColorChannel.kColorChannelRed; channel < (int)SHColorChannel.kColorChannelCount; channel++)
        {
            coefficients[channel].x = Probe[channel, 3];
            coefficients[channel].y = Probe[channel, 1];
            coefficients[channel].z = Probe[channel, 2];
            coefficients[channel].w = Probe[channel, 0] - Probe[channel, 6];
        }

        for (int channel = (int)SHColorChannel.kColorChannelRed; channel < (int)SHColorChannel.kColorChannelCount; channel++)
        {
            coefficients[channel + 3].x = Probe[channel, 4];
            coefficients[channel + 3].y = Probe[channel, 5];
            coefficients[channel + 3].z = Probe[channel, 6] * 3.0f;
            coefficients[channel + 3].w = Probe[channel, 7];
        }

        coefficients[6].x = Probe[(int)SHColorChannel.kColorChannelRed, 8];
        coefficients[6].y = Probe[(int)SHColorChannel.kColorChannelGreen, 8];
        coefficients[6].z = Probe[(int)SHColorChannel.kColorChannelBlue, 8];
        coefficients[6].w = 1.0f;

        if (bCharacterUse)
            ApplyShader();
        else
            ApplyShaderScene();

    }

    void ApplyShader()
    {
        Shader.SetGlobalVector(Uniforms.ambient_SHAr, coefficients[0]);
        Shader.SetGlobalVector(Uniforms.ambient_SHAg, coefficients[1]);
        Shader.SetGlobalVector(Uniforms.ambient_SHAb, coefficients[2]);

        Shader.SetGlobalVector(Uniforms.ambient_SHBr, coefficients[3]);
        Shader.SetGlobalVector(Uniforms.ambient_SHBg, coefficients[4]);
        Shader.SetGlobalVector(Uniforms.ambient_SHBb, coefficients[5]);
        Shader.SetGlobalVector(Uniforms.ambient_SHC, coefficients[6]);
    }

    void ApplyShaderScene()
    {
        Shader.SetGlobalVector(Uniforms.scene_ambient_SHAr, coefficients[0]);
        Shader.SetGlobalVector(Uniforms.scene_ambient_SHAg, coefficients[1]);
        Shader.SetGlobalVector(Uniforms.scene_ambient_SHAb, coefficients[2]);

        Shader.SetGlobalVector(Uniforms.scene_ambient_SHBr, coefficients[3]);
        Shader.SetGlobalVector(Uniforms.scene_ambient_SHBg, coefficients[4]);
        Shader.SetGlobalVector(Uniforms.scene_ambient_SHBb, coefficients[5]);
        Shader.SetGlobalVector(Uniforms.scene_ambient_SHC, coefficients[6]);
    }
    void EvaluteEnvironmentL2(ref UnityEngine.Rendering.SphericalHarmonicsL2 output)
    {
        output.Clear();

        float fWt = 0.0f;
        int nTexWidth = _cubeMap.width;
        int nTexHeight = _cubeMap.height;

        if (nTexWidth !=nTexHeight)
        {
            return;
        }

        for (int face = 0; face < 6; face++)
        {
               
            // step between two texels for range [0, 1]
            float invWidth = 1.0f / (float)(nTexWidth);
            // initial negative bound for range [-1, 1]
            float negativeBound = -1.0f + invWidth;
            // step between two texels for range [-1, 1]
            float invWidthBy2 = 2.0f / (float)(nTexWidth);

            for (int y = 0; y < nTexWidth; y++)
            {
                // texture coordinate V in range [-1 to 1]
                float fV = negativeBound + (float)(y) * invWidthBy2;

                for (int x = 0; x < nTexWidth; x++)
                {
                    // texture coordinate U in range [-1 to 1]
                    float fU = negativeBound + (float)(x) * invWidthBy2;

                    // determine direction from center of cube texture to current texel
                    Vector3 dir = new Vector3();
                    switch ((CubemapFace)(face))
                    {
                        case CubemapFace.NegativeX: //swap pos <-> neg
                            dir.x = 1.0f;
                            dir.y = 1.0f - (invWidthBy2 * (float)(y) + invWidth);
                            dir.z = 1.0f - (invWidthBy2 * (float)(x) + invWidth);
                            dir = -dir;
                            break;
                        case CubemapFace.PositiveX:
                            dir.x = -1.0f;
                            dir.y = 1.0f - (invWidthBy2 * (float)(y) + invWidth);
                            dir.z = -1.0f + (invWidthBy2 * (float)(x) + invWidth);
                            dir = -dir;
                            break;
                        case CubemapFace.NegativeY: //swap pos <-> neg
                            dir.x = -1.0f + (invWidthBy2 * (float)(x) + invWidth);
                            dir.y = 1.0f;
                            dir.z = -1.0f + (invWidthBy2 * (float)(y) + invWidth);
                            dir = -dir;
                            break;
                        case CubemapFace.PositiveY:
                            dir.x = -1.0f + (invWidthBy2 * (float)(x) + invWidth);
                            dir.y = -1.0f;
                            dir.z = 1.0f - (invWidthBy2 * (float)(y) + invWidth);
                            dir = -dir;
                            break;
                        case CubemapFace.PositiveZ:
                            dir.x = -1.0f + (invWidthBy2 * (float)(x) + invWidth);
                            dir.y = 1.0f - (invWidthBy2 * (float)(y) + invWidth);
                            dir.z = 1.0f;
                            break;
                        case CubemapFace.NegativeZ:
                            dir.x = 1.0f - (invWidthBy2 * (float)(x) + invWidth);
                            dir.y = 1.0f - (invWidthBy2 * (float)(y) + invWidth);
                            dir.z = -1.0f;
                            break;
                        default:
                            return ;
                    }

                    // normalize direction
                    dir.Normalize();
                    //directions.Add( dir );

                    // get color from texture and map to range [0, 1]
                    Color clr = _cubeMap.GetPixel((CubemapFace)face, x, y);
                    //colors.Add( clr );

                    clr.r = Mathf.GammaToLinearSpace(clr.r);
                    clr.g = Mathf.GammaToLinearSpace(clr.g);
                    clr.b = Mathf.GammaToLinearSpace(clr.b);

                    // scale factor depending on distance from center of the face
                    float fDiffSolid = 4.0f / ((1.0f + fU * fU + fV * fV) *
                        Mathf.Sqrt(1.0f + fU * fU + fV * fV));
                    fWt += fDiffSolid;

                    output.AddDirectionalLight(dir, clr, fDiffSolid);
                }// for x
            }// for y
        } // for face
        float fNormProj = (4.0f * (float)Mathf.PI) / fWt;
        output *= fNormProj;

        //output.Evaluate( directions.ToArray(), colors.ToArray() );
        return ;
    }




}
