using SharpDX;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Device = SharpDX.Direct3D11.Device;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;
using SharpDX.Direct3D11;
using System.IO;
using CodeWalker.GameFiles;
using CodeWalker.World;

namespace CodeWalker.Rendering
{

    public struct SkydomeShaderVSSceneVars
    {
        public Matrix ViewProj;
        public Matrix ViewInv;
    }
    public struct SkydomeShaderVSEntityVars
    {
        public Vector4 CamRel;
        public Quaternion Orientation;
    }
    public struct SkydomeShaderVSModelVars
    {
        public Matrix Transform;
    }
    public struct SkydomeShaderPSSceneVars
    {
        public Vector4 LightDirection;
        public uint EnableHDR;
        public uint Pad0;
        public uint Pad1;
        public uint Pad2;
    }

    public struct SkydomeShaderVSSunMoonVars
    {
        public Matrix ViewProj;
        public Matrix ViewInv;
        public Vector4 CamRel;
        public Vector2 Size;
        public Vector2 Offset;
    }
    public struct SkydomeShaderPSSunMoonVars
    {
        public Vector4 Colour;
    }


    public struct SkydomeShaderSkySystemLocals
    {
        public Vector4 azimuthEastColor;           // Offset:    0 Size:    12 
        public Vector4 azimuthWestColor;           // Offset:   16 Size:    12 
        public Vector3 azimuthTransitionColor;     // Offset:   32 Size:    12 
        public float azimuthTransitionPosition;    // Offset:   44 Size:     4 
        public Vector4 zenithColor;                // Offset:   48 Size:    12 
        public Vector4 zenithTransitionColor;      // Offset:   64 Size:    12 
        public Vector4 zenithConstants;            // Offset:   80 Size:    16 
        public Vector4 skyPlaneColor;              // Offset:   96 Size:    16 [unused]
        public Vector4 skyPlaneParams;             // Offset:  112 Size:    16 [unused]
        public float hdrIntensity;                 // Offset:  128 Size:     4 
        public Vector3 sunColor;                   // Offset:  132 Size:    12
        public Vector4 sunColorHdr;                // Offset:  144 Size:    12
        public Vector4 sunDiscColorHdr;            // Offset:  160 Size:    12 [unused]
        public Vector4 sunConstants;               // Offset:  176 Size:    16
        public Vector4 sunDirection;               // Offset:  192 Size:    12
        public Vector4 sunPosition;                // Offset:  208 Size:    12 [unused]
        public Vector4 cloudBaseMinusMidColour;    // Offset:  224 Size:    12
        public Vector4 cloudMidColour;             // Offset:  240 Size:    12
        public Vector4 cloudShadowMinusBaseColourTimesShadowStrength;// Offset:  256 Size:    12
        public Vector4 cloudDetailConstants;       // Offset:  272 Size:    16
        public Vector4 cloudConstants1;            // Offset:  288 Size:    16
        public Vector4 cloudConstants2;            // Offset:  304 Size:    16
        public Vector4 smallCloudConstants;        // Offset:  320 Size:    16
        public Vector4 smallCloudColorHdr;         // Offset:  336 Size:    12
        public Vector4 effectsConstants;           // Offset:  352 Size:    16
        public float horizonLevel;                 // Offset:  368 Size:     4 
        public Vector3 speedConstants;             // Offset:  372 Size:    12 
        public float starfieldIntensity;           // Offset:  384 Size:     4
        public Vector3 moonDirection;              // Offset:  388 Size:    12
        public Vector3 moonPosition;               // Offset:  400 Size:    12 [unused]
        public float moonIntensity;                // Offset:  412 Size:     4 [unused]
        public Vector4 lunarCycle;                 // Offset:  416 Size:    12
        public Vector3 moonColor;                  // Offset:  432 Size:    12
        public float noiseFrequency;               // Offset:  444 Size:     4 [unused]
        public float noiseScale;                   // Offset:  448 Size:     4 [unused]
        public float noiseThreshold;               // Offset:  452 Size:     4 [unused]
        public float noiseSoftness;                // Offset:  456 Size:     4 [unused]
        public float noiseDensityOffset;           // Offset:  460 Size:     4 [unused]
        public Vector4 noisePhase;                 // Offset:  464 Size:     8 [unused]
    }


    public class SkydomeShader : Shader, IDisposable
    {
        bool disposed = false;

        VertexShader skyvs;
        PixelShader skyps;
        VertexShader sunvs;
        PixelShader sunps;
        VertexShader moonvs;
        PixelShader moonps;

        GpuVarsBuffer<SkydomeShaderSkySystemLocals> SkyLocalVars;
        GpuVarsBuffer<SkydomeShaderVSSceneVars> VSSceneVars;
        GpuVarsBuffer<SkydomeShaderVSEntityVars> VSEntityVars;
        GpuVarsBuffer<SkydomeShaderVSModelVars> VSModelVars;
        GpuVarsBuffer<SkydomeShaderPSSceneVars> PSSceneVars;

        GpuVarsBuffer<SkydomeShaderVSSunMoonVars> VSSunMoonVars;
        GpuVarsBuffer<SkydomeShaderPSSunMoonVars> PSSunMoonVars;


        SamplerState texsampler;

        InputLayout skylayout;
        InputLayout sunlayout;
        InputLayout moonlayout;

        UnitDisc sundisc;
        UnitQuad moonquad;

        public bool EnableHDR { get; set; }


        public SkydomeShader(Device device)
        {
            byte[] skyvsbytes = File.ReadAllBytes("Shaders\\SkydomeVS.cso");
            byte[] skypsbytes = File.ReadAllBytes("Shaders\\SkydomePS.cso");
            byte[] sunvsbytes = File.ReadAllBytes("Shaders\\SkySunVS.cso");
            byte[] sunpsbytes = File.ReadAllBytes("Shaders\\SkySunPS.cso");
            byte[] moonvsbytes = File.ReadAllBytes("Shaders\\SkyMoonVS.cso");
            byte[] moonpsbytes = File.ReadAllBytes("Shaders\\SkyMoonPS.cso");

            skyvs = new VertexShader(device, skyvsbytes);
            skyps = new PixelShader(device, skypsbytes);
            sunvs = new VertexShader(device, sunvsbytes);
            sunps = new PixelShader(device, sunpsbytes);
            moonvs = new VertexShader(device, moonvsbytes);
            moonps = new PixelShader(device, moonpsbytes);

            SkyLocalVars = new GpuVarsBuffer<SkydomeShaderSkySystemLocals>(device);
            VSSceneVars = new GpuVarsBuffer<SkydomeShaderVSSceneVars>(device);
            VSEntityVars = new GpuVarsBuffer<SkydomeShaderVSEntityVars>(device);
            VSModelVars = new GpuVarsBuffer<SkydomeShaderVSModelVars>(device);
            PSSceneVars = new GpuVarsBuffer<SkydomeShaderPSSceneVars>(device);

            VSSunMoonVars = new GpuVarsBuffer<SkydomeShaderVSSunMoonVars>(device);
            PSSunMoonVars = new GpuVarsBuffer<SkydomeShaderPSSunMoonVars>(device);

            sundisc = new UnitDisc(device, 30, true);
            sunlayout = new InputLayout(device, sunvsbytes, sundisc.GetLayout());
            skylayout = new InputLayout(device, skyvsbytes, VertexTypeGTAV.GetLayout(VertexType.PTT));

            moonquad = new UnitQuad(device, true);
            moonlayout = new InputLayout(device, moonvsbytes, moonquad.GetLayout());

            texsampler = new SamplerState(device, new SamplerStateDescription()
            {
                AddressU = TextureAddressMode.Wrap,
                AddressV = TextureAddressMode.Wrap,
                AddressW = TextureAddressMode.Wrap,
                BorderColor = Color.Black,
                ComparisonFunction = Comparison.Always,
                Filter = Filter.MinMagMipLinear,
                MaximumAnisotropy = 1,
                MaximumLod = float.MaxValue,
                MinimumLod = 0,
                MipLodBias = 0,
            });

        }



        public override void SetShader(DeviceContext context)
        {
            context.VertexShader.Set(skyvs);
            context.PixelShader.Set(skyps);
        }

        public override bool SetInputLayout(DeviceContext context, VertexType type)
        {
            if (type != VertexType.PTT)
            {
                return false;
            }
            context.InputAssembler.InputLayout = skylayout;
            return true;
        }

        public void UpdateSkyLocals(Weather weather, ShaderGlobalLights lights)
        {
            var wv = weather.CurrentValues;

            float skyhdr = EnableHDR ? wv.skyHdr : Math.Min(wv.skyHdr, 1.8f);
            float sunhdr = EnableHDR ? wv.skySunHdr : Math.Min(wv.skySunHdr, 4.8f); //NO HDR - turn it down!!!
            float scathdr = EnableHDR ? wv.skySunScatterInten : Math.Min(wv.skySunScatterInten, 1.8f);
            Vector3 suncolhdr = wv.skySunCol * sunhdr;
            Vector4 azecol = wv.skyAzimuthEastCol;
            Vector4 azwcol = wv.skyAzimuthWestCol;
            Vector4 aztcol = wv.skyAzimuthTransitionCol;
            Vector4 zencol = wv.skyZenithCol;
            Vector4 zetcol = wv.skyZenithTransitionCol;
            Vector4 plncol = wv.skyPlane;

            SkyLocalVars.Vars.azimuthEastColor = azecol;
            SkyLocalVars.Vars.azimuthWestColor = azwcol;
            SkyLocalVars.Vars.azimuthTransitionColor = aztcol.XYZ();
            SkyLocalVars.Vars.azimuthTransitionPosition = wv.skyAzimuthTransition;
            SkyLocalVars.Vars.zenithColor = zencol;
            SkyLocalVars.Vars.zenithTransitionColor = zetcol;
            SkyLocalVars.Vars.zenithConstants = wv.skyZenithTransition;
            SkyLocalVars.Vars.skyPlaneColor = plncol;
            SkyLocalVars.Vars.skyPlaneParams = Vector4.Zero;
            SkyLocalVars.Vars.hdrIntensity = skyhdr;
            SkyLocalVars.Vars.sunColor = wv.skySunCol;
            SkyLocalVars.Vars.sunColorHdr = new Vector4(suncolhdr, sunhdr);
            SkyLocalVars.Vars.sunDiscColorHdr = new Vector4(wv.skySunDiscCol, wv.skySunDiscSize);
            //SkyLocalVars.Vars.sunConstants = new Vector4(wv.skySunMie, scathdr);
            //SkyLocalVars.Vars.sunConstants.X = wv.skySunMie.X; //mie phase
            //SkyLocalVars.Vars.sunConstants.Y = wv.skySunMie.Y; //mie scatter
            //SkyLocalVars.Vars.sunConstants.Z = 0.5f / wv.skySunInfluenceRadius;// * 0.01f; // 0.0025f;  //mie size/"range"
            //SkyLocalVars.Vars.sunConstants.W = wv.skySunMie.Z;// * scathdr; //mie hdr intensity
            SkyLocalVars.Vars.sunConstants.X = wv.skySunMie.X * wv.skySunMie.Y; //mie phase
            SkyLocalVars.Vars.sunConstants.Y = wv.skySunMie.Y;// 1.0f /wv.skySunMie.Y;// 1.7f;// wv.skySunMie.Y; //mie scatter
            SkyLocalVars.Vars.sunConstants.Z = 0.00003f * wv.skySunInfluenceRadius;// * wv.skySunMie.X;// wv.skySunMie.Z;///720.0f;// * 0.01f; // 0.0025f;  //mie size/"range"
            SkyLocalVars.Vars.sunConstants.W = wv.skySunMie.Z;// * scathdr; //mie intensity multiplier
            SkyLocalVars.Vars.sunDirection = new Vector4(-lights.CurrentSunDir, 0);// wv.sunDirection, 0);
            SkyLocalVars.Vars.sunPosition = new Vector4(-lights.CurrentSunDir, 0); //not used
            SkyLocalVars.Vars.cloudBaseMinusMidColour = Vector4.Zero;
            SkyLocalVars.Vars.cloudMidColour = Vector4.Zero;
            SkyLocalVars.Vars.cloudShadowMinusBaseColourTimesShadowStrength = Vector4.Zero;
            SkyLocalVars.Vars.cloudDetailConstants = Vector4.Zero;
            SkyLocalVars.Vars.cloudConstants1 = Vector4.Zero;
            SkyLocalVars.Vars.cloudConstants2 = Vector4.Zero;
            SkyLocalVars.Vars.smallCloudConstants = Vector4.Zero;
            SkyLocalVars.Vars.smallCloudColorHdr = Vector4.Zero;
            SkyLocalVars.Vars.effectsConstants = Vector4.Zero;
            SkyLocalVars.Vars.horizonLevel = 0;
            SkyLocalVars.Vars.speedConstants = Vector3.Zero;
            SkyLocalVars.Vars.starfieldIntensity = wv.skyStarsIten * 5.0f; //makes stars brighter....
            SkyLocalVars.Vars.moonDirection = wv.moonDirection;
            SkyLocalVars.Vars.moonPosition = Vector3.Zero;//need to update this?
            SkyLocalVars.Vars.moonIntensity = wv.skyMoonIten;
            SkyLocalVars.Vars.lunarCycle = Vector4.Zero;
            SkyLocalVars.Vars.moonColor = wv.skyMoonCol;
            SkyLocalVars.Vars.noiseFrequency = 0;
            SkyLocalVars.Vars.noiseScale = 0;
            SkyLocalVars.Vars.noiseThreshold = 0;
            SkyLocalVars.Vars.noiseSoftness = 0;
            SkyLocalVars.Vars.noiseDensityOffset = 0;
            SkyLocalVars.Vars.noisePhase = Vector4.Zero;
        }

        public override void SetSceneVars(DeviceContext context, Camera camera, Shadowmap shadowmap, ShaderGlobalLights lights)
        {
            SkyLocalVars.Update(context);
            SkyLocalVars.SetVSCBuffer(context, 0);
            SkyLocalVars.SetPSCBuffer(context, 0);

            VSSceneVars.Vars.ViewProj = Matrix.Transpose(camera.ViewProjMatrix);
            VSSceneVars.Vars.ViewInv = Matrix.Transpose(camera.ViewInvMatrix);
            VSSceneVars.Update(context);
            VSSceneVars.SetVSCBuffer(context, 1);

            PSSceneVars.Vars.LightDirection = new Vector4(lights.Params.LightDir, 0.0f);
            PSSceneVars.Vars.EnableHDR = EnableHDR ? 1u : 0u;
            PSSceneVars.Update(context);
            PSSceneVars.SetPSCBuffer(context, 1);
        }

        public override void SetEntityVars(DeviceContext context, ref RenderableInst rend)
        {
            VSEntityVars.Vars.CamRel = new Vector4(rend.CamRel, 0.0f);
            VSEntityVars.Vars.Orientation = rend.Orientation;
            VSEntityVars.Update(context);
            VSEntityVars.SetVSCBuffer(context, 2);
        }

        public override void SetModelVars(DeviceContext context, RenderableModel model)
        {
            if (!model.UseTransform) return;
            VSModelVars.Vars.Transform = Matrix.Transpose(model.Transform);
            VSModelVars.Update(context);
            VSModelVars.SetVSCBuffer(context, 3);
        }

        public override void SetGeomVars(DeviceContext context, RenderableGeometry geom)
        {
            //don't use this version
        }

        public void SetTextures(DeviceContext context, RenderableTexture starfield)
        {
            if (starfield != null)
            {
                context.PixelShader.SetSampler(0, texsampler);
                starfield.SetPSResource(context, 0);
            }
        }

        public override void UnbindResources(DeviceContext context)
        {
            context.VertexShader.SetConstantBuffer(0, null);
            context.PixelShader.SetConstantBuffer(0, null);
            context.VertexShader.SetConstantBuffer(1, null);
            context.PixelShader.SetConstantBuffer(1, null);
            context.VertexShader.SetConstantBuffer(2, null);
            context.VertexShader.SetConstantBuffer(3, null);
            context.PixelShader.SetSampler(0, null);
            context.PixelShader.SetShaderResource(0, null);
            context.VertexShader.Set(null);
            context.PixelShader.Set(null);
        }



        public void RenderSun(DeviceContext context, Camera camera, Weather weather, ShaderGlobalLights lights)
        {

            context.VertexShader.Set(sunvs);
            context.PixelShader.Set(sunps);

            VSSunMoonVars.Vars.ViewProj = Matrix.Transpose(camera.ViewProjMatrix);
            VSSunMoonVars.Vars.ViewInv = Matrix.Transpose(camera.ViewInvMatrix);
            VSSunMoonVars.Vars.CamRel = new Vector4(lights.CurrentSunDir, 0);
            VSSunMoonVars.Vars.Size = new Vector2(weather.CurrentValues.skySunDiscSize * 0.008f);
            VSSunMoonVars.Vars.Offset = Vector2.Zero;
            VSSunMoonVars.Update(context);
            VSSunMoonVars.SetVSCBuffer(context, 0);

            PSSunMoonVars.Vars.Colour = new Vector4(weather.CurrentValues.skySunDiscCol * weather.CurrentValues.skySunHdr, 1);
            PSSunMoonVars.Update(context);
            PSSunMoonVars.SetPSCBuffer(context, 0);

            context.InputAssembler.InputLayout = sunlayout;
            sundisc.Draw(context);
        }

        public void RenderMoon(DeviceContext context, Camera camera, Weather weather, ShaderGlobalLights lights, RenderableTexture moontex)
        {
            context.VertexShader.Set(moonvs);
            context.PixelShader.Set(moonps);


            Quaternion ori = Quaternion.Invert(Quaternion.LookAtRH(Vector3.Zero, lights.CurrentMoonDir, lights.MoonAxis));
            Matrix omat = ori.ToMatrix();


            VSSunMoonVars.Vars.ViewProj = Matrix.Transpose(camera.ViewProjMatrix);
            VSSunMoonVars.Vars.ViewInv = Matrix.Transpose(omat);// camera.ViewInvMatrix);
            VSSunMoonVars.Vars.CamRel = new Vector4(lights.CurrentMoonDir, 0);
            VSSunMoonVars.Vars.Size = new Vector2(weather.CurrentValues.skyMoonDiscSize * 0.008f);
            VSSunMoonVars.Vars.Offset = Vector2.Zero;
            VSSunMoonVars.Update(context);
            VSSunMoonVars.SetVSCBuffer(context, 0);

            PSSunMoonVars.Vars.Colour = new Vector4(weather.CurrentValues.skyMoonCol * weather.CurrentValues.skyMoonIten, weather.CurrentValues.skyMoonIten);
            PSSunMoonVars.Update(context);
            PSSunMoonVars.SetPSCBuffer(context, 0);

            context.PixelShader.SetSampler(0, texsampler);
            moontex.SetPSResource(context, 0);

            context.InputAssembler.InputLayout = moonlayout;
            moonquad.Draw(context);
        }




        public void Dispose()
        {
            if (disposed) return;
            disposed = true;

            texsampler.Dispose();

            sundisc.Dispose();
            moonquad.Dispose();

            skylayout.Dispose();
            sunlayout.Dispose();
            moonlayout.Dispose();

            VSSunMoonVars.Dispose();
            PSSunMoonVars.Dispose();

            SkyLocalVars.Dispose();
            VSSceneVars.Dispose();
            VSEntityVars.Dispose();
            VSModelVars.Dispose();
            PSSceneVars.Dispose();

            skyps.Dispose();
            skyvs.Dispose();
            sunps.Dispose();
            sunvs.Dispose();
            moonps.Dispose();
            moonvs.Dispose();
        }
    }
}
