using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using System;

namespace Ryujinx.Graphics.Vulkan
{
    struct PipelineDynamicState
    {
        private float _depthBiasSlopeFactor;
        private float _depthBiasConstantFactor;
        private float _depthBiasClamp;

        public int ScissorsCount;
        private Array16<Rect2D> _scissors;

        private uint _backCompareMask;
        private uint _backWriteMask;
        private uint _backReference;
        private uint _frontCompareMask;
        private uint _frontWriteMask;
        private uint _frontReference;

        private Array4<float> _blendConstants;

        private FeedbackLoopAspects _feedbackLoopAspects;

        public uint ViewportsCount;
        public Array16<Viewport> Viewports;

        [Flags]
        private enum DirtyFlags
        {
            None = 0,
            Blend = 1 << 0,
            DepthBias = 1 << 1,
            Scissor = 1 << 2,
            Stencil = 1 << 3,
            Viewport = 1 << 4,
            FeedbackLoop = 1 << 5,
            All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
        }

        private DirtyFlags _dirty;

        public void SetBlendConstants(float r, float g, float b, float a)
        {
            _blendConstants[0] = r;
            _blendConstants[1] = g;
            _blendConstants[2] = b;
            _blendConstants[3] = a;

            _dirty |= DirtyFlags.Blend;
        }

        public void SetDepthBias(float slopeFactor, float constantFactor, float clamp)
        {
            _depthBiasSlopeFactor = slopeFactor;
            _depthBiasConstantFactor = constantFactor;
            _depthBiasClamp = clamp;

            _dirty |= DirtyFlags.DepthBias;
        }

        public void SetScissor(int index, Rect2D scissor)
        {
            _scissors[index] = scissor;

            _dirty |= DirtyFlags.Scissor;
        }

        public void SetStencilMasks(
            uint backCompareMask,
            uint backWriteMask,
            uint backReference,
            uint frontCompareMask,
            uint frontWriteMask,
            uint frontReference)
        {
            _backCompareMask = backCompareMask;
            _backWriteMask = backWriteMask;
            _backReference = backReference;
            _frontCompareMask = frontCompareMask;
            _frontWriteMask = frontWriteMask;
            _frontReference = frontReference;

            _dirty |= DirtyFlags.Stencil;
        }

        public void SetViewport(int index, Viewport viewport)
        {
            Viewports[index] = viewport;

            _dirty |= DirtyFlags.Viewport;
        }

        public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
        {
            Viewports = viewports;
            ViewportsCount = viewportsCount;

            if (ViewportsCount != 0)
            {
                _dirty |= DirtyFlags.Viewport;
            }
        }

        public void SetFeedbackLoop(FeedbackLoopAspects aspects)
        {
            _feedbackLoopAspects = aspects;

            _dirty |= DirtyFlags.FeedbackLoop;
        }

        public void ForceAllDirty()
        {
            _dirty = DirtyFlags.All;
        }

        public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
        {
            Vk api = gd.Api;

            if ((_dirty & DirtyFlags.Blend) == DirtyFlags.Blend)
            {
                RecordBlend(api, commandBuffer);
            }

            if ((_dirty & DirtyFlags.DepthBias) == DirtyFlags.DepthBias)
            {
                RecordDepthBias(api, commandBuffer);
            }

            if ((_dirty & DirtyFlags.Scissor) == DirtyFlags.Scissor)
            {
                RecordScissor(api, commandBuffer);
            }

            if ((_dirty & DirtyFlags.Stencil) == DirtyFlags.Stencil)
            {
                RecordStencilMasks(api, commandBuffer);
            }

            if ((_dirty & DirtyFlags.Viewport) == DirtyFlags.Viewport)
            {
                RecordViewport(api, commandBuffer);
            }

            if ((_dirty & DirtyFlags.FeedbackLoop) == DirtyFlags.FeedbackLoop && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
            {
                RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
            }

            _dirty = DirtyFlags.None;
        }

        private void RecordBlend(Vk api, CommandBuffer commandBuffer)
        {
            api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan());
        }

        private readonly void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
        {
            api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
        }

        private void RecordScissor(Vk api, CommandBuffer commandBuffer)
        {
            if (ScissorsCount != 0)
            {
                api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
            }
        }

        private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
        {
            api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
            api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
            api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceBackBit, _backReference);
            api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontCompareMask);
            api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontWriteMask);
            api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference);
        }

        private void RecordViewport(Vk api, CommandBuffer commandBuffer)
        {
            if (ViewportsCount != 0)
            {
                api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
            }
        }

        private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
        {
            ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;

            if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
            {
                aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
            }

            api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
        }
    }
}
