// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
namespace Stride.Rendering.Materials
{
    /// <summary>
    /// Performs the shading of a material according to the lights
    /// </summary>
    shader MaterialSurfaceLightingAndShading : IMaterialSurfacePixel, DirectLightGroupArray, EnvironmentLightArray, MaterialPixelShadingStream, Math, Transformation, ShaderBaseStream, NormalUpdate
    {
        compose IMaterialSurfaceShading surfaces[];

        override void Compute()
        {
            // Before performing the shading for all lights, update the NormalVS with the latest normal
            // In case normal mapping is not used, this is a no-op
            UpdateNormalFromTangentSpace(streams.matNormal);
            
            // Flip the normal so it is facing the right direction for back faces
#if STRIDE_GRAPHICS_API_DIRECT3D && STRIDE_GRAPHICS_PROFILE < GRAPHICS_PROFILE_LEVEL_10_0
            //streams.normalWS = streams.normalWS * sign(streams.IsFrontFace);// FIXME: VFACE seems to not work in a proper way
#else
            if(!streams.IsFrontFace)
                streams.normalWS = -streams.normalWS;
#endif

            // Make sure that light stream is reset
            ResetLightStream();

            // Prepare the material for lighting (allows to pre-compute things which are reused during lighting computation)
            PrepareMaterialForLightingAndShading();

            // Prepare shading model
            foreach (var surface in surfaces)
            {
                surface.PrepareForLightingAndShading();
            }

            // ---------------------------------------------------------------------------
            // Compute Direct Lighting contribution
            // ---------------------------------------------------------------------------
            float3 directLightingContribution = 0;
            foreach(var lightGroup in directLightGroups)
            {
                lightGroup.PrepareDirectLights();

                const int maxLightCount = lightGroup.GetMaxLightCount();
                int count = lightGroup.GetLightCount();
                
                // [unroll] Don't unroll and let the driver handle it
                for(int i = 0; i < maxLightCount; i++)
                {
                    if (i >= count)
                    {
                        break;
                    }

                    // Compute the light color and direction
                    lightGroup.PrepareDirectLight(i);

                    // Compute common material shading streams (TODO: This is temporary)
                    PrepareMaterialPerDirectLight();

                    // Iterate on shading models
                    foreach(var surface in surfaces)
                    {
                        directLightingContribution += surface.ComputeDirectLightContribution();
                    }
                }
            }

            // ---------------------------------------------------------------------------
            // Compute Environment Lighting contribution
            // ---------------------------------------------------------------------------
            float3 environmentLightingContribution = 0;
            foreach(var environmentLight in environmentLights)
            {
                // Compute the environment light color (streams.lightColor)
                environmentLight.PrepareEnvironmentLight();

                // Iterate on shading models
                foreach(var surface in surfaces)
                {
                    environmentLightingContribution += surface.ComputeEnvironmentLightContribution();
                }
            }

            // Add Direct (*PI over hemisphere) and Environment Lighting
            streams.shadingColor += directLightingContribution * PI + environmentLightingContribution;
            streams.shadingColorAlpha = streams.matDiffuse.a;

            // Do any computations after lighting and shading, like discarding pixels for example.
            foreach (var surface in surfaces)
            {
                surface.AfterLightingAndShading();
            }
        }
    };
}
