#include <algorithm>
#include <Core/Common/EnumUtils.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <Graphics/D3D12/D3D12CommandList.h>
#include <Graphics/D3D12/D3D12GraphicsDevice.h>
#include <Graphics/D3D12/D3D12Synchronization.h>
#include <Graphics/D3D12/D3D12Texture.h>
#include <Graphics/D3D12/D3D12Utils.h>

namespace Juliet::D3D12
{
    namespace
    {
        DXGI_FORMAT JulietToD3D12_TextureFormat[] = {
            DXGI_FORMAT_UNKNOWN,             // INVALID
            DXGI_FORMAT_A8_UNORM,            // A8_UNORM
            DXGI_FORMAT_R8_UNORM,            // R8_UNORM
            DXGI_FORMAT_R8G8_UNORM,          // R8G8_UNORM
            DXGI_FORMAT_R8G8B8A8_UNORM,      // R8G8B8A8_UNORM
            DXGI_FORMAT_R16_UNORM,           // R16_UNORM
            DXGI_FORMAT_R16G16_UNORM,        // R16G16_UNORM
            DXGI_FORMAT_R16G16B16A16_UNORM,  // R16G16B16A16_UNORM
            DXGI_FORMAT_R10G10B10A2_UNORM,   // R10G10B10A2_UNORM
            DXGI_FORMAT_B5G6R5_UNORM,        // B5G6R5_UNORM
            DXGI_FORMAT_B5G5R5A1_UNORM,      // B5G5R5A1_UNORM
            DXGI_FORMAT_B4G4R4A4_UNORM,      // B4G4R4A4_UNORM
            DXGI_FORMAT_B8G8R8A8_UNORM,      // B8G8R8A8_UNORM
            DXGI_FORMAT_BC1_UNORM,           // BC1_UNORM
            DXGI_FORMAT_BC2_UNORM,           // BC2_UNORM
            DXGI_FORMAT_BC3_UNORM,           // BC3_UNORM
            DXGI_FORMAT_BC4_UNORM,           // BC4_UNORM
            DXGI_FORMAT_BC5_UNORM,           // BC5_UNORM
            DXGI_FORMAT_BC7_UNORM,           // BC7_UNORM
            DXGI_FORMAT_BC6H_SF16,           // BC6H_FLOAT
            DXGI_FORMAT_BC6H_UF16,           // BC6H_UFLOAT
            DXGI_FORMAT_R8_SNORM,            // R8_SNORM
            DXGI_FORMAT_R8G8_SNORM,          // R8G8_SNORM
            DXGI_FORMAT_R8G8B8A8_SNORM,      // R8G8B8A8_SNORM
            DXGI_FORMAT_R16_SNORM,           // R16_SNORM
            DXGI_FORMAT_R16G16_SNORM,        // R16G16_SNORM
            DXGI_FORMAT_R16G16B16A16_SNORM,  // R16G16B16A16_SNORM
            DXGI_FORMAT_R16_FLOAT,           // R16_FLOAT
            DXGI_FORMAT_R16G16_FLOAT,        // R16G16_FLOAT
            DXGI_FORMAT_R16G16B16A16_FLOAT,  // R16G16B16A16_FLOAT
            DXGI_FORMAT_R32_FLOAT,           // R32_FLOAT
            DXGI_FORMAT_R32G32_FLOAT,        // R32G32_FLOAT
            DXGI_FORMAT_R32G32B32A32_FLOAT,  // R32G32B32A32_FLOAT
            DXGI_FORMAT_R11G11B10_FLOAT,     // R11G11B10_UFLOAT
            DXGI_FORMAT_R8_UINT,             // R8_UINT
            DXGI_FORMAT_R8G8_UINT,           // R8G8_UINT
            DXGI_FORMAT_R8G8B8A8_UINT,       // R8G8B8A8_UINT
            DXGI_FORMAT_R16_UINT,            // R16_UINT
            DXGI_FORMAT_R16G16_UINT,         // R16G16_UINT
            DXGI_FORMAT_R16G16B16A16_UINT,   // R16G16B16A16_UINT
            DXGI_FORMAT_R32_UINT,            // R32_UINT
            DXGI_FORMAT_R32G32_UINT,         // R32G32_UINT
            DXGI_FORMAT_R32G32B32A32_UINT,   // R32G32B32A32_UINT
            DXGI_FORMAT_R8_SINT,             // R8_INT
            DXGI_FORMAT_R8G8_SINT,           // R8G8_INT
            DXGI_FORMAT_R8G8B8A8_SINT,       // R8G8B8A8_INT
            DXGI_FORMAT_R16_SINT,            // R16_INT
            DXGI_FORMAT_R16G16_SINT,         // R16G16_INT
            DXGI_FORMAT_R16G16B16A16_SINT,   // R16G16B16A16_INT
            DXGI_FORMAT_R32_SINT,            // R32_INT
            DXGI_FORMAT_R32G32_SINT,         // R32G32_INT
            DXGI_FORMAT_R32G32B32A32_SINT,   // R32G32B32A32_INT
            DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, // R8G8B8A8_UNORM_SRGB
            DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, // B8G8R8A8_UNORM_SRGB
            DXGI_FORMAT_BC1_UNORM_SRGB,      // BC1_UNORM_SRGB
            DXGI_FORMAT_BC2_UNORM_SRGB,      // BC2_UNORM_SRGB
            DXGI_FORMAT_BC3_UNORM_SRGB,      // BC3_UNORM_SRGB
            DXGI_FORMAT_BC7_UNORM_SRGB,      // BC7_UNORM_SRGB
            DXGI_FORMAT_R16_TYPELESS,        // D16_UNORM
            DXGI_FORMAT_R24G8_TYPELESS,      // D24_UNORM
            DXGI_FORMAT_R32_TYPELESS,        // D32_FLOAT
            DXGI_FORMAT_R24G8_TYPELESS,      // D24_UNORM_S8_UINT
            DXGI_FORMAT_R32G8X24_TYPELESS,   // D32_FLOAT_S8_UINT
            DXGI_FORMAT_UNKNOWN,             // ASTC_4x4_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_5x4_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_5x5_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_6x5_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_6x6_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x5_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x6_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x8_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x5_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x6_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x8_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x10_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_12x10_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_12x12_UNORM
            DXGI_FORMAT_UNKNOWN,             // ASTC_4x4_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_5x4_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_5x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_6x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_6x6_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x6_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x8_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x6_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x8_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x10_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_12x10_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_12x12_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,             // ASTC_4x4_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_5x4_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_5x5_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_6x5_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_6x6_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x5_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x6_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_8x8_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x5_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x6_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x8_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_10x10_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_12x10_FLOAT
            DXGI_FORMAT_UNKNOWN,             // ASTC_12x12_FLOAT
        };
        static_assert(sizeof(JulietToD3D12_TextureFormat) / sizeof(JulietToD3D12_TextureFormat[0]) ==
                      ToUnderlying(TextureFormat::Count));

        DXGI_FORMAT JulietToD3D12_DepthFormat[] = {
            DXGI_FORMAT_UNKNOWN,              // INVALID
            DXGI_FORMAT_UNKNOWN,              // A8_UNORM
            DXGI_FORMAT_UNKNOWN,              // R8_UNORM
            DXGI_FORMAT_UNKNOWN,              // R8G8_UNORM
            DXGI_FORMAT_UNKNOWN,              // R8G8B8A8_UNORM
            DXGI_FORMAT_UNKNOWN,              // R16_UNORM
            DXGI_FORMAT_UNKNOWN,              // R16G16_UNORM
            DXGI_FORMAT_UNKNOWN,              // R16G16B16A16_UNORM
            DXGI_FORMAT_UNKNOWN,              // R10G10B10A2_UNORM
            DXGI_FORMAT_UNKNOWN,              // B5G6R5_UNORM
            DXGI_FORMAT_UNKNOWN,              // B5G5R5A1_UNORM
            DXGI_FORMAT_UNKNOWN,              // B4G4R4A4_UNORM
            DXGI_FORMAT_UNKNOWN,              // B8G8R8A8_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC1_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC2_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC3_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC4_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC5_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC7_UNORM
            DXGI_FORMAT_UNKNOWN,              // BC6H_FLOAT
            DXGI_FORMAT_UNKNOWN,              // BC6H_UFLOAT
            DXGI_FORMAT_UNKNOWN,              // R8_SNORM
            DXGI_FORMAT_UNKNOWN,              // R8G8_SNORM
            DXGI_FORMAT_UNKNOWN,              // R8G8B8A8_SNORM
            DXGI_FORMAT_UNKNOWN,              // R16_SNORM
            DXGI_FORMAT_UNKNOWN,              // R16G16_SNORM
            DXGI_FORMAT_UNKNOWN,              // R16G16B16A16_SNORM
            DXGI_FORMAT_UNKNOWN,              // R16_FLOAT
            DXGI_FORMAT_UNKNOWN,              // R16G16_FLOAT
            DXGI_FORMAT_UNKNOWN,              // R16G16B16A16_FLOAT
            DXGI_FORMAT_UNKNOWN,              // R32_FLOAT
            DXGI_FORMAT_UNKNOWN,              // R32G32_FLOAT
            DXGI_FORMAT_UNKNOWN,              // R32G32B32A32_FLOAT
            DXGI_FORMAT_UNKNOWN,              // R11G11B10_UFLOAT
            DXGI_FORMAT_UNKNOWN,              // R8_UINT
            DXGI_FORMAT_UNKNOWN,              // R8G8_UINT
            DXGI_FORMAT_UNKNOWN,              // R8G8B8A8_UINT
            DXGI_FORMAT_UNKNOWN,              // R16_UINT
            DXGI_FORMAT_UNKNOWN,              // R16G16_UINT
            DXGI_FORMAT_UNKNOWN,              // R16G16B16A16_UINT
            DXGI_FORMAT_UNKNOWN,              // R32_UINT
            DXGI_FORMAT_UNKNOWN,              // R32G32_UINT
            DXGI_FORMAT_UNKNOWN,              // R32G32B32A32_UINT
            DXGI_FORMAT_UNKNOWN,              // R8_INT
            DXGI_FORMAT_UNKNOWN,              // R8G8_INT
            DXGI_FORMAT_UNKNOWN,              // R8G8B8A8_INT
            DXGI_FORMAT_UNKNOWN,              // R16_INT
            DXGI_FORMAT_UNKNOWN,              // R16G16_INT
            DXGI_FORMAT_UNKNOWN,              // R16G16B16A16_INT
            DXGI_FORMAT_UNKNOWN,              // R32_INT
            DXGI_FORMAT_UNKNOWN,              // R32G32_INT
            DXGI_FORMAT_UNKNOWN,              // R32G32B32A32_INT
            DXGI_FORMAT_UNKNOWN,              // R8G8B8A8_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // B8G8R8A8_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // BC1_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // BC2_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // BC3_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // BC7_UNORM_SRGB
            DXGI_FORMAT_D16_UNORM,            // D16_UNORM
            DXGI_FORMAT_D24_UNORM_S8_UINT,    // D24_UNORM
            DXGI_FORMAT_D32_FLOAT,            // D32_FLOAT
            DXGI_FORMAT_D24_UNORM_S8_UINT,    // D24_UNORM_S8_UINT
            DXGI_FORMAT_D32_FLOAT_S8X24_UINT, // D32_FLOAT_S8_UINT
            DXGI_FORMAT_UNKNOWN,              // ASTC_4x4_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_5x4_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_5x5_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_6x5_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_6x6_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x5_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x6_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x8_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x5_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x6_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x8_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x10_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_12x10_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_12x12_UNORM
            DXGI_FORMAT_UNKNOWN,              // ASTC_4x4_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_5x4_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_5x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_6x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_6x6_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x6_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x8_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x5_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x6_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x8_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x10_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_12x10_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_12x12_UNORM_SRGB
            DXGI_FORMAT_UNKNOWN,              // ASTC_4x4_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_5x4_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_5x5_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_6x5_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_6x6_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x5_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x6_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_8x8_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x5_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x6_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x8_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_10x10_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_12x10_FLOAT
            DXGI_FORMAT_UNKNOWN,              // ASTC_12x12_FLOAT
        };
        static_assert(sizeof(JulietToD3D12_DepthFormat) / sizeof(JulietToD3D12_DepthFormat[0]) == ToUnderlying(TextureFormat::Count));

        uint32 ComputeSubresourceIndex(uint32 mipLevel, uint32 layer, uint32 numLevels)
        {
            return mipLevel + (layer * numLevels);
        }

        D3D12_RESOURCE_STATES GetDefaultTextureResourceState(TextureUsageFlag usageFlags)
        {
            if ((usageFlags & TextureUsageFlag::Sampler) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
            }
            if ((usageFlags & TextureUsageFlag::GraphicsStorageRead) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
            }
            if ((usageFlags & TextureUsageFlag::ColorTarget) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_RENDER_TARGET;
            }
            if ((usageFlags & TextureUsageFlag::DepthStencilTarget) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_DEPTH_WRITE;
            }
            if ((usageFlags & TextureUsageFlag::ComputeStorageRead) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
            }
            if ((usageFlags & TextureUsageFlag::ComputeStorageWrite) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
            }
            if ((usageFlags & TextureUsageFlag::ComputeStorageSimultaneousReadWrite) != TextureUsageFlag::None)
            {
                return D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
            }
            Log(LogLevel::Error, LogCategory::Graphics, "Texture has no default usage mode!");
            return D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
        }
    } // namespace

    namespace Internal
    {
        D3D12TextureSubresource* PrepareTextureSubresourceForWrite(NonNullPtr<D3D12CommandList>      commandList,
                                                                   NonNullPtr<D3D12TextureContainer> container,
                                                                   uint32 layer, uint32 level, bool shouldCycle,
                                                                   D3D12_RESOURCE_STATES newTextureUsage)
        {
            D3D12TextureSubresource* subresource = Internal::FetchTextureSubresource(container, layer, level);
            if (shouldCycle and container->CanBeCycled and subresource->Parent->ReferenceCount > 0)
            {
                // TODO: Cycle the active texture to an available one. Not needed for swap chain (current objective)
                // CycleActiveTexture(commandList->Driver, container);

                subresource = Internal::FetchTextureSubresource(container, layer, level);
            }

            Internal::TextureSubresourceTransitionFromDefaultUsage(commandList, subresource, newTextureUsage);

            return subresource;
        }

        D3D12TextureSubresource* FetchTextureSubresource(NonNullPtr<D3D12TextureContainer> container, uint32 layer, uint32 level)
        {
            uint32 index = ComputeSubresourceIndex(level, layer, container->Header.CreateInfo.MipLevelCount);
            return &container->ActiveTexture->Subresources[index];
        }

        void TextureSubresourceBarrier(NonNullPtr<D3D12CommandList> commandList, D3D12_RESOURCE_STATES sourceState,
                                       D3D12_RESOURCE_STATES destinationState, NonNullPtr<D3D12TextureSubresource> textureSubresource)
        {
            TextureUsageFlag currentFlag = textureSubresource->Parent->Container->Header.CreateInfo.Flags;
            bool             needsUAVBarrier =
                ((currentFlag & TextureUsageFlag::ComputeStorageWrite) != TextureUsageFlag::None) ||
                ((currentFlag & TextureUsageFlag::ComputeStorageSimultaneousReadWrite) != TextureUsageFlag::None);
            Internal::ResourceBarrier(commandList, sourceState, destinationState, textureSubresource->Parent->Resource,
                                      textureSubresource->Index, needsUAVBarrier);
        }

        void TextureSubresourceTransitionFromDefaultUsage(NonNullPtr<D3D12CommandList>        commandList,
                                                          NonNullPtr<D3D12TextureSubresource> subresource,
                                                          D3D12_RESOURCE_STATES               toTextureUsage)
        {
            D3D12_RESOURCE_STATES defaultUsage =
                GetDefaultTextureResourceState(subresource->Parent->Container->Header.CreateInfo.Flags);
            TextureSubresourceBarrier(commandList, defaultUsage, toTextureUsage, subresource);
        }

        void TextureTransitionFromDefaultUsage(NonNullPtr<D3D12CommandList> commandList,
                                               NonNullPtr<D3D12Texture> texture, D3D12_RESOURCE_STATES toTextureUsage)
        {
            for (uint32 i = 0; i < texture->SubresourceCount; ++i)
            {
                TextureSubresourceTransitionFromDefaultUsage(commandList, &texture->Subresources[i], toTextureUsage);
            }
        }

        void TextureSubresourceTransitionToDefaultUsage(NonNullPtr<D3D12CommandList>        commandList,
                                                        NonNullPtr<D3D12TextureSubresource> subresource,
                                                        D3D12_RESOURCE_STATES               fromTextureUsage)
        {
            D3D12_RESOURCE_STATES defaultUsage =
                GetDefaultTextureResourceState(subresource->Parent->Container->Header.CreateInfo.Flags);
            TextureSubresourceBarrier(commandList, fromTextureUsage, defaultUsage, subresource);
        }

        void TextureTransitionToDefaultUsage(NonNullPtr<D3D12CommandList> commandList, NonNullPtr<D3D12Texture> texture,
                                             D3D12_RESOURCE_STATES fromTextureUsage)
        {
            for (uint32 i = 0; i < texture->SubresourceCount; ++i)
            {
                TextureSubresourceTransitionToDefaultUsage(commandList, &texture->Subresources[i], fromTextureUsage);
            }
        }

        // Utils
        DXGI_FORMAT ConvertToD3D12TextureFormat(TextureFormat format)
        {
            return JulietToD3D12_TextureFormat[ToUnderlying(format)];
        }

        DXGI_FORMAT ConvertToD3D12DepthFormat(TextureFormat format)
        {
            return JulietToD3D12_DepthFormat[ToUnderlying(format)];
        }

        uint32 JulietToD3D12_SampleCount[] = {
            1, // MSAA 1x
            2, // MSAA 2x
            4, // MSAA 4x
            8, // MSAA 8x
        };
    } // namespace Internal

    Texture* CreateTexture(NonNullPtr<GPUDriver> driver, const TextureCreateInfo& createInfo)
    {
        auto* d3d12Driver = static_cast<D3D12Driver*>(driver.Get());

        D3D12_RESOURCE_DESC desc = {};
        switch (createInfo.Type)
        {
        case TextureType::Texture_2D:
        case TextureType::Texture_2DArray:
        case TextureType::Texture_Cube:
        case TextureType::Texture_CubeArray: desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; break;
        case TextureType::Texture_3D:
        case TextureType::Texture_3DArray: desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE3D; break;
        }

        desc.Alignment          = 0;
        desc.Width              = createInfo.Width;
        desc.Height             = createInfo.Height;
        desc.DepthOrArraySize   = static_cast<uint16>(createInfo.LayerCount);
        desc.MipLevels          = static_cast<uint16>(createInfo.MipLevelCount);
        desc.Format             = Internal::ConvertToD3D12TextureFormat(createInfo.Format);
        desc.SampleDesc.Count   = Internal::JulietToD3D12_SampleCount[ToUnderlying(createInfo.SampleCount)];
        desc.SampleDesc.Quality = 0;
        desc.Layout             = D3D12_TEXTURE_LAYOUT_UNKNOWN;
        desc.Flags              = D3D12_RESOURCE_FLAG_NONE;

        if ((createInfo.Flags & TextureUsageFlag::ColorTarget) != TextureUsageFlag::None)
        {
            desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
        }
        if ((createInfo.Flags & TextureUsageFlag::DepthStencilTarget) != TextureUsageFlag::None)
        {
            desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
        }
        if ((createInfo.Flags & TextureUsageFlag::ComputeStorageWrite) != TextureUsageFlag::None)
        {
            desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
        }

        D3D12_HEAP_PROPERTIES heapProps = {};
        heapProps.Type                  = D3D12_HEAP_TYPE_DEFAULT;
        heapProps.CPUPageProperty       = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
        heapProps.MemoryPoolPreference  = D3D12_MEMORY_POOL_UNKNOWN;
        heapProps.CreationNodeMask      = 0; // We don't do multi-adapter operation
        heapProps.VisibleNodeMask       = 0; // We don't do multi-adapter operation

        ID3D12Resource*    resource    = nullptr;
        D3D12_CLEAR_VALUE  clearValue  = {};
        D3D12_CLEAR_VALUE* pClearValue = nullptr;

        if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)
        {
            clearValue.Format               = Internal::ConvertToD3D12DepthFormat(createInfo.Format);
            clearValue.DepthStencil.Depth   = 1.0f;
            clearValue.DepthStencil.Stencil = 0;
            pClearValue                     = &clearValue;
        }
        else if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)
        {
            clearValue.Format   = desc.Format;
            clearValue.Color[0] = 0.0f;
            clearValue.Color[1] = 0.0f;
            clearValue.Color[2] = 0.0f;
            clearValue.Color[3] = 0.0f;
            pClearValue         = &clearValue;
        }

        D3D12_RESOURCE_STATES initialState = GetDefaultTextureResourceState(createInfo.Flags);
        HRESULT hr = d3d12Driver->D3D12Device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &desc,
                                                                       initialState, pClearValue, IID_ID3D12Resource,
                                                                       reinterpret_cast<void**>(&resource));

        if (FAILED(hr))
        {
            LogError(d3d12Driver->D3D12Device, "Failed to create D3D12 committed resource for texture", hr);
            return nullptr;
        }

        auto* textureContainer = static_cast<D3D12TextureContainer*>(Calloc(1, sizeof(D3D12TextureContainer)));
        auto* texture          = static_cast<D3D12Texture*>(Calloc(1, sizeof(D3D12Texture)));

        textureContainer->Header.CreateInfo = createInfo;
        textureContainer->ActiveTexture     = texture;
        textureContainer->Textures          = static_cast<D3D12Texture**>(Malloc(sizeof(D3D12Texture*)));
        textureContainer->Textures[0]       = texture;
        textureContainer->Capacity          = 1;
        textureContainer->Count             = 1;
        textureContainer->CanBeCycled       = true;

        texture->Container      = textureContainer;
        texture->Resource       = resource;
        texture->ReferenceCount = 1;

        uint32 numLayers          = std::max<uint32>(1, createInfo.LayerCount);
        uint32 numMips            = std::max<uint32>(1, createInfo.MipLevelCount);
        texture->SubresourceCount = numLayers * numMips;
        texture->Subresources =
            static_cast<D3D12TextureSubresource*>(Calloc(texture->SubresourceCount, sizeof(D3D12TextureSubresource)));

        for (uint32 layer = 0; layer < numLayers; ++layer)
        {
            for (uint32 mip = 0; mip < numMips; ++mip)
            {
                uint32 index = mip + (layer * numMips);
                auto&  sub   = texture->Subresources[index];
                sub.Parent   = texture;
                sub.Layer    = layer;
                sub.Level    = mip;
                sub.Index    = index;
                sub.Depth    = 1; // 3D texture depth handling would go here

                if ((createInfo.Flags & TextureUsageFlag::ColorTarget) != TextureUsageFlag::None)
                {
                    sub.RTVHandles = static_cast<D3D12StagingDescriptor*>(Calloc(1, sizeof(D3D12StagingDescriptor)));
                    Internal::AssignStagingDescriptor(d3d12Driver, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, sub.RTVHandles[0]);

                    D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
                    rtvDesc.Format                        = desc.Format;
                    rtvDesc.ViewDimension                 = D3D12_RTV_DIMENSION_TEXTURE2D;
                    rtvDesc.Texture2D.MipSlice            = mip;
                    d3d12Driver->D3D12Device->CreateRenderTargetView(resource, &rtvDesc, sub.RTVHandles[0].CpuHandle);
                }

                if ((createInfo.Flags & TextureUsageFlag::DepthStencilTarget) != TextureUsageFlag::None)
                {
                    Internal::AssignStagingDescriptor(d3d12Driver, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, sub.DSVHandle);

                    D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
                    dsvDesc.Format                        = Internal::ConvertToD3D12DepthFormat(createInfo.Format);
                    dsvDesc.ViewDimension                 = D3D12_DSV_DIMENSION_TEXTURE2D;
                    dsvDesc.Texture2D.MipSlice            = mip;
                    d3d12Driver->D3D12Device->CreateDepthStencilView(resource, &dsvDesc, sub.DSVHandle.CpuHandle);
                }
            }
        }

        // Create SRV for sampled/readable textures (bindless access)
        // Assign to the bindless CBV_SRV_UAV heap
        {
            Internal::D3D12Descriptor descriptor;
            if (Internal::AssignDescriptor(d3d12Driver->BindlessDescriptorHeap, descriptor))
            {
                texture->SRVHandle                = D3D12StagingDescriptor{};
                texture->SRVHandle.CpuHandleIndex = descriptor.Index;
                texture->SRVHandle.CpuHandle      = descriptor.CpuHandle;
                texture->SRVHandle.Heap           = descriptor.Heap;

                D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
                srvDesc.Format                          = desc.Format;

                // Fix SRV format for Depth Buffers (TypeLess -> Typed)
                if (createInfo.Format == TextureFormat::D32_FLOAT)
                    srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
                else if (createInfo.Format == TextureFormat::D16_UNORM)
                    srvDesc.Format = DXGI_FORMAT_R16_UNORM;
                else if (createInfo.Format == TextureFormat::D24_UNORM_S8_UINT)
                    srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
                else if (createInfo.Format == TextureFormat::D32_FLOAT_S8_UINT)
                    srvDesc.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;

                srvDesc.Shader4ComponentMapping       = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
                srvDesc.ViewDimension                 = D3D12_SRV_DIMENSION_TEXTURE2D;
                srvDesc.Texture2D.MostDetailedMip     = 0;
                srvDesc.Texture2D.MipLevels           = numMips;
                srvDesc.Texture2D.PlaneSlice          = 0;
                srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;

                d3d12Driver->D3D12Device->CreateShaderResourceView(resource, &srvDesc, descriptor.CpuHandle);
            }
        }

        return reinterpret_cast<Texture*>(textureContainer);
    }

    void DestroyTexture(NonNullPtr<GPUDriver> driver, NonNullPtr<Texture> texture)
    {
        auto* d3d12Driver      = static_cast<D3D12Driver*>(driver.Get());
        auto* textureContainer = reinterpret_cast<D3D12TextureContainer*>(texture.Get());

        for (uint32 i = 0; i < textureContainer->Count; ++i)
        {
            D3D12Texture* d3d12Texture = textureContainer->Textures[i];
            for (uint32 j = 0; j < d3d12Texture->SubresourceCount; ++j)
            {
                D3D12TextureSubresource& sub = d3d12Texture->Subresources[j];
                if (sub.RTVHandles)
                {
                    Internal::ReleaseStagingDescriptor(d3d12Driver, sub.RTVHandles[0]);
                    Free(sub.RTVHandles);
                }
                if (sub.DSVHandle.Heap)
                {
                    Internal::ReleaseStagingDescriptor(d3d12Driver, sub.DSVHandle);
                }
            }
            d3d12Texture->Resource->Release();
            Free(d3d12Texture->Subresources);
            Free(d3d12Texture);
        }

        Free(textureContainer->Textures);
        Free(textureContainer);
    }
} // namespace Juliet::D3D12
