// Copyright 2023 The Khronos Group Inc.
// Copyright 2023 Valve Corporation
// Copyright 2023 LunarG, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//

#include <gtest/gtest.h>
#define MAGIC_ENUM_RANGE_MIN 0
#define MAGIC_ENUM_RANGE_MAX 512
#include <magic_enum.hpp>
#include <magic_enum_flags.hpp>
#include <vulkan/utility/vk_format_utils.h>

#include <string_view>

// Given the string_view of a VkFormat, find the location of the letter that corresponds with a component
// EG. find_component("R8G8B8", 'G') would return 2
// returns std::string::npos if it is not found.
size_t find_component(std::string_view str, char letter) {
    size_t loc = str.find(letter, 10);
    while (loc != std::string_view::npos) {
        if (loc < str.length() - 1 && str[loc] == letter && (std::isdigit(str[loc - 1]) || str[loc - 1] == '_') &&
            std::isdigit(str[loc + 1])) {
            break;
        }
        loc = str.find(letter, loc + 1);
    }

    return loc;
}

// Given the string_view of a VkFormat, find the size of the letter that corresponds with a component
// EG. find_component_size("R16G16B16", 3) would return 16
// Returns 0 if the component can't be found
size_t find_component_size(std::string_view str, char letter) {
    size_t loc = find_component(str, letter);
    if (loc == std::string_view::npos) return 0;
    if (loc + 1 >= str.length()) return 0;
    return static_cast<size_t>(std::stoi(std::string(str.substr(loc + 1)), nullptr));
}

size_t find_component_count(std::string_view str) {
    size_t comp_count = 0;
    if (find_component(str, 'R') != std::string_view::npos) comp_count++;
    if (find_component(str, 'G') != std::string_view::npos) comp_count++;
    if (find_component(str, 'B') != std::string_view::npos) comp_count++;
    if (find_component(str, 'A') != std::string_view::npos) comp_count++;
    if (find_component(str, 'D') != std::string_view::npos) comp_count++;
    if (find_component(str, 'S') != std::string_view::npos) comp_count++;
    return comp_count;
}

TEST(format_utils, vkuFormatIsSFLOAT) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // special case depth + stencil formats
        if (format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
            EXPECT_FALSE(vkuFormatIsSFLOAT(format));
            continue;
        }

        if (std::string::npos != format_str.find("_SFLOAT")) {
            EXPECT_TRUE(vkuFormatIsSFLOAT(format));
        } else {
            EXPECT_FALSE(vkuFormatIsSFLOAT(format));
        }
    }
}

TEST(format_utils, vkuFormatIsSINT) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_SINT")) {
            EXPECT_TRUE(vkuFormatIsSINT(format));
        } else {
            EXPECT_FALSE(vkuFormatIsSINT(format));
        }
    }
}
TEST(format_utils, vkuFormatIsSNORM) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_SNORM")) {
            EXPECT_TRUE(vkuFormatIsSNORM(format));
        } else {
            EXPECT_FALSE(vkuFormatIsSNORM(format));
        }
    }
}
TEST(format_utils, vkuFormatIsSRGB) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_SRGB")) {
            EXPECT_TRUE(vkuFormatIsSRGB(format));
        } else {
            EXPECT_FALSE(vkuFormatIsSRGB(format));
        }
    }
}
TEST(format_utils, vkuFormatIsUFLOAT) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_UFLOAT")) {
            EXPECT_TRUE(vkuFormatIsUFLOAT(format));
        } else {
            EXPECT_FALSE(vkuFormatIsUFLOAT(format));
        }
    }
}
TEST(format_utils, vkuFormatIsUINT) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // special case depth + stencil formats
        if (format == VK_FORMAT_D16_UNORM_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT ||
            format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
            EXPECT_FALSE(vkuFormatIsUINT(format));
            continue;
        }
        if (std::string::npos != format_str.find("_UINT")) {
            EXPECT_TRUE(vkuFormatIsUINT(format));
        } else {
            EXPECT_FALSE(vkuFormatIsUINT(format));
        }
    }
}
TEST(format_utils, vkuFormatIsUNORM) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (format == VK_FORMAT_D16_UNORM_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT) {
            EXPECT_FALSE(vkuFormatIsUNORM(format));
            continue;
        }
        if (std::string::npos != format_str.find("_UNORM")) {
            EXPECT_TRUE(vkuFormatIsUNORM(format));
        } else {
            EXPECT_FALSE(vkuFormatIsUNORM(format));
        }
    }
}
TEST(format_utils, vkuFormatIsUSCALED) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_USCALED")) {
            EXPECT_TRUE(vkuFormatIsUSCALED(format));
        } else {
            EXPECT_FALSE(vkuFormatIsUSCALED(format));
        }
    }
}
TEST(format_utils, vkuFormatIsCompressed_ASTC_HDR) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // contains ASTC and SFLOAT in the enum
        if (std::string::npos != format_str.find("_ASTC_") && std::string::npos != format_str.find("_SFLOAT_BLOCK")) {
            EXPECT_TRUE(vkuFormatIsCompressed_ASTC_HDR(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed_ASTC_HDR(format));
        }
    }
}
TEST(format_utils, vkuFormatIsCompressed_ASTC_LDR) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // contains ASTC and does not contain SFLOAT in the enum
        if (std::string::npos != format_str.find("_ASTC_") && std::string::npos == format_str.find("_SFLOAT_BLOCK")) {
            EXPECT_TRUE(vkuFormatIsCompressed_ASTC_LDR(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed_ASTC_LDR(format));
        }
    }
}
TEST(format_utils, vkuFormatIsCompressed_BC) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_BC1") || std::string::npos != format_str.find("_BC2") ||
            std::string::npos != format_str.find("_BC3") || std::string::npos != format_str.find("_BC4") ||
            std::string::npos != format_str.find("_BC5") || std::string::npos != format_str.find("_BC6") ||
            std::string::npos != format_str.find("_BC7")) {
            EXPECT_TRUE(vkuFormatIsCompressed_BC(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed_BC(format));
        }
    }
}
TEST(format_utils, vkuFormatIsCompressed_EAC) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_EAC_")) {
            EXPECT_TRUE(vkuFormatIsCompressed_EAC(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed_EAC(format));
        }
    }
}
TEST(format_utils, vkuFormatIsCompressed_ETC2) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_ETC2_")) {
            EXPECT_TRUE(vkuFormatIsCompressed_ETC2(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed_ETC2(format));
        }
    }
}
TEST(format_utils, vkuFormatIsCompressed_PVRTC) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_PVRTC_")) {
            EXPECT_TRUE(vkuFormatIsCompressed_PVRTC(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed_PVRTC(format));
        }
    }
}

TEST(format_utils, vkuFormatIsCompressed) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // Since the contents of FormatIsCompressed is generated, there is no easy way to check a format based on the string.
        // Instead, this function will have to be
        if (std::string::npos != format_str.find("_BLOCK")) {
            EXPECT_TRUE(vkuFormatIsCompressed(format));
        } else {
            EXPECT_FALSE(vkuFormatIsCompressed(format));
        }
    }
}

TEST(format_utils, vkuFormatIsDepthOrStencil) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_D16") || std::string::npos != format_str.find("_D24") ||
            std::string::npos != format_str.find("_D32") || std::string::npos != format_str.find("_S8")) {
            EXPECT_TRUE(vkuFormatIsDepthOrStencil(format));
        } else {
            EXPECT_FALSE(vkuFormatIsDepthOrStencil(format));
        }
    }
}
TEST(format_utils, vkuFormatIsDepthAndStencil) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if ((std::string::npos != format_str.find("_D16") || std::string::npos != format_str.find("_D24") ||
             std::string::npos != format_str.find("_D32")) &&
            std::string::npos != format_str.find("_S8")) {
            EXPECT_TRUE(vkuFormatIsDepthAndStencil(format));
        } else {
            EXPECT_FALSE(vkuFormatIsDepthAndStencil(format));
        }
    }
}
TEST(format_utils, vkuFormatIsDepthOnly) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // enum contains D16, D24, or D32 but does not contain _S8
        if ((std::string::npos != format_str.find("_D16") || std::string::npos != format_str.find("_D24") ||
             std::string::npos != format_str.find("_D32")) &&
            std::string::npos == format_str.find("_S8")) {
            EXPECT_TRUE(vkuFormatIsDepthOnly(format));
        } else {
            EXPECT_FALSE(vkuFormatIsDepthOnly(format));
        }
    }
}
TEST(format_utils, vkuFormatIsStencilOnly) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        // enum contains _S8 but does not contain D16, D24, or D32
        if (std::string::npos == format_str.find("_D16") && std::string::npos == format_str.find("_D24") &&
            std::string::npos == format_str.find("_D32") && std::string::npos != format_str.find("_S8")) {
            EXPECT_TRUE(vkuFormatIsStencilOnly(format));
        } else {
            EXPECT_FALSE(vkuFormatIsStencilOnly(format));
        }
    }
}
TEST(format_utils, vkuFormatDepthSize) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_D16")) {
            EXPECT_EQ(vkuFormatDepthSize(format), 16);
        } else if (std::string::npos != format_str.find("_D24")) {
            EXPECT_EQ(vkuFormatDepthSize(format), 24);
        } else if (std::string::npos != format_str.find("_D32")) {
            EXPECT_EQ(vkuFormatDepthSize(format), 32);
        } else {
            EXPECT_EQ(vkuFormatDepthSize(format), 0);
        }
    }
}
TEST(format_utils, vkuFormatStencilSize) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_S8")) {
            EXPECT_EQ(vkuFormatStencilSize(format), 8);
        } else {
            EXPECT_EQ(vkuFormatStencilSize(format), 0);
        }
    }
}
TEST(format_utils, vkuFormatDepthNumericalType) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("D16_UNORM") || std::string::npos != format_str.find("D24_UNORM") ||
            std::string::npos != format_str.find("D32_UNORM")) {
            EXPECT_EQ(vkuFormatDepthNumericalType(format), VKU_FORMAT_NUMERICAL_TYPE_UNORM);
        } else if (std::string::npos != format_str.find("D32_SFLOAT")) {
            EXPECT_EQ(vkuFormatDepthNumericalType(format), VKU_FORMAT_NUMERICAL_TYPE_SFLOAT);
        } else {
            EXPECT_EQ(vkuFormatDepthNumericalType(format), VKU_FORMAT_NUMERICAL_TYPE_NONE);
        }
    }
}
TEST(format_utils, vkuFormatStencilNumericalType) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("S8_UINT")) {
            EXPECT_EQ(vkuFormatStencilNumericalType(format), VKU_FORMAT_NUMERICAL_TYPE_UINT);
        } else {
            EXPECT_EQ(vkuFormatStencilNumericalType(format), VKU_FORMAT_NUMERICAL_TYPE_NONE);
        }
    }
}
TEST(format_utils, vkuFormatIsPacked) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_PACK8") || std::string::npos != format_str.find("_PACK16") ||
            std::string::npos != format_str.find("_PACK32")) {
            EXPECT_TRUE(vkuFormatIsPacked(format));
        } else {
            EXPECT_FALSE(vkuFormatIsPacked(format));
        }
    }
}
TEST(format_utils, vkuFormatRequiresYcbcrConversion) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_444_") || std::string::npos != format_str.find("_422_") ||
            std::string::npos != format_str.find("_420_")) {
            EXPECT_TRUE(vkuFormatRequiresYcbcrConversion(format));
        } else {
            EXPECT_FALSE(vkuFormatRequiresYcbcrConversion(format));
        }
    }
}
TEST(format_utils, vkuFormatIsXChromaSubsampled) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos == format_str.find("_444_") &&
            (std::string::npos != format_str.find("_422_") || std::string::npos != format_str.find("_420_"))) {
            EXPECT_TRUE(vkuFormatIsXChromaSubsampled(format));
        } else {
            EXPECT_FALSE(vkuFormatIsXChromaSubsampled(format));
        }
    }
}
TEST(format_utils, vkuFormatIsYChromaSubsampled) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_420_")) {
            EXPECT_TRUE(vkuFormatIsYChromaSubsampled(format));
        } else {
            EXPECT_FALSE(vkuFormatIsYChromaSubsampled(format));
        }
    }
}
TEST(format_utils, vkuFormatIsSinglePlane_422) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("_422_")) {
            EXPECT_TRUE(vkuFormatIsSinglePlane_422(format));
        } else {
            EXPECT_FALSE(vkuFormatIsSinglePlane_422(format));
        }
    }
}
TEST(format_utils, vkuFormatPlaneCount) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("2PLANE")) {
            EXPECT_EQ(vkuFormatPlaneCount(format), 2);
        } else if (std::string::npos != format_str.find("3PLANE")) {
            EXPECT_EQ(vkuFormatPlaneCount(format), 3);
        } else {
            EXPECT_EQ(vkuFormatPlaneCount(format), 1);
        }
    }
}
TEST(format_utils, vkuFormatIsMultiplane) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("2PLANE") || std::string::npos != format_str.find("3PLANE")) {
            EXPECT_TRUE(vkuFormatIsMultiplane(format));
        } else {
            EXPECT_FALSE(vkuFormatIsMultiplane(format));
        }
    }
}
TEST(format_utils, vkuFindMultiplaneCompatibleFormat) {
    EXPECT_EQ(vkuFindMultiplaneCompatibleFormat(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_0_BIT),
              VK_FORMAT_R10X6_UNORM_PACK16);
    EXPECT_EQ(vkuFindMultiplaneCompatibleFormat(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_1_BIT),
              VK_FORMAT_R10X6_UNORM_PACK16);
    EXPECT_EQ(vkuFindMultiplaneCompatibleFormat(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_2_BIT),
              VK_FORMAT_R10X6_UNORM_PACK16);

    EXPECT_EQ(vkuFindMultiplaneCompatibleFormat(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_0_BIT),
              VK_FORMAT_R16_UNORM);
    EXPECT_EQ(vkuFindMultiplaneCompatibleFormat(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_1_BIT),
              VK_FORMAT_R16G16_UNORM);
    EXPECT_EQ(vkuFindMultiplaneCompatibleFormat(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_2_BIT),
              VK_FORMAT_UNDEFINED);
}
TEST(format_utils, vkuFindMultiplaneExtentDivisors) {
    EXPECT_EQ(
        vkuFindMultiplaneExtentDivisors(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_0_BIT).width,
        1);
    EXPECT_EQ(
        vkuFindMultiplaneExtentDivisors(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_1_BIT).width,
        2);
    EXPECT_EQ(
        vkuFindMultiplaneExtentDivisors(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_2_BIT).width,
        2);

    EXPECT_EQ(
        vkuFindMultiplaneExtentDivisors(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_0_BIT).height,
        1);
    EXPECT_EQ(
        vkuFindMultiplaneExtentDivisors(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_1_BIT).height,
        2);
    EXPECT_EQ(
        vkuFindMultiplaneExtentDivisors(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, VK_IMAGE_ASPECT_PLANE_2_BIT).height,
        2);

    EXPECT_EQ(vkuFindMultiplaneExtentDivisors(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_0_BIT).width, 1);
    EXPECT_EQ(vkuFindMultiplaneExtentDivisors(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_1_BIT).width, 2);
    EXPECT_EQ(vkuFindMultiplaneExtentDivisors(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_2_BIT).width, 1);

    EXPECT_EQ(vkuFindMultiplaneExtentDivisors(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_0_BIT).height, 1);
    EXPECT_EQ(vkuFindMultiplaneExtentDivisors(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_1_BIT).height, 1);
    EXPECT_EQ(vkuFindMultiplaneExtentDivisors(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, VK_IMAGE_ASPECT_PLANE_2_BIT).height, 1);
}

TEST(format_utils, vkuFormatComponentCount) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (vkuFormatIsCompressed(format)) {
            // special case compressed formats because they don't typically list their components in the enum itself
            continue;
        }

        EXPECT_EQ(vkuFormatComponentCount(format), find_component_count(format_str));
        if (vkuFormatComponentCount(format) != find_component_count(format_str)) {
            std::cout << "";
        }
    }
}
TEST(format_utils, vkuFormatTexelBlockExtent) {
    constexpr auto formats = magic_enum::enum_values<VkFormat>();
    for (auto format : formats) {
        auto extent = vkuFormatTexelBlockExtent(format);
        if (vkuFormatIsCompressed(format)) {
            EXPECT_GT(extent.width, 1);
            EXPECT_GT(extent.height, 1);
            EXPECT_EQ(extent.depth, 1);
        } else if (format == VK_FORMAT_UNDEFINED) {
            EXPECT_EQ(extent.width, 0);
            EXPECT_EQ(extent.height, 0);
            EXPECT_EQ(extent.depth, 0);
            continue;
        } else {
            EXPECT_EQ(extent.width, 1);
            EXPECT_EQ(extent.height, 1);
            EXPECT_EQ(extent.depth, 1);
        }
    }
    auto extent = vkuFormatTexelBlockExtent(static_cast<VkFormat>(10001));
    EXPECT_EQ(extent.width, 0);
    EXPECT_EQ(extent.height, 0);
    EXPECT_EQ(extent.depth, 0);
}
TEST(format_utils, vkuFormatCompatibilityClass) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (format == VK_FORMAT_UNDEFINED) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_NONE);
            continue;
        }
        if (std::string::npos != format_str.find("D16") && std::string::npos != format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_D16S8);
        } else if (std::string::npos != format_str.find("D16") && std::string::npos == format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_D16);
        } else if (std::string::npos != format_str.find("D24") && std::string::npos != format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_D24S8);
        } else if (std::string::npos != format_str.find("D24") && std::string::npos == format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_D24);
        } else if (std::string::npos != format_str.find("D32") && std::string::npos != format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_D32S8);
        } else if (std::string::npos != format_str.find("D32") && std::string::npos == format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_D32);
        } else if (std::string::npos != format_str.find("S8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_S8);
        } else if (std::string::npos != format_str.find("_PACK8")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_8BIT);
        } else if (std::string::npos != format_str.find("_PACK16")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_16BIT);
        } else if (std::string::npos != format_str.find("_PACK32")) {
            EXPECT_EQ(vkuFormatCompatibilityClass(format), VKU_FORMAT_COMPATIBILITY_CLASS_32BIT);
        } else if (vkuFormatIsCompressed(format)) {
            // special case compressed formats because they don't typically list their components in the enum itself
            continue;
        } else {
            size_t component_size_combined = find_component_size(format_str, 'R') + find_component_size(format_str, 'G') +
                                             find_component_size(format_str, 'B') + find_component_size(format_str, 'A');
            VKU_FORMAT_COMPATIBILITY_CLASS comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_NONE;
            if (component_size_combined == 8) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_8BIT;
            if (component_size_combined == 16) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_16BIT;
            if (component_size_combined == 24) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_24BIT;
            if (component_size_combined == 32) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_32BIT;
            if (component_size_combined == 48) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_48BIT;
            if (component_size_combined == 64) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_64BIT;
            if (component_size_combined == 96) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_96BIT;
            if (component_size_combined == 128) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_128BIT;
            if (component_size_combined == 192) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_192BIT;
            if (component_size_combined == 256) comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_256BIT;

            if (comp_class == VKU_FORMAT_COMPATIBILITY_CLASS_8BIT && find_component(format_str, 'A') != std::string_view::npos)
                comp_class = VKU_FORMAT_COMPATIBILITY_CLASS_8BIT_ALPHA;

            EXPECT_EQ(vkuFormatCompatibilityClass(format), comp_class);
            if (vkuFormatCompatibilityClass(format) != comp_class) {
                std::cout << "";
            }
        }
    }
}
TEST(format_utils, vkuFormatElementIsTexel) {
    constexpr auto formats = magic_enum::enum_values<VkFormat>();
    for (auto format : formats) {
        if (!(vkuFormatIsPacked(format) || vkuFormatIsCompressed(format) || vkuFormatIsSinglePlane_422(format) ||
              vkuFormatIsMultiplane(format))) {
            EXPECT_TRUE(vkuFormatElementIsTexel(format));
        } else {
            EXPECT_FALSE(vkuFormatElementIsTexel(format));
        }
    }
}
TEST(format_utils, vkuFormatElementSizeWithAspect) {
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_R64G64_SFLOAT, VK_IMAGE_ASPECT_NONE), 16);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_R64G64_SFLOAT, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_R64G64_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_NONE), 16);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_PLANE_0_BIT), 16);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_PLANE_1_BIT), 16);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_PLANE_2_BIT), 16);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_NONE), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_PLANE_0_BIT), 2);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_PLANE_1_BIT), 2);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_PLANE_2_BIT), 2);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_NONE), 4);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT), 4);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_D32_SFLOAT_S8_UINT, VK_IMAGE_ASPECT_NONE), 5);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_D32_SFLOAT_S8_UINT, VK_IMAGE_ASPECT_STENCIL_BIT), 1);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_D32_SFLOAT_S8_UINT, VK_IMAGE_ASPECT_DEPTH_BIT), 4);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_S8_UINT, VK_IMAGE_ASPECT_NONE), 1);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_S8_UINT, VK_IMAGE_ASPECT_STENCIL_BIT), 1);
    EXPECT_EQ(vkuFormatElementSizeWithAspect(VK_FORMAT_S8_UINT, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
}
TEST(format_utils, vkuFormatTexelSizeWithAspect) {
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_R64G64_SFLOAT, VK_IMAGE_ASPECT_NONE), 16);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_R64G64_SFLOAT, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_R64G64_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_NONE), 16. / 20.);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_NONE), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_PLANE_0_BIT), 2);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_PLANE_1_BIT), 2);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_PLANE_2_BIT), 2);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_NONE), 4);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_STENCIL_BIT), 0);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_D32_SFLOAT, VK_IMAGE_ASPECT_DEPTH_BIT), 4);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_D32_SFLOAT_S8_UINT, VK_IMAGE_ASPECT_NONE), 5);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_D32_SFLOAT_S8_UINT, VK_IMAGE_ASPECT_STENCIL_BIT), 1);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_D32_SFLOAT_S8_UINT, VK_IMAGE_ASPECT_DEPTH_BIT), 4);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_S8_UINT, VK_IMAGE_ASPECT_NONE), 1);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_S8_UINT, VK_IMAGE_ASPECT_STENCIL_BIT), 1);
    EXPECT_EQ(vkuFormatTexelSizeWithAspect(VK_FORMAT_S8_UINT, VK_IMAGE_ASPECT_DEPTH_BIT), 0);
}
TEST(format_utils, vkuFormatIs64bit) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (std::string::npos != format_str.find("R64")) {
            EXPECT_TRUE(vkuFormatIs64bit(format));
        } else {
            EXPECT_FALSE(vkuFormatIs64bit(format));
        }
    }
}
TEST(format_utils, vkuFormatHasComponentSize) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (vkuFormatIsCompressed(format)) {
            // special case compressed formats because they don't typically list their components in the enum itself
            continue;
        }
        for (uint32_t i = 1; i < 64; i++) {
            bool has_component_size = false;
            has_component_size |= find_component_size(format_str, 'R') == i;
            has_component_size |= find_component_size(format_str, 'G') == i;
            has_component_size |= find_component_size(format_str, 'B') == i;
            has_component_size |= find_component_size(format_str, 'A') == i;
            has_component_size |= find_component_size(format_str, 'D') == i;
            has_component_size |= find_component_size(format_str, 'S') == i;
            EXPECT_EQ(vkuFormatHasComponentSize(format, i), has_component_size);
        }
    }
}

void check_for_letter(char letter, bool (*func)(VkFormat)) {
    for (auto [format, format_str] : magic_enum::enum_entries<VkFormat>()) {
        if (vkuFormatIsCompressed(format)) {
            // special case compressed formats because they don't typically list their components in the enum itself
            continue;
        }
        auto loc = find_component(format_str, letter);
        if (std::string::npos != loc) {
            EXPECT_TRUE(func(format));
        } else {
            EXPECT_FALSE(func(format));
        }
    }
}
TEST(format_utils, vkuFormatHasRed) { check_for_letter('R', vkuFormatHasRed); }
TEST(format_utils, vkuFormatHasGreen) { check_for_letter('G', vkuFormatHasGreen); }
TEST(format_utils, vkuFormatHasBlue) { check_for_letter('B', vkuFormatHasBlue); }
TEST(format_utils, vkuFormatHasAlpha) { check_for_letter('A', vkuFormatHasAlpha); }

TEST(format_utils, vkuFormatIsUndefined) {
    constexpr auto formats = magic_enum::enum_values<VkFormat>();
    for (auto format : formats) {
        if (format == VK_FORMAT_UNDEFINED) {
            EXPECT_TRUE(vkuFormatIsUndefined(format));
        } else {
            EXPECT_FALSE(vkuFormatIsUndefined(format));
        }
    }
}
TEST(format_utils, vkuFormatIsBlockedImage) {
    constexpr auto formats = magic_enum::enum_values<VkFormat>();
    for (auto format : formats) {
        if (vkuFormatIsCompressed(format) || vkuFormatIsSinglePlane_422(format)) {
            EXPECT_TRUE(vkuFormatIsBlockedImage(format));
        } else {
            EXPECT_FALSE(vkuFormatIsBlockedImage(format));
        }
    }
}
TEST(format_utils, vkuFormatIsColor) {
    constexpr auto formats = magic_enum::enum_values<VkFormat>();
    for (auto format : formats) {
        if (!(vkuFormatIsUndefined(format) || vkuFormatIsDepthOrStencil(format) || vkuFormatIsMultiplane(format))) {
            EXPECT_TRUE(vkuFormatIsColor(format));
        } else {
            EXPECT_FALSE(vkuFormatIsColor(format));
        }
    }
}
template <>
struct magic_enum::customize::enum_range<VkImageAspectFlagBits> {
    static constexpr bool is_flags = true;
};

TEST(format_utils, vkuGetPlaneIndex) {
    for (auto [aspect_flag, aspect_flag_str] : magic_enum::enum_entries<VkImageAspectFlagBits>()) {
        if (std::string::npos != aspect_flag_str.find("_ASPECT_PLANE_0")) {
            EXPECT_EQ(vkuGetPlaneIndex(aspect_flag), 0);
        } else if (std::string::npos != aspect_flag_str.find("_ASPECT_PLANE_1")) {
            EXPECT_EQ(vkuGetPlaneIndex(aspect_flag), 1);

        } else if (std::string::npos != aspect_flag_str.find("_ASPECT_PLANE_2")) {
            EXPECT_EQ(vkuGetPlaneIndex(aspect_flag), 2);
        } else {
            EXPECT_EQ(vkuGetPlaneIndex(aspect_flag), VKU_FORMAT_INVALID_INDEX);
        }
    }
}
