// MobileGL - MobileGL/MG_Util/Metrics/TextureMetrics.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
//   https://www.gnu.org/licenses/gpl-3.0.txt
//   https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header

#include "TextureMetrics.h"
#include "Defines.h"
#include "MG_Util/Math/VectorTypes.h"

namespace MobileGL {
    namespace MG_Util {
        SizeT GetSizedInternalFormatSizeInBytes(TextureInternalFormat internal) {
            switch (internal) {
            case TextureInternalFormat::R8:
            case TextureInternalFormat::Red: // UNorm8 shadow layout
            case TextureInternalFormat::R8Snorm:
            case TextureInternalFormat::R8I:
            case TextureInternalFormat::StencilIndex8:
            case TextureInternalFormat::R8UI:
                return 1;

            case TextureInternalFormat::R16:
            case TextureInternalFormat::R16Snorm:
            case TextureInternalFormat::R16I:
            case TextureInternalFormat::R16UI:
            case TextureInternalFormat::R16F:
            case TextureInternalFormat::RG8:
            case TextureInternalFormat::RG: // UNorm8x2 shadow layout
            case TextureInternalFormat::RG8Snorm:
            case TextureInternalFormat::RG8I:
            case TextureInternalFormat::RG8UI:
            case TextureInternalFormat::DepthComponent16:
                return 2;

            case TextureInternalFormat::R3G3B2: // UNorm8x3 shadow layout
            case TextureInternalFormat::RGB4:
            case TextureInternalFormat::RGB5:
            case TextureInternalFormat::RGB: // UNorm8x3 shadow layout
            case TextureInternalFormat::RGB8:
            case TextureInternalFormat::RGB8Snorm:
            case TextureInternalFormat::SRGB8:
            case TextureInternalFormat::RGB8I:
            case TextureInternalFormat::RGB8UI:
                return 3;
            // Canonical depth shadow is a full 32-bit unorm word (see PixelStoreProcessor),
            // converted at upload to the image's own 24/32-bit layout.
            case TextureInternalFormat::DepthComponent24:
                return 4;

            case TextureInternalFormat::RGBA2:
            case TextureInternalFormat::RGBA4:
            case TextureInternalFormat::RGB5A1:
            case TextureInternalFormat::RGBA: // UNorm8x4 shadow layout
            case TextureInternalFormat::RGBA8:
            case TextureInternalFormat::RGBA8Snorm:
            case TextureInternalFormat::RGBA8I:
            case TextureInternalFormat::RGBA8UI:
            case TextureInternalFormat::SRGB8Alpha8:
            case TextureInternalFormat::RGB10A2:
            case TextureInternalFormat::RGB10A2UI:
            case TextureInternalFormat::R32F:
            case TextureInternalFormat::R32I:
            case TextureInternalFormat::R32UI:
            case TextureInternalFormat::DepthComponent:
            case TextureInternalFormat::DepthComponent32:
            case TextureInternalFormat::DepthComponent32F:
            case TextureInternalFormat::DepthStencil:
            case TextureInternalFormat::Depth24Stencil8:

            case TextureInternalFormat::RG16:
            case TextureInternalFormat::RG16Snorm:
            case TextureInternalFormat::RG16I:
            case TextureInternalFormat::RG16UI:
            case TextureInternalFormat::RG16F:
                return 4;

            case TextureInternalFormat::RGB16:
            case TextureInternalFormat::RGB10: // UNorm16x3 shadow layout
            case TextureInternalFormat::RGB12: // UNorm16x3 shadow layout
            case TextureInternalFormat::RGB16Snorm:
            case TextureInternalFormat::RGB16F:
            case TextureInternalFormat::RGB16I:
            case TextureInternalFormat::RGB16UI:
                return 6;

            case TextureInternalFormat::RGBA12:
            case TextureInternalFormat::RGBA16:
            case TextureInternalFormat::RGBA16Snorm:
            case TextureInternalFormat::RGBA16I:
            case TextureInternalFormat::RGBA16UI:
            case TextureInternalFormat::RGBA16F:
            case TextureInternalFormat::RG32F:
            case TextureInternalFormat::RG32I:
            case TextureInternalFormat::RG32UI:
            // Shadow bytes hold the GL_FLOAT_32_UNSIGNED_INT_24_8_REV wire format
            // (float depth + a word whose low 8 bits are stencil), 8 bytes/texel.
            case TextureInternalFormat::Depth32FStencil8:
                return 8;

            case TextureInternalFormat::RGB32F:
            case TextureInternalFormat::RGB32I:
            case TextureInternalFormat::RGB32UI:
                return 12;

            case TextureInternalFormat::RGBA32F:
            case TextureInternalFormat::RGBA32I:
            case TextureInternalFormat::RGBA32UI:
                return 16;

            case TextureInternalFormat::R11FG11FB10F:
            case TextureInternalFormat::RGB9E5:
                return 4;

            default:
                return 0;
            }
        }

        SizeT GetBaseInputFormatComponentCount(TextureInputFormat format) {
            switch (format) {
            case TextureInputFormat::Red:
            case TextureInputFormat::RInteger:
            case TextureInputFormat::Green:
            case TextureInputFormat::GreenInteger:
            case TextureInputFormat::Blue:
            case TextureInputFormat::BlueInteger:
            case TextureInputFormat::Alpha:
            case TextureInputFormat::AlphaInteger:
                return 1;
            case TextureInputFormat::RG:
            case TextureInputFormat::RGInteger:
                return 2;
            case TextureInputFormat::RGB:
            case TextureInputFormat::BGR:
            case TextureInputFormat::RGBInteger:
            case TextureInputFormat::BGRInteger:
                return 3;
            case TextureInputFormat::RGBA:
            case TextureInputFormat::BGRA:
            case TextureInputFormat::RGBAInteger:
            case TextureInputFormat::BGRAInteger:
                return 4;
            case TextureInputFormat::StencilIndex:
            case TextureInputFormat::DepthComponent:
            case TextureInputFormat::DepthStencil:
                return 1;
            default:
                MGLOG_D("%s: Unknown input format!", __func__);
                return 0;
            }
        }

        SizeT GetBaseInternalFormatComponentCount(TextureInternalFormat format) {
            switch (format) {
            case TextureInternalFormat::DepthComponent:
            case TextureInternalFormat::DepthComponent16:
            case TextureInternalFormat::DepthComponent24:
            case TextureInternalFormat::DepthComponent32:
            case TextureInternalFormat::DepthComponent32F:
            case TextureInternalFormat::DepthStencil:
            case TextureInternalFormat::Depth24Stencil8:
            case TextureInternalFormat::Depth32FStencil8:
                // Depth stencil is actually 2 components
                // tho real formats always gives byte size in whole
                // so we count this as one here
            case TextureInternalFormat::Red:
            case TextureInternalFormat::R8:
            case TextureInternalFormat::R8Snorm:
            case TextureInternalFormat::R8I:
            case TextureInternalFormat::R8UI:
            case TextureInternalFormat::R16:
            case TextureInternalFormat::R16Snorm:
            case TextureInternalFormat::R16I:
            case TextureInternalFormat::R16UI:
            case TextureInternalFormat::R16F:
            case TextureInternalFormat::R32F:
            case TextureInternalFormat::R32I:
            case TextureInternalFormat::R32UI:
                return 1;
            case TextureInternalFormat::RG:
            case TextureInternalFormat::RG8:
            case TextureInternalFormat::RG8Snorm:
            case TextureInternalFormat::RG8I:
            case TextureInternalFormat::RG8UI:
            case TextureInternalFormat::RG16:
            case TextureInternalFormat::RG16Snorm:
            case TextureInternalFormat::RG16I:
            case TextureInternalFormat::RG16UI:
            case TextureInternalFormat::RG16F:
            case TextureInternalFormat::RG32F:
            case TextureInternalFormat::RG32I:
            case TextureInternalFormat::RG32UI:
                return 2;
            case TextureInternalFormat::RGB:
            case TextureInternalFormat::RGB4:
            case TextureInternalFormat::RGB5:
            case TextureInternalFormat::RGB8:
            case TextureInternalFormat::RGB8Snorm:
            case TextureInternalFormat::RGB10:
            case TextureInternalFormat::RGB12:
            case TextureInternalFormat::RGB16:
            case TextureInternalFormat::RGB16Snorm:
            case TextureInternalFormat::SRGB8:
            case TextureInternalFormat::RGB8I:
            case TextureInternalFormat::RGB8UI:
            case TextureInternalFormat::RGB16F:
            case TextureInternalFormat::RGB32F:
            case TextureInternalFormat::RGB16I:
            case TextureInternalFormat::RGB16UI:
            case TextureInternalFormat::RGB32I:
            case TextureInternalFormat::RGB32UI:
            case TextureInternalFormat::R11FG11FB10F:
            case TextureInternalFormat::RGB9E5:
                return 3;
            case TextureInternalFormat::RGBA:
            case TextureInternalFormat::RGBA2:
            case TextureInternalFormat::RGBA4:
            case TextureInternalFormat::RGBA8:
            case TextureInternalFormat::RGBA8Snorm:
            case TextureInternalFormat::RGBA8I:
            case TextureInternalFormat::RGBA8UI:
            case TextureInternalFormat::SRGB8Alpha8:
            case TextureInternalFormat::RGBA12:
            case TextureInternalFormat::RGBA16:
            case TextureInternalFormat::RGBA16Snorm:
            case TextureInternalFormat::RGBA16I:
            case TextureInternalFormat::RGBA16UI:
            case TextureInternalFormat::RGBA16F:
            case TextureInternalFormat::RGBA32F:
            case TextureInternalFormat::RGBA32I:
            case TextureInternalFormat::RGBA32UI:
            case TextureInternalFormat::RGB10A2:
            case TextureInternalFormat::RGB10A2UI:
                return 4;
            default:
                return 0;
            }
        }

        SizeT GetSizedTexturePixelDataTypeSize(TexturePixelDataType type) {
            switch (type) {
            case TexturePixelDataType::UnsignedByte332:
            case TexturePixelDataType::UnsignedByte233Rev:
                return 1;
            case TexturePixelDataType::UnsignedShort565:
            case TexturePixelDataType::UnsignedShort565Rev:
            case TexturePixelDataType::UnsignedShort4444:
            case TexturePixelDataType::UnsignedShort4444Rev:
            case TexturePixelDataType::UnsignedShort5551:
            case TexturePixelDataType::UnsignedShort1555Rev:
                return 2;
            case TexturePixelDataType::UnsignedInt8888:
            case TexturePixelDataType::UnsignedInt8888Rev:
            case TexturePixelDataType::UnsignedInt1010102:
            case TexturePixelDataType::UnsignedInt2101010Rev:
            case TexturePixelDataType::UnsignedInt101111Rev:
            case TexturePixelDataType::UnsignedInt5999Rev:
            case TexturePixelDataType::UnsignedInt248:
                return 4;
            case TexturePixelDataType::Float32UnsignedInt248Rev:
                // A 32-bit float depth word followed by a 32-bit word holding stencil.
                return 8;
            default:
                return 0;
            }
        }

        SizeT GetBaseTexturePixelDataTypeSize(TexturePixelDataType type) {
            switch (type) {
            case TexturePixelDataType::UnsignedByte:
            case TexturePixelDataType::Byte:
                return 1;
            case TexturePixelDataType::UnsignedShort:
            case TexturePixelDataType::HalfFloat:
            case TexturePixelDataType::Short:
                return 2;
            case TexturePixelDataType::UnsignedInt:
            case TexturePixelDataType::Float:
            case TexturePixelDataType::Int:
                return 4;
            default:
                return 0;
            }
        }

        SizeT GetTexturePixelDataTypeSize(TexturePixelDataType type) {
            SizeT sizedPixelFormatSize = GetSizedTexturePixelDataTypeSize(type);
            if (sizedPixelFormatSize > 0) return sizedPixelFormatSize;
            return GetBaseTexturePixelDataTypeSize(type);
        }

        SizeT GetInternalBytesPerPixel(TextureInternalFormat internalformat, TexturePixelDataType type) {
            SizeT sizedTextureFormatSize = GetSizedInternalFormatSizeInBytes(internalformat);
            if (sizedTextureFormatSize > 0) return sizedTextureFormatSize;
            SizeT sizedPixelFormatSize = GetSizedTexturePixelDataTypeSize(type);
            if (sizedPixelFormatSize > 0) return sizedPixelFormatSize;
            SizeT chCount = GetBaseInternalFormatComponentCount(internalformat);
            SizeT bytesPerChannel = GetBaseTexturePixelDataTypeSize(type);
            return chCount * bytesPerChannel;
        }

        SizeT GetInputBytesPerPixel(TextureInputFormat inputFormat, TexturePixelDataType type) {
            SizeT sizedPixelFormatSize = GetSizedTexturePixelDataTypeSize(type);
            if (sizedPixelFormatSize > 0) return sizedPixelFormatSize;
            SizeT bytesPerChannel = GetBaseTexturePixelDataTypeSize(type);
            SizeT chCount = GetBaseInputFormatComponentCount(inputFormat);
            return chCount * bytesPerChannel;
        }

        SizeT CalculateInputTextureImageSize(TextureInputFormat inputFormat, TexturePixelDataType pixelDataType,
                                             IntVec3 size) {
            return GetInputBytesPerPixel(inputFormat, pixelDataType) * size.x() * size.y() * size.z();
        }

        ComponentSizes GetComponentSizesForInternalFormat(TextureInternalFormat internal) {
            ComponentSizes s = {};

            switch (internal) {
            case TextureInternalFormat::R8:
            case TextureInternalFormat::Red:
            case TextureInternalFormat::R8Snorm:
            case TextureInternalFormat::R8I:
            case TextureInternalFormat::R8UI:
                s.Red = 8;
                break;

            case TextureInternalFormat::R16:
            case TextureInternalFormat::R16Snorm:
            case TextureInternalFormat::R16I:
            case TextureInternalFormat::R16UI:
            case TextureInternalFormat::R16F:
                s.Red = 16;
                break;

            case TextureInternalFormat::RG8:
            case TextureInternalFormat::RG8Snorm:
            case TextureInternalFormat::RG8I:
            case TextureInternalFormat::RG8UI:
            case TextureInternalFormat::RG:
                s.Red = 8;
                s.Green = 8;
                break;

            case TextureInternalFormat::RG16:
            case TextureInternalFormat::RG16Snorm:
            case TextureInternalFormat::RG16I:
            case TextureInternalFormat::RG16UI:
            case TextureInternalFormat::RG16F:
                s.Red = 16;
                s.Green = 16;
                break;

            case TextureInternalFormat::RGB4:
            case TextureInternalFormat::RGB5:
            case TextureInternalFormat::RGB8:
            case TextureInternalFormat::RGB8Snorm:
            case TextureInternalFormat::SRGB8:
            case TextureInternalFormat::RGB8I:
            case TextureInternalFormat::RGB8UI:
            case TextureInternalFormat::RGB:
                s.Red = 8;
                s.Green = 8;
                s.Blue = 8;
                break;
            case TextureInternalFormat::RGB10:
                s.Red = 10;
                s.Green = 10;
                s.Blue = 10;
                break;
            case TextureInternalFormat::RGB12:
                s.Red = 12;
                s.Green = 12;
                s.Blue = 12;
                break;
            case TextureInternalFormat::RGB16:
            case TextureInternalFormat::RGB16Snorm:
                s.Red = 16;
                s.Green = 16;
                s.Blue = 16;
                break;

            case TextureInternalFormat::R3G3B2:
                s.Red = 3;
                s.Green = 3;
                s.Blue = 2;
                break;

            case TextureInternalFormat::RGBA2:
                s.Red = 2;
                s.Green = 2;
                s.Blue = 2;
                s.Alpha = 2;
                break;
            case TextureInternalFormat::RGBA4:
                s.Red = 4;
                s.Green = 4;
                s.Blue = 4;
                s.Alpha = 4;
                break;
            case TextureInternalFormat::RGB5A1:
                s.Red = 5;
                s.Green = 5;
                s.Blue = 5;
                s.Alpha = 1;
                break;
            case TextureInternalFormat::RGBA8:
            case TextureInternalFormat::RGBA8Snorm:
            case TextureInternalFormat::RGBA8I:
            case TextureInternalFormat::RGBA8UI:
            case TextureInternalFormat::SRGB8Alpha8:
            case TextureInternalFormat::RGBA:
                s.Red = 8;
                s.Green = 8;
                s.Blue = 8;
                s.Alpha = 8;
                break;
            case TextureInternalFormat::RGB10A2:
            case TextureInternalFormat::RGB10A2UI:
                s.Red = 10;
                s.Green = 10;
                s.Blue = 10;
                s.Alpha = 2;
                break;

            case TextureInternalFormat::R32F:
            case TextureInternalFormat::R32I:
            case TextureInternalFormat::R32UI:
                s.Red = 32;
                break;
            case TextureInternalFormat::RG32F:
            case TextureInternalFormat::RG32I:
            case TextureInternalFormat::RG32UI:
                s.Red = 32;
                s.Green = 32;
                break;

            case TextureInternalFormat::RGBA12:
                s.Red = 12;
                s.Green = 12;
                s.Blue = 12;
                s.Alpha = 12;
                break;
            case TextureInternalFormat::RGBA16:
            case TextureInternalFormat::RGBA16Snorm:
            case TextureInternalFormat::RGBA16I:
            case TextureInternalFormat::RGBA16UI:
            case TextureInternalFormat::RGBA16F:
                s.Red = 16;
                s.Green = 16;
                s.Blue = 16;
                s.Alpha = 16;
                break;

            case TextureInternalFormat::RGB16F:
            case TextureInternalFormat::RGB16I:
            case TextureInternalFormat::RGB16UI:
                s.Red = 16;
                s.Green = 16;
                s.Blue = 16;
                break;
            case TextureInternalFormat::RGB32F:
            case TextureInternalFormat::RGB32I:
            case TextureInternalFormat::RGB32UI:
                s.Red = 32;
                s.Green = 32;
                s.Blue = 32;
                break;

            case TextureInternalFormat::RGBA32F:
            case TextureInternalFormat::RGBA32I:
            case TextureInternalFormat::RGBA32UI:
                s.Red = 32;
                s.Green = 32;
                s.Blue = 32;
                s.Alpha = 32;
                break;

            case TextureInternalFormat::R11FG11FB10F:
                s.Red = 11;
                s.Green = 11;
                s.Blue = 10;
                break;
            case TextureInternalFormat::RGB9E5:
                s.Red = 9;
                s.Green = 9;
                s.Blue = 9;
                break;

            case TextureInternalFormat::DepthComponent:
            case TextureInternalFormat::DepthComponent16:
                s.Depth = 16;
                break;
            case TextureInternalFormat::DepthComponent24:
                s.Depth = 24;
                break;
            case TextureInternalFormat::DepthComponent32:
            case TextureInternalFormat::DepthComponent32F:
                s.Depth = 32;
                break;
            case TextureInternalFormat::DepthStencil:
            case TextureInternalFormat::Depth24Stencil8:
                s.Depth = 24;
                s.Stencil = 8;
                break;
            case TextureInternalFormat::Depth32FStencil8:
                s.Depth = 32;
                s.Stencil = 8;
                break;
            case TextureInternalFormat::StencilIndex8:
                s.Stencil = 8;
                break;
            case TextureInternalFormat::Unknown:
                // Queried for attachments that have no storage yet (e.g. framebuffer
                // parameter queries on the initial state); every size stays 0.
                break;
            default:
                MGLOG_W_ONCE("Unimplemented internal format in GetComponentSizesForInternalFormat: %d",
                        static_cast<Int>(internal));
                break;
            }

            return s;
        }

        CompressedFormatInfo GetCompressedFormatInfo(GLenum internalFormat) {
            // Every format here is 4x4-blocked; only the bytes per block differ (8 for the one- and
            // two-channel RGTC/EAC and the 1-bit-alpha ETC2 forms, 16 for BPTC and the full-alpha
            // ETC2/EAC and two-channel EAC forms). The generic compressed formats (GL_COMPRESSED_RGBA
            // and friends) are deliberately absent: GL lets the implementation pick, MobileGL picks
            // uncompressed, and glCompressedTexImage* must reject them because there is no defined
            // block layout to hand it. The accepted set is exactly the set
            // ConvertGLEnumToTextureInternalFormat can back with uncompressed storage, so this call
            // can never accept a format whose texel shadow cannot be allocated.
            switch (internalFormat) {
            case GL_COMPRESSED_RED_RGTC1:
            case GL_COMPRESSED_SIGNED_RED_RGTC1:
            case GL_COMPRESSED_RGB8_ETC2:
            case GL_COMPRESSED_SRGB8_ETC2:
            case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            case GL_COMPRESSED_R11_EAC:
            case GL_COMPRESSED_SIGNED_R11_EAC:
                return {4, 4, 8};
            case GL_COMPRESSED_RG_RGTC2:
            case GL_COMPRESSED_SIGNED_RG_RGTC2:
            case GL_COMPRESSED_RGBA_BPTC_UNORM:
            case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
            case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
            case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
            case GL_COMPRESSED_RGBA8_ETC2_EAC:
            case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
            case GL_COMPRESSED_RG11_EAC:
            case GL_COMPRESSED_SIGNED_RG11_EAC:
                return {4, 4, 16};
            default:
                return {};
            }
        }

        SizeT CalculateCompressedTextureImageSize(const CompressedFormatInfo& info, IntVec3 size) {
            if (info.blockWidth == 0 || info.blockHeight == 0) return 0;
            const SizeT width = static_cast<SizeT>(std::max<Int>(size.x(), 0));
            const SizeT height = static_cast<SizeT>(std::max<Int>(size.y(), 0));
            const SizeT depth = static_cast<SizeT>(std::max<Int>(size.z(), 1));
            const SizeT blocksX = (width + info.blockWidth - 1) / info.blockWidth;
            const SizeT blocksY = (height + info.blockHeight - 1) / info.blockHeight;
            return blocksX * blocksY * depth * info.blockByteSize;
        }

    } // namespace MG_Util
} // namespace MobileGL
