// MobileGL - MobileGL/MG_Impl/GLImpl/Texture/Validators.h
// 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

#pragma once
#include "MG_State/GLState/TextureState/TextureEnum.h"
#include "MG_Util/Types.h"
#include <Includes.h>
#include <MG_State/GLState/TextureState/TextureObject.h>

namespace MobileGL::MG_Impl::GLImpl::TextureImpl {
    Bool ValidateTextureTarget(TextureTarget target);
    Bool ValidateTextureUploadTarget(TextureUploadTarget textureUploadTarget);
    Bool ValidateTextureName(Uint texture, Bool allowZero = false);
    Bool ValidateTextureInputFormat(TextureInputFormat format);
    Bool ValidateTexturePixelDataType(TexturePixelDataType texturePixelDataType);
    Bool ValidateTextureLevelNumber(Int level);
    Bool ValidateTextureSizeWithTextureUploadTarget(TextureUploadTarget target, GLsizei width, GLsizei height);
    // The two shape rules a cube-map-array level owes (GL 4.6 core 8.5): its faces are square, and
    // its depth counts whole cubes. Both are GL_INVALID_VALUE. This used to be spelled inline in
    // glTexStorage3D only, which is why glTexImage3D let both violations through - every entry
    // point that DEFINES a cube-array level calls this now, so the two cannot drift again. A
    // non-cube-array upload target answers true untouched.
    Bool ValidateCubeMapArrayShape(TextureUploadTarget target, GLsizei width, GLsizei height, GLsizei depth,
                                   const char* caller);
    Bool ValidateTextureSizeRange(Int width, Int height, Int depth);
    Bool ValidateTextureInternalFormat(TextureInternalFormat format);
    Bool ValidateTextureBorderNumber(Int border);
    Bool IsIntegerColorInputFormat(TextureInputFormat format);
    Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat);
    Bool ValidateClientFormatTypePairing(TextureInputFormat format, TexturePixelDataType type);
    Bool ValidateTextureInternalFormatCompatibleWithInput(TextureInputFormat format,
                                                          TextureInternalFormat internalFormat,
                                                          TexturePixelDataType type);
    Bool ValidateTextureLevelWithUploadTarget(TextureUploadTarget target, Int level);
    // "Is <level> a level this texture actually has?", which ValidateTextureLevelNumber above
    // does NOT answer - that one only bounds the index by GL_MAX_TEXTURE_SIZE and knows nothing
    // about the object. Entry points that resolve a level straight into a backend image
    // subresource need this one: a level the texture never had is GL_INVALID_VALUE (GL 4.6 core
    // 18.3.2), and passing it through instead reaches the driver as an out-of-range subresource.
    // Note the error split is per-entry-point, so this is not universally reusable:
    // glClearTexImage owes INVALID_OPERATION for the same out-of-range level and spells its own
    // copy of this predicate in GL_Texture.cpp (GetClearTextureObject).
    Bool ValidateTextureLevelExists(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, Int level,
                                    const char* caller);
    Bool ValidateTextureObject(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject);
    // Rejects the per-target default texture objects (name 0) with GL_INVALID_OPERATION for entry
    // points that require a GenTextures-created texture, e.g. TexStorage* ("An INVALID_OPERATION
    // error is generated if zero is bound to target", ARB_texture_storage).
    Bool ValidateTextureNotDefault(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
                                   const char* caller);
    Bool ValidateTextureTargetUniformity(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
                                         TextureTarget target);
    Bool ValidateTextureSubImageOffsets(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, Int xoffset,
                                        Int width, Int yoffset = 0, Int height = 0, Int zoffset = 0, Int depth = 0);
    // The texel block of one glCopyImageSubData endpoint, resolved to the two things the
    // compatibility rule actually asks about. `compressed` is not redundant with a block bigger
    // than 1x1: it is what distinguishes "compressed, and so the region is measured in texels of
    // a blocked image" from "uncompressed, and so it is measured in texels".
    struct CopyImageTexelBlock {
        SizeT byteSize = 0;
        Uint blockWidth = 1;
        Uint blockHeight = 1;
        Bool compressed = false;
    };
    // `compressedFormat` is the GLenum a glCompressedTexImage* upload recorded for the level, or
    // GL_NONE. It has to be asked for separately because MobileGL stores every compressed format
    // in uncompressed storage (ConvertGLEnumToTextureInternalFormat), so the TextureInternalFormat
    // alone can no longer tell a BPTC image from the RGBA8 backing it.
    CopyImageTexelBlock ResolveCopyImageTexelBlock(TextureInternalFormat format, GLenum compressedFormat);
    // GL 4.6 core 18.3.2: the two images must be COMPATIBLE, and compatible means their texel
    // blocks are the same SIZE - not that they share a base internal format. RGBA32UI into
    // RGBA32F is legal (both 128-bit) while RGBA8 into RGBA32F is not, and a compressed image
    // pairs with an uncompressed one whose texel is as big as the compressed block.
    Bool ValidateCopyImageFormatCompatibility(const CopyImageTexelBlock& srcBlock,
                                              const CopyImageTexelBlock& dstBlock);
    // GL 4.6 core 18.3.2: for a compressed image the region's origin must sit on a block
    // boundary and its size must be a whole number of blocks - unless the edge it runs to is
    // the edge of the image.
    Bool ValidateCopyImageBlockAlignment(const CopyImageTexelBlock& block, Int x, Int y, Int width, Int height,
                                         Int imageWidth, Int imageHeight, const char* endpointName);
    // GL 4.6 SS 8.6 subset rule for glCopyTexImage*: the read buffer must supply every component
    // the requested internalformat asks for, but may supply more.
    Bool ValidateCopyTexImageBaseFormatSubset(TextureInternalFormat destFormat, TextureInternalFormat srcFormat);

    // ---- glTextureView (ARB_texture_view / GL 4.6 core 8.18) ----
    // Table 8.21's view classes. `None` is not a class - it means the format has NO entry in the
    // table, which the spec turns into a much stricter rule than "same class": such a format can
    // only ever be viewed as ITSELF. Every depth, stencil and depth/stencil format lands here,
    // which is why the Better Clouds D24S8 view must name GL_DEPTH24_STENCIL8 exactly.
    enum class TextureViewClass {
        None = 0,
        Bits128,
        Bits96,
        Bits64,
        Bits48,
        Bits32,
        Bits24,
        Bits16,
        Bits8,
        Rgtc1Red,
        Rgtc2Rg,
        BptcUnorm,
        BptcFloat,
    };
    TextureViewClass GetTextureViewClass(GLenum internalformat);
    // Table 8.20: which <target> values glTextureView accepts for a given origtexture target.
    Bool IsLegalTextureViewTargetPair(TextureTarget origTarget, TextureTarget viewTarget);
    // Table 8.20 again, read the other way: how many layers <target> requires. Returns 0 for the
    // targets whose layer count is unconstrained (the array targets), 6 for GL_TEXTURE_CUBE_MAP,
    // and 1 for every single-layer target. GL_TEXTURE_CUBE_MAP_ARRAY is special-cased by the
    // caller because its constraint is "a multiple of 6", not an exact count.
    Uint RequiredTextureViewLayerCount(TextureTarget viewTarget);
} // namespace MobileGL::MG_Impl::GLImpl::TextureImpl
