/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Texture level state query tests
 *//*--------------------------------------------------------------------*/

#include "es31fTextureLevelStateQueryTests.hpp"
#include "glsStateQueryUtil.hpp"
#include "tcuTestLog.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuFormatUtil.hpp"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using namespace gls::StateQueryUtil;

static bool textureTypeHasDepth(glw::GLenum textureBindTarget)
{
    switch (textureBindTarget)
    {
    case GL_TEXTURE_2D:
        return false;
    case GL_TEXTURE_3D:
        return true;
    case GL_TEXTURE_2D_ARRAY:
        return true;
    case GL_TEXTURE_CUBE_MAP:
        return false;
    case GL_TEXTURE_2D_MULTISAMPLE:
        return false;
    case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
        return true;
    case GL_TEXTURE_BUFFER:
        return false;
    case GL_TEXTURE_CUBE_MAP_ARRAY:
        return true;
    default:
        DE_ASSERT(false);
        return false;
    }
}

static bool textureTypeHasHeight(glw::GLenum textureBindTarget)
{
    switch (textureBindTarget)
    {
    case GL_TEXTURE_2D:
        return true;
    case GL_TEXTURE_3D:
        return true;
    case GL_TEXTURE_2D_ARRAY:
        return true;
    case GL_TEXTURE_CUBE_MAP:
        return true;
    case GL_TEXTURE_2D_MULTISAMPLE:
        return true;
    case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
        return true;
    case GL_TEXTURE_BUFFER:
        return false;
    case GL_TEXTURE_CUBE_MAP_ARRAY:
        return true;
    default:
        DE_ASSERT(false);
        return false;
    }
}

static const char *getTextureTargetExtension(glw::GLenum target)
{
    switch (target)
    {
    case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
        return "GL_OES_texture_storage_multisample_2d_array";
    case GL_TEXTURE_BUFFER:
        return "GL_EXT_texture_buffer";
    case GL_TEXTURE_CUBE_MAP_ARRAY:
        return "GL_EXT_texture_cube_map_array";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static bool isCoreTextureTarget(glw::GLenum target, const glu::ContextType &contextType)
{
    switch (target)
    {
    case GL_TEXTURE_2D:
    case GL_TEXTURE_3D:
    case GL_TEXTURE_2D_ARRAY:
    case GL_TEXTURE_CUBE_MAP:
    case GL_TEXTURE_2D_MULTISAMPLE:
        return true;

    case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
    case GL_TEXTURE_BUFFER:
    case GL_TEXTURE_CUBE_MAP_ARRAY:
        return glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
               glu::contextSupports(contextType, glu::ApiType::core(4, 5));

    default:
        return false;
    }
}

struct TextureGenerationSpec
{
    struct TextureLevelSpec
    {
        int width;
        int height;
        int depth;
        int level;
        glw::GLenum internalFormat;
        bool compressed;

        TextureLevelSpec(void) : width(0), height(0), depth(0), level(0), internalFormat(GL_RGBA), compressed(false)
        {
        }
    };

    glw::GLenum bindTarget;
    glw::GLenum queryTarget;
    bool immutable;
    bool fixedSamplePos; // !< fixed sample pos argument for multisample textures
    int sampleCount;
    int texBufferDataOffset;
    int texBufferDataSize;
    bool bindWholeArray;
    std::vector<TextureLevelSpec> levels;
    std::string description;

    TextureGenerationSpec(void)
        : bindTarget(0)
        , queryTarget(0)
        , immutable(true)
        , fixedSamplePos(true)
        , sampleCount(0)
        , texBufferDataOffset(0)
        , texBufferDataSize(256)
        , bindWholeArray(false)
    {
    }
};
struct IntegerPrinter
{
    static std::string getIntegerName(int v)
    {
        return de::toString(v);
    }
    static std::string getFloatName(float v)
    {
        return de::toString(v);
    }
};

struct PixelFormatPrinter
{
    static std::string getIntegerName(int v)
    {
        return de::toString(glu::getTextureFormatStr(v));
    }
    static std::string getFloatName(float v)
    {
        return de::toString(glu::getTextureFormatStr((int)v));
    }
};

template <typename Printer>
static bool verifyTextureLevelParameterEqualWithPrinter(glu::CallLogWrapper &gl, glw::GLenum target, int level,
                                                        glw::GLenum pname, int refValue, QueryType type)
{
    QueriedState state;
    tcu::ResultCollector result(gl.getLog(), " // ERROR: ");

    gl.getLog() << tcu::TestLog::Message << "Verifying " << glu::getTextureLevelParameterStr(pname) << ", expecting "
                << Printer::getIntegerName(refValue) << tcu::TestLog::EndMessage;
    queryTextureLevelState(result, gl, type, target, level, pname, state);

    if (state.isUndefined())
        return false;

    verifyInteger(result, state, refValue);

    return result.getResult() == QP_TEST_RESULT_PASS;
}

static bool verifyTextureLevelParameterEqual(glu::CallLogWrapper &gl, glw::GLenum target, int level, glw::GLenum pname,
                                             int refValue, QueryType type)
{
    return verifyTextureLevelParameterEqualWithPrinter<IntegerPrinter>(gl, target, level, pname, refValue, type);
}

static bool verifyTextureLevelParameterInternalFormatEqual(glu::CallLogWrapper &gl, glw::GLenum target, int level,
                                                           glw::GLenum pname, int refValue, QueryType type)
{
    return verifyTextureLevelParameterEqualWithPrinter<PixelFormatPrinter>(gl, target, level, pname, refValue, type);
}

static bool verifyTextureLevelParameterGreaterOrEqual(glu::CallLogWrapper &gl, glw::GLenum target, int level,
                                                      glw::GLenum pname, int refValue, QueryType type)
{
    QueriedState state;
    tcu::ResultCollector result(gl.getLog(), " // ERROR: ");

    gl.getLog() << tcu::TestLog::Message << "Verifying " << glu::getTextureLevelParameterStr(pname) << ", expecting "
                << refValue << " or greater" << tcu::TestLog::EndMessage;
    queryTextureLevelState(result, gl, type, target, level, pname, state);

    if (state.isUndefined())
        return false;

    verifyIntegerMin(result, state, refValue);

    return result.getResult() == QP_TEST_RESULT_PASS;
}

static bool verifyTextureLevelParameterInternalFormatAnyOf(glu::CallLogWrapper &gl, glw::GLenum target, int level,
                                                           glw::GLenum pname, const int *refValues, int numRefValues,
                                                           QueryType type)
{
    QueriedState state;
    tcu::ResultCollector result(gl.getLog(), " // ERROR: ");

    // Log what we try to do
    {
        tcu::MessageBuilder msg(&gl.getLog());

        msg << "Verifying " << glu::getTextureLevelParameterStr(pname) << ", expecting any of {";
        for (int ndx = 0; ndx < numRefValues; ++ndx)
        {
            if (ndx != 0)
                msg << ", ";
            msg << glu::getTextureFormatStr(refValues[ndx]);
        }
        msg << "}";
        msg << tcu::TestLog::EndMessage;
    }

    queryTextureLevelState(result, gl, type, target, level, pname, state);
    if (state.isUndefined())
        return false;

    // verify
    switch (state.getType())
    {
    case DATATYPE_INTEGER:
    {
        for (int ndx = 0; ndx < numRefValues; ++ndx)
            if (state.getIntAccess() == refValues[ndx])
                return true;

        gl.getLog() << tcu::TestLog::Message << "Error: got " << state.getIntAccess() << ", ("
                    << glu::getTextureFormatStr(state.getIntAccess()) << ")" << tcu::TestLog::EndMessage;
        return false;
    }
    case DATATYPE_FLOAT:
    {
        for (int ndx = 0; ndx < numRefValues; ++ndx)
            if (state.getFloatAccess() == (float)refValues[ndx])
                return true;

        gl.getLog() << tcu::TestLog::Message << "Error: got " << state.getFloatAccess() << ", ("
                    << glu::getTextureFormatStr((int)state.getFloatAccess()) << ")" << tcu::TestLog::EndMessage;
        return false;
    }
    default:
        DE_ASSERT(false);
        return false;
    }
}

static bool isDepthFormat(const tcu::TextureFormat &fmt)
{
    return fmt.order == tcu::TextureFormat::D || fmt.order == tcu::TextureFormat::DS;
}

static bool isColorRenderableFormat(glw::GLenum internalFormat)
{
    return internalFormat == GL_RGB565 || internalFormat == GL_RGBA4 || internalFormat == GL_RGB5_A1 ||
           internalFormat == GL_RGB10_A2 || internalFormat == GL_RGB10_A2UI || internalFormat == GL_SRGB8_ALPHA8 ||
           internalFormat == GL_R8 || internalFormat == GL_RG8 || internalFormat == GL_RGB8 ||
           internalFormat == GL_RGBA8 || internalFormat == GL_R8I || internalFormat == GL_RG8I ||
           internalFormat == GL_RGBA8I || internalFormat == GL_R8UI || internalFormat == GL_RG8UI ||
           internalFormat == GL_RGBA8UI || internalFormat == GL_R16I || internalFormat == GL_RG16I ||
           internalFormat == GL_RGBA16I || internalFormat == GL_R16UI || internalFormat == GL_RG16UI ||
           internalFormat == GL_RGBA16UI || internalFormat == GL_R32I || internalFormat == GL_RG32I ||
           internalFormat == GL_RGBA32I || internalFormat == GL_R32UI || internalFormat == GL_RG32UI ||
           internalFormat == GL_RGBA32UI;
}

static bool isRenderableFormat(glw::GLenum internalFormat)
{
    return isColorRenderableFormat(internalFormat) || internalFormat == GL_DEPTH_COMPONENT16 ||
           internalFormat == GL_DEPTH_COMPONENT24 || internalFormat == GL_DEPTH_COMPONENT32F ||
           internalFormat == GL_DEPTH24_STENCIL8 || internalFormat == GL_DEPTH32F_STENCIL8;
}

static bool isTextureBufferFormat(glw::GLenum internalFormat)
{
    return internalFormat == GL_R8 || internalFormat == GL_R16F || internalFormat == GL_R32F ||
           internalFormat == GL_R8I || internalFormat == GL_R16I || internalFormat == GL_R32I ||
           internalFormat == GL_R8UI || internalFormat == GL_R16UI || internalFormat == GL_R32UI ||
           internalFormat == GL_RG8 || internalFormat == GL_RG16F || internalFormat == GL_RG32F ||
           internalFormat == GL_RG8I || internalFormat == GL_RG16I || internalFormat == GL_RG32I ||
           internalFormat == GL_RG8UI || internalFormat == GL_RG16UI || internalFormat == GL_RG32UI ||
           internalFormat == GL_RGB32F || internalFormat == GL_RGB32I || internalFormat == GL_RGB32UI ||
           internalFormat == GL_RGBA8 || internalFormat == GL_RGBA16F || internalFormat == GL_RGBA32F ||
           internalFormat == GL_RGBA8I || internalFormat == GL_RGBA16I || internalFormat == GL_RGBA32I ||
           internalFormat == GL_RGBA8UI || internalFormat == GL_RGBA16UI || internalFormat == GL_RGBA32UI;
}

static bool isLegalFormatForTarget(glw::GLenum target, glw::GLenum format)
{
    const tcu::TextureFormat fmt = glu::mapGLInternalFormat(format);

    if (target == GL_TEXTURE_3D && isDepthFormat(fmt))
        return false;
    if ((target == GL_TEXTURE_2D_MULTISAMPLE || target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) &&
        !isRenderableFormat(format))
        return false;
    if (target == GL_TEXTURE_BUFFER || !isTextureBufferFormat(format))
        return false;
    return true;
}

static bool isCompressionSupportedForTarget(glw::GLenum target)
{
    return target == GL_TEXTURE_2D || target == GL_TEXTURE_2D_ARRAY;
}

static bool isMultisampleTarget(glw::GLenum target)
{
    return target == GL_TEXTURE_2D_MULTISAMPLE || target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
}

static bool targetSupportsMipLevels(glw::GLenum target)
{
    return target != GL_TEXTURE_2D_MULTISAMPLE && target != GL_TEXTURE_2D_MULTISAMPLE_ARRAY &&
           target != GL_TEXTURE_BUFFER;
}

static int getPixelSize(glw::GLenum internalFormat)
{
    const tcu::TextureFormat fmt = glu::mapGLInternalFormat(internalFormat);
    return fmt.getPixelSize();
}

static void generateColorTextureGenerationGroup(std::vector<TextureGenerationSpec> &group, glw::GLenum target,
                                                int maxSamples, glw::GLenum internalFormat)
{
    const glw::GLenum queryTarget = (target == GL_TEXTURE_CUBE_MAP) ? (GL_TEXTURE_CUBE_MAP_NEGATIVE_Y) : (target);

    // initial
    {
        TextureGenerationSpec texGen;
        texGen.bindTarget  = target;
        texGen.queryTarget = queryTarget;
        texGen.immutable   = true;
        texGen.sampleCount = 0;
        texGen.description = glu::getTextureTargetStr(target).toString() + ", initial values";

        group.push_back(texGen);
    }

    // ms targets
    if (isMultisampleTarget(target))
    {
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;

            texGen.bindTarget     = target;
            texGen.queryTarget    = queryTarget;
            texGen.immutable      = true;
            texGen.sampleCount    = 1;
            texGen.fixedSamplePos = false;
            texGen.description    = glu::getTextureTargetStr(target).toString() + ", low sample count";

            level.width          = 16;
            level.height         = 16;
            level.depth          = (textureTypeHasDepth(texGen.bindTarget)) ? (6) : (1);
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;

            texGen.bindTarget     = target;
            texGen.queryTarget    = queryTarget;
            texGen.immutable      = true;
            texGen.sampleCount    = maxSamples;
            texGen.fixedSamplePos = false;
            texGen.description    = glu::getTextureTargetStr(target).toString() + ", high sample count";

            level.width          = 32;
            level.height         = 32;
            level.depth          = (textureTypeHasDepth(texGen.bindTarget)) ? (6) : (1);
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;

            texGen.bindTarget     = target;
            texGen.queryTarget    = queryTarget;
            texGen.immutable      = true;
            texGen.sampleCount    = maxSamples;
            texGen.fixedSamplePos = true;
            texGen.description    = glu::getTextureTargetStr(target).toString() + ", fixed sample positions";

            level.width          = 32;
            level.height         = 32;
            level.depth          = (textureTypeHasDepth(texGen.bindTarget)) ? (6) : (1);
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
    }
    else if (target == GL_TEXTURE_BUFFER)
    {
        // whole buffer
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;
            const int baseSize = getPixelSize(internalFormat);

            texGen.bindTarget          = target;
            texGen.queryTarget         = queryTarget;
            texGen.immutable           = true;
            texGen.description         = glu::getTextureTargetStr(target).toString() + ", whole buffer";
            texGen.texBufferDataOffset = 0;
            texGen.texBufferDataSize   = 32 * baseSize + (baseSize - 1);
            texGen.bindWholeArray      = true;

            level.width          = 32;
            level.height         = 1;
            level.depth          = 1;
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
        // partial buffer
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;
            const int baseSize = getPixelSize(internalFormat);

            texGen.bindTarget          = target;
            texGen.queryTarget         = queryTarget;
            texGen.immutable           = true;
            texGen.description         = glu::getTextureTargetStr(target).toString() + ", partial buffer";
            texGen.texBufferDataOffset = 256;
            texGen.texBufferDataSize   = 16 * baseSize + (baseSize - 1);
            texGen.bindWholeArray      = false;

            level.width          = 16;
            level.height         = 1;
            level.depth          = 1;
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
    }
    else
    {
        // immutable
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;

            texGen.bindTarget  = target;
            texGen.queryTarget = queryTarget;
            texGen.immutable   = true;
            texGen.description = glu::getTextureTargetStr(target).toString() + ", immutable";

            level.width          = 32;
            level.height         = 32;
            level.depth          = (textureTypeHasDepth(texGen.bindTarget)) ? (6) : (1);
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
        // mutable
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;

            texGen.bindTarget  = target;
            texGen.queryTarget = queryTarget;
            texGen.immutable   = false;
            texGen.description = glu::getTextureTargetStr(target).toString() + ", mutable";

            level.width          = 16;
            level.height         = (target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_CUBE_MAP_ARRAY) ? (16) : (64);
            level.depth          = (textureTypeHasDepth(texGen.bindTarget)) ? (6) : (1);
            level.level          = 0;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
        // mip3
        {
            TextureGenerationSpec texGen;
            TextureGenerationSpec::TextureLevelSpec level;

            texGen.bindTarget  = target;
            texGen.queryTarget = queryTarget;
            texGen.immutable   = false;
            texGen.description = glu::getTextureTargetStr(target).toString() + ", mip level 3";

            level.width          = 4;
            level.height         = (target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_CUBE_MAP_ARRAY) ? (4) : (8);
            level.depth          = (textureTypeHasDepth(texGen.bindTarget)) ? (6) : (1);
            level.level          = 3;
            level.internalFormat = internalFormat;
            level.compressed     = false;

            texGen.levels.push_back(level);
            group.push_back(texGen);
        }
    }
}

static void generateInternalFormatTextureGenerationGroup(std::vector<TextureGenerationSpec> &group, glw::GLenum target)
{
    const glw::GLenum queryTarget = (target == GL_TEXTURE_CUBE_MAP) ? (GL_TEXTURE_CUBE_MAP_NEGATIVE_Y) : (target);

    // Internal formats
    static const glw::GLenum internalFormats[] = {GL_R8,
                                                  GL_R8_SNORM,
                                                  GL_RG8,
                                                  GL_RG8_SNORM,
                                                  GL_RGB8,
                                                  GL_RGB8_SNORM,
                                                  GL_RGB565,
                                                  GL_RGBA4,
                                                  GL_RGB5_A1,
                                                  GL_RGBA8,
                                                  GL_RGBA8_SNORM,
                                                  GL_RGB10_A2,
                                                  GL_RGB10_A2UI,
                                                  GL_SRGB8,
                                                  GL_SRGB8_ALPHA8,
                                                  GL_R16F,
                                                  GL_RG16F,
                                                  GL_RGB16F,
                                                  GL_RGBA16F,
                                                  GL_R32F,
                                                  GL_RG32F,
                                                  GL_RGB32F,
                                                  GL_RGBA32F,
                                                  GL_R11F_G11F_B10F,
                                                  GL_RGB9_E5,
                                                  GL_R8I,
                                                  GL_R8UI,
                                                  GL_R16I,
                                                  GL_R16UI,
                                                  GL_R32I,
                                                  GL_R32UI,
                                                  GL_RG8I,
                                                  GL_RG8UI,
                                                  GL_RG16I,
                                                  GL_RG16UI,
                                                  GL_RG32I,
                                                  GL_RG32UI,
                                                  GL_RGB8I,
                                                  GL_RGB8UI,
                                                  GL_RGB16I,
                                                  GL_RGB16UI,
                                                  GL_RGB32I,
                                                  GL_RGB32UI,
                                                  GL_RGBA8I,
                                                  GL_RGBA8UI,
                                                  GL_RGBA16I,
                                                  GL_RGBA16UI,
                                                  GL_RGBA32I,
                                                  GL_RGBA32UI,

                                                  GL_DEPTH_COMPONENT32F,
                                                  GL_DEPTH_COMPONENT24,
                                                  GL_DEPTH_COMPONENT16,
                                                  GL_DEPTH32F_STENCIL8,
                                                  GL_DEPTH24_STENCIL8};

    // initial
    {
        TextureGenerationSpec texGen;
        texGen.bindTarget     = target;
        texGen.queryTarget    = queryTarget;
        texGen.immutable      = true;
        texGen.sampleCount    = 0;
        texGen.fixedSamplePos = true;
        texGen.description    = glu::getTextureTargetStr(target).toString() + ", initial values";

        group.push_back(texGen);
    }

    // test all formats
    for (int internalFormatNdx = 0; internalFormatNdx < DE_LENGTH_OF_ARRAY(internalFormats); ++internalFormatNdx)
    {
        if (!isLegalFormatForTarget(target, internalFormats[internalFormatNdx]))
            continue;

        const int baseSize = getPixelSize(internalFormats[internalFormatNdx]);
        TextureGenerationSpec texGen;
        TextureGenerationSpec::TextureLevelSpec level;

        texGen.bindTarget  = target;
        texGen.queryTarget = queryTarget;
        texGen.immutable   = true;
        texGen.sampleCount = (isMultisampleTarget(target) ? (1) : (0));
        texGen.description = glu::getTextureTargetStr(target).toString() + ", internal format " +
                             glu::getTextureFormatName(internalFormats[internalFormatNdx]);

        if (target == GL_TEXTURE_BUFFER)
        {
            texGen.texBufferDataOffset = 0;
            texGen.texBufferDataSize   = 32 * baseSize + (baseSize - 1);
            texGen.bindWholeArray      = true;
        }

        level.width          = 32;
        level.height         = (textureTypeHasHeight(target)) ? (32) : (1);
        level.depth          = (textureTypeHasDepth(target)) ? (6) : (1);
        level.level          = 0;
        level.internalFormat = internalFormats[internalFormatNdx];
        level.compressed     = false;

        texGen.levels.push_back(level);
        group.push_back(texGen);
    }

    // test mutable rgba8 with mip level 3
    if (targetSupportsMipLevels(target))
    {
        TextureGenerationSpec texGen;
        TextureGenerationSpec::TextureLevelSpec level;

        texGen.bindTarget  = target;
        texGen.queryTarget = queryTarget;
        texGen.immutable   = false;
        texGen.description = glu::getTextureTargetStr(target).toString() + ", internal format GL_RGBA8, mip level 3";

        level.width          = 32;
        level.height         = 32;
        level.depth          = (textureTypeHasDepth(target)) ? (6) : (1);
        level.level          = 3;
        level.internalFormat = GL_RGBA8;
        level.compressed     = false;

        texGen.levels.push_back(level);
        group.push_back(texGen);
    }
}

static void generateCompressedTextureGenerationGroup(std::vector<TextureGenerationSpec> &group, glw::GLenum target)
{
    const glw::GLenum queryTarget = (target == GL_TEXTURE_CUBE_MAP) ? (GL_TEXTURE_CUBE_MAP_NEGATIVE_Y) : (target);

    // initial
    {
        TextureGenerationSpec texGen;
        texGen.bindTarget  = target;
        texGen.queryTarget = queryTarget;
        texGen.immutable   = true;
        texGen.description = glu::getTextureTargetStr(target).toString() + ", initial values";

        group.push_back(texGen);
    }

    // compressed
    if (isCompressionSupportedForTarget(target))
    {
        TextureGenerationSpec texGen;
        TextureGenerationSpec::TextureLevelSpec level;

        texGen.bindTarget  = target;
        texGen.queryTarget = queryTarget;
        texGen.immutable   = false;
        texGen.description = glu::getTextureTargetStr(target).toString() + ", compressed";

        level.width          = 32;
        level.height         = 32;
        level.depth          = (target == GL_TEXTURE_2D_ARRAY) ? (2) : (1);
        level.level          = 0;
        level.internalFormat = GL_COMPRESSED_RGB8_ETC2;
        level.compressed     = true;

        texGen.levels.push_back(level);
        group.push_back(texGen);
    }
}

static void generateTextureBufferGenerationGroup(std::vector<TextureGenerationSpec> &group, glw::GLenum target)
{
    const glw::GLenum queryTarget = (target == GL_TEXTURE_CUBE_MAP) ? (GL_TEXTURE_CUBE_MAP_NEGATIVE_Y) : (target);

    // initial
    {
        TextureGenerationSpec texGen;
        texGen.bindTarget  = target;
        texGen.queryTarget = queryTarget;
        texGen.immutable   = true;
        texGen.sampleCount = 0;
        texGen.description = glu::getTextureTargetStr(target).toString() + ", initial values";

        group.push_back(texGen);
    }

    // actual specification tests are in texture_buffer tests, no need to do them here too
}

bool applyTextureGenerationSpec(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec, glw::GLuint &texBuffer)
{
    bool allOk = true;

    DE_ASSERT(!(spec.immutable && spec.levels.size() > 1)); // !< immutable textures have only one level

    for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
    {
        const glu::TransferFormat transferFormat =
            (spec.levels[levelNdx].compressed) ?
                (glu::TransferFormat()) :
                (glu::getTransferFormat(glu::mapGLInternalFormat(spec.levels[levelNdx].internalFormat)));

        if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D)
            gl.glTexStorage2D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                              spec.levels[levelNdx].height);
        else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_3D)
            gl.glTexStorage3D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                              spec.levels[levelNdx].height, spec.levels[levelNdx].depth);
        else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_ARRAY)
            gl.glTexStorage3D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                              spec.levels[levelNdx].height, spec.levels[levelNdx].depth);
        else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_CUBE_MAP)
            gl.glTexStorage2D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                              spec.levels[levelNdx].height);
        else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_MULTISAMPLE)
            gl.glTexStorage2DMultisample(spec.bindTarget, spec.sampleCount, spec.levels[levelNdx].internalFormat,
                                         spec.levels[levelNdx].width, spec.levels[levelNdx].height,
                                         (spec.fixedSamplePos) ? (GL_TRUE) : (GL_FALSE));
        else if (spec.immutable && !spec.levels[levelNdx].compressed &&
                 spec.bindTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
            gl.glTexStorage3DMultisample(spec.bindTarget, spec.sampleCount, spec.levels[levelNdx].internalFormat,
                                         spec.levels[levelNdx].width, spec.levels[levelNdx].height,
                                         spec.levels[levelNdx].depth, (spec.fixedSamplePos) ? (GL_TRUE) : (GL_FALSE));
        else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_CUBE_MAP_ARRAY)
            gl.glTexStorage3D(spec.bindTarget, 1, spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                              spec.levels[levelNdx].height, spec.levels[levelNdx].depth);
        else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D)
            gl.glTexImage2D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat,
                            spec.levels[levelNdx].width, spec.levels[levelNdx].height, 0, transferFormat.format,
                            transferFormat.dataType, DE_NULL);
        else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_3D)
            gl.glTexImage3D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat,
                            spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth, 0,
                            transferFormat.format, transferFormat.dataType, DE_NULL);
        else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_ARRAY)
            gl.glTexImage3D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat,
                            spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth, 0,
                            transferFormat.format, transferFormat.dataType, DE_NULL);
        else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_CUBE_MAP)
            gl.glTexImage2D(spec.queryTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat,
                            spec.levels[levelNdx].width, spec.levels[levelNdx].height, 0, transferFormat.format,
                            transferFormat.dataType, DE_NULL);
        else if (!spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_CUBE_MAP_ARRAY)
            gl.glTexImage3D(spec.bindTarget, spec.levels[levelNdx].level, spec.levels[levelNdx].internalFormat,
                            spec.levels[levelNdx].width, spec.levels[levelNdx].height, spec.levels[levelNdx].depth, 0,
                            transferFormat.format, transferFormat.dataType, DE_NULL);
        else if (!spec.immutable && spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D)
        {
            DE_ASSERT(spec.levels[levelNdx].width == 32);
            DE_ASSERT(spec.levels[levelNdx].height == 32);
            DE_ASSERT(spec.levels[levelNdx].internalFormat == GL_COMPRESSED_RGB8_ETC2);

            static const uint8_t buffer[64 * 8] = {0};
            gl.glCompressedTexImage2D(spec.bindTarget, spec.levels[levelNdx].level,
                                      spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                                      spec.levels[levelNdx].height, 0, sizeof(buffer), buffer);
        }
        else if (!spec.immutable && spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_2D_ARRAY)
        {
            DE_ASSERT(spec.levels[levelNdx].width == 32);
            DE_ASSERT(spec.levels[levelNdx].height == 32);
            DE_ASSERT(spec.levels[levelNdx].depth == 2);
            DE_ASSERT(spec.levels[levelNdx].internalFormat == GL_COMPRESSED_RGB8_ETC2);

            static const uint8_t buffer[64 * 8 * 2] = {0};
            gl.glCompressedTexImage3D(spec.bindTarget, spec.levels[levelNdx].level,
                                      spec.levels[levelNdx].internalFormat, spec.levels[levelNdx].width,
                                      spec.levels[levelNdx].height, spec.levels[levelNdx].depth, 0, sizeof(buffer),
                                      buffer);
        }
        else if (spec.immutable && !spec.levels[levelNdx].compressed && spec.bindTarget == GL_TEXTURE_BUFFER)
        {
            gl.glGenBuffers(1, &texBuffer);
            gl.glBindBuffer(GL_TEXTURE_BUFFER, texBuffer);

            if (spec.bindWholeArray)
            {
                gl.glBufferData(GL_TEXTURE_BUFFER, spec.texBufferDataSize, DE_NULL, GL_STATIC_DRAW);
                gl.glTexBuffer(GL_TEXTURE_BUFFER, spec.levels[levelNdx].internalFormat, texBuffer);
            }
            else
            {
                gl.glBufferData(GL_TEXTURE_BUFFER, spec.texBufferDataOffset + spec.texBufferDataSize, DE_NULL,
                                GL_STATIC_DRAW);
                gl.glTexBufferRange(GL_TEXTURE_BUFFER, spec.levels[levelNdx].internalFormat, texBuffer,
                                    spec.texBufferDataOffset, spec.texBufferDataSize);
            }
        }
        else
            DE_ASSERT(false);

        {
            const glw::GLenum err = gl.glGetError();
            if (err != GL_NO_ERROR)
            {
                gl.getLog() << tcu::TestLog::Message
                            << "Texture specification failed, got " + glu::getErrorStr(err).toString()
                            << tcu::TestLog::EndMessage;
                allOk = false;
            }
        }
    }

    return allOk;
}

class TextureLevelCase : public TestCase
{
public:
    TextureLevelCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type);
    ~TextureLevelCase(void);

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

protected:
    void getFormatSamples(glw::GLenum internalFormat, std::vector<int> &samples);
    bool testConfig(const TextureGenerationSpec &spec);
    virtual bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec) = 0;
    virtual void generateTestIterations(std::vector<TextureGenerationSpec> &iterations)        = 0;

    const QueryType m_type;
    const glw::GLenum m_target;
    glw::GLuint m_texture;
    glw::GLuint m_texBuffer;

private:
    int m_iteration;
    std::vector<TextureGenerationSpec> m_iterations;
    bool m_allIterationsOk;
    std::vector<int> m_failedIterations;
};

TextureLevelCase::TextureLevelCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
    : TestCase(ctx, name, desc)
    , m_type(type)
    , m_target(target)
    , m_texture(0)
    , m_texBuffer(0)
    , m_iteration(0)
    , m_allIterationsOk(true)
{
}

TextureLevelCase::~TextureLevelCase(void)
{
    deinit();
}

void TextureLevelCase::init(void)
{
    if (!isCoreTextureTarget(m_target, m_context.getRenderContext().getType()))
    {
        const char *const targetExtension = getTextureTargetExtension(m_target);

        if (!m_context.getContextInfo().isExtensionSupported(targetExtension))
            throw tcu::NotSupportedError("Test requires " + std::string(targetExtension) + " extension");
    }

    generateTestIterations(m_iterations);

    for (int iterationNdx = 0; iterationNdx < (int)m_iterations.size(); ++iterationNdx)
        DE_ASSERT(m_iterations[iterationNdx].bindTarget == m_target);
}

void TextureLevelCase::deinit(void)
{
    if (m_texture)
    {
        m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texture);
        m_texture = 0;
    }
    if (m_texBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texBuffer);
        m_texBuffer = 0;
    }
}

void TextureLevelCase::getFormatSamples(glw::GLenum internalFormat, std::vector<int> &samples)
{
    const glw::Functions gl = m_context.getRenderContext().getFunctions();
    int sampleCount         = -1;

    if (!isMultisampleTarget(m_target))
        return;

    gl.getInternalformativ(m_target, internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCount);

    if (sampleCount < 0)
        throw tcu::TestError("internal format query failed");

    samples.resize(sampleCount);

    if (sampleCount > 0)
    {
        gl.getInternalformativ(m_target, internalFormat, GL_SAMPLES, sampleCount, &samples[0]);
        GLU_EXPECT_NO_ERROR(gl.getError(), "get max samples");
    }
}

TextureLevelCase::IterateResult TextureLevelCase::iterate(void)
{
    const bool result = testConfig(m_iterations[m_iteration]);

    if (!result)
    {
        m_failedIterations.push_back(m_iteration);
        m_allIterationsOk = false;
    }

    if (++m_iteration < (int)m_iterations.size())
        return CONTINUE;

    if (m_allIterationsOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
    {
        tcu::MessageBuilder msg(&m_testCtx.getLog());

        msg << "Following iteration(s) failed: ";
        for (int ndx = 0; ndx < (int)m_failedIterations.size(); ++ndx)
        {
            if (ndx)
                msg << ", ";
            msg << (m_failedIterations[ndx] + 1);
        }
        msg << tcu::TestLog::EndMessage;

        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more iterations failed");
    }
    return STOP;
}

bool TextureLevelCase::testConfig(const TextureGenerationSpec &spec)
{
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration",
                                        std::string() + "Iteration " + de::toString(m_iteration + 1) + "/" +
                                            de::toString((int)m_iterations.size()) + " - " + spec.description);
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    bool result;

    gl.enableLogging(true);

    gl.glGenTextures(1, &m_texture);
    gl.glBindTexture(spec.bindTarget, m_texture);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

    // Set the state
    applyTextureGenerationSpec(gl, spec, m_texBuffer);

    // Verify the state
    result = checkTextureState(gl, spec);

    gl.glDeleteTextures(1, &m_texture);
    m_texture = 0;

    if (m_texBuffer)
    {
        gl.glDeleteBuffers(1, &m_texBuffer);
        m_texture = 0;
    }

    return result;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test texture target
 *//*--------------------------------------------------------------------*/
class TextureLevelCommonCase : public TextureLevelCase
{
public:
    TextureLevelCommonCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type);

protected:
    virtual void generateTestIterations(std::vector<TextureGenerationSpec> &iterations);
};

TextureLevelCommonCase::TextureLevelCommonCase(Context &ctx, const char *name, const char *desc, glw::GLenum target,
                                               QueryType type)
    : TextureLevelCase(ctx, name, desc, target, type)
{
}

void TextureLevelCommonCase::generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
{
    const glw::GLenum internalFormat = GL_RGBA8;
    int maxSamples;
    std::vector<int> samples;

    getFormatSamples(internalFormat, samples);
    if (samples.empty())
        maxSamples = -1;
    else
        maxSamples = samples[0];

    generateColorTextureGenerationGroup(iterations, m_target, maxSamples, internalFormat);
}

class TextureLevelSampleCase : public TextureLevelCommonCase
{
public:
    TextureLevelSampleCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCommonCase(ctx, name, desc, target, type)
    {
    }

private:
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        const int queryLevel = (spec.levels.empty()) ? (0) : (spec.levels[0].level);
        const int refValue   = (spec.levels.empty()) ? (0) : (spec.sampleCount);

        return verifyTextureLevelParameterGreaterOrEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_SAMPLES, refValue,
                                                         m_type);
    }
};

class TextureLevelFixedSamplesCase : public TextureLevelCommonCase
{
public:
    TextureLevelFixedSamplesCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCommonCase(ctx, name, desc, target, type)
    {
    }

private:
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        const int queryLevel = (spec.levels.empty()) ? (0) : (spec.levels[0].level);
        const int refValue   = (spec.levels.empty()) ? (1) : ((spec.fixedSamplePos) ? (1) : (0));

        return verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_FIXED_SAMPLE_LOCATIONS,
                                                refValue, m_type);
    }
};

class TextureLevelWidthCase : public TextureLevelCommonCase
{
public:
    TextureLevelWidthCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCommonCase(ctx, name, desc, target, type)
    {
    }

private:
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        const int initialValue = 0;
        bool allOk             = true;

        if (spec.levels.empty())
        {
            const int queryLevel = 0;
            const int refValue   = initialValue;

            allOk &=
                verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_WIDTH, refValue, m_type);
        }
        else
        {
            for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
            {
                const int queryLevel = spec.levels[levelNdx].level;
                const int refValue   = spec.levels[levelNdx].width;

                allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_WIDTH, refValue,
                                                          m_type);
            }
        }

        return allOk;
    }
};

class TextureLevelHeightCase : public TextureLevelCommonCase
{
public:
    TextureLevelHeightCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCommonCase(ctx, name, desc, target, type)
    {
    }

private:
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        const int initialValue = 0;
        bool allOk             = true;

        if (spec.levels.empty())
        {
            const int queryLevel = 0;
            const int refValue   = initialValue;

            allOk &=
                verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_HEIGHT, refValue, m_type);
        }
        else
        {
            for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
            {
                const int queryLevel = spec.levels[levelNdx].level;
                const int refValue   = spec.levels[levelNdx].height;

                allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_HEIGHT, refValue,
                                                          m_type);
            }
        }

        return allOk;
    }
};

class TextureLevelDepthCase : public TextureLevelCommonCase
{
public:
    TextureLevelDepthCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCommonCase(ctx, name, desc, target, type)
    {
    }

private:
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        const int initialValue = 0;
        bool allOk             = true;

        if (spec.levels.empty())
        {
            const int queryLevel = 0;
            const int refValue   = initialValue;

            allOk &=
                verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_DEPTH, refValue, m_type);
        }
        else
        {
            for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
            {
                const int queryLevel = spec.levels[levelNdx].level;
                const int refValue   = spec.levels[levelNdx].depth;

                allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_DEPTH, refValue,
                                                          m_type);
            }
        }

        return allOk;
    }
};

class TextureLevelInternalFormatCase : public TextureLevelCase
{
public:
    TextureLevelInternalFormatCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCase(ctx, name, desc, target, type)
    {
    }

private:
    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
    {
        generateInternalFormatTextureGenerationGroup(iterations, m_target);
    }

    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        bool allOk = true;

        if (spec.levels.empty())
        {
            const int queryLevel       = 0;
            const int initialValues[2] = {GL_RGBA, GL_R8};

            allOk &= verifyTextureLevelParameterInternalFormatAnyOf(gl, spec.queryTarget, queryLevel,
                                                                    GL_TEXTURE_INTERNAL_FORMAT, initialValues,
                                                                    DE_LENGTH_OF_ARRAY(initialValues), m_type);
        }
        else
        {
            for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
            {
                const int queryLevel = spec.levels[levelNdx].level;
                const int refValue   = spec.levels[levelNdx].internalFormat;

                allOk &= verifyTextureLevelParameterInternalFormatEqual(gl, spec.queryTarget, queryLevel,
                                                                        GL_TEXTURE_INTERNAL_FORMAT, refValue, m_type);
            }
        }

        return allOk;
    }
};

class TextureLevelSizeCase : public TextureLevelCase
{
public:
    TextureLevelSizeCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type,
                         glw::GLenum pname);

private:
    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations);
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec);
    int getMinimumComponentResolution(glw::GLenum internalFormat);

    const glw::GLenum m_pname;
};

TextureLevelSizeCase::TextureLevelSizeCase(Context &ctx, const char *name, const char *desc, glw::GLenum target,
                                           QueryType type, glw::GLenum pname)
    : TextureLevelCase(ctx, name, desc, target, type)
    , m_pname(pname)
{
}

void TextureLevelSizeCase::generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
{
    generateInternalFormatTextureGenerationGroup(iterations, m_target);
}

bool TextureLevelSizeCase::checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
{
    bool allOk = true;

    if (spec.levels.empty())
    {
        allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, m_pname, 0, m_type);
    }
    else
    {
        for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
        {
            const int queryLevel = spec.levels[levelNdx].level;
            const int refValue   = getMinimumComponentResolution(spec.levels[levelNdx].internalFormat);

            allOk &=
                verifyTextureLevelParameterGreaterOrEqual(gl, spec.queryTarget, queryLevel, m_pname, refValue, m_type);
        }
    }

    return allOk;
}

int TextureLevelSizeCase::getMinimumComponentResolution(glw::GLenum internalFormat)
{
    const tcu::TextureFormat format  = glu::mapGLInternalFormat(internalFormat);
    const tcu::IVec4 channelBitDepth = tcu::getTextureFormatBitDepth(format);

    switch (m_pname)
    {
    case GL_TEXTURE_RED_SIZE:
        if (format.order == tcu::TextureFormat::R || format.order == tcu::TextureFormat::RG ||
            format.order == tcu::TextureFormat::RGB || format.order == tcu::TextureFormat::RGBA ||
            format.order == tcu::TextureFormat::BGRA || format.order == tcu::TextureFormat::ARGB ||
            format.order == tcu::TextureFormat::sRGB || format.order == tcu::TextureFormat::sRGBA)
            return channelBitDepth[0];
        else
            return 0;

    case GL_TEXTURE_GREEN_SIZE:
        if (format.order == tcu::TextureFormat::RG || format.order == tcu::TextureFormat::RGB ||
            format.order == tcu::TextureFormat::RGBA || format.order == tcu::TextureFormat::BGRA ||
            format.order == tcu::TextureFormat::ARGB || format.order == tcu::TextureFormat::sRGB ||
            format.order == tcu::TextureFormat::sRGBA)
            return channelBitDepth[1];
        else
            return 0;

    case GL_TEXTURE_BLUE_SIZE:
        if (format.order == tcu::TextureFormat::RGB || format.order == tcu::TextureFormat::RGBA ||
            format.order == tcu::TextureFormat::BGRA || format.order == tcu::TextureFormat::ARGB ||
            format.order == tcu::TextureFormat::sRGB || format.order == tcu::TextureFormat::sRGBA)
            return channelBitDepth[2];
        else
            return 0;

    case GL_TEXTURE_ALPHA_SIZE:
        if (format.order == tcu::TextureFormat::RGBA || format.order == tcu::TextureFormat::BGRA ||
            format.order == tcu::TextureFormat::ARGB || format.order == tcu::TextureFormat::sRGBA)
            return channelBitDepth[3];
        else
            return 0;

    case GL_TEXTURE_DEPTH_SIZE:
        if (format.order == tcu::TextureFormat::D || format.order == tcu::TextureFormat::DS)
            return channelBitDepth[0];
        else
            return 0;

    case GL_TEXTURE_STENCIL_SIZE:
        if (format.order == tcu::TextureFormat::DS)
            return channelBitDepth[3];
        else
            return 0;

    case GL_TEXTURE_SHARED_SIZE:
        if (internalFormat == GL_RGB9_E5)
            return 5;
        else
            return 0;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

class TextureLevelTypeCase : public TextureLevelCase
{
public:
    TextureLevelTypeCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type,
                         glw::GLenum pname);

private:
    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations);
    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec);
    int getComponentType(glw::GLenum internalFormat);

    const glw::GLenum m_pname;
};

TextureLevelTypeCase::TextureLevelTypeCase(Context &ctx, const char *name, const char *desc, glw::GLenum target,
                                           QueryType type, glw::GLenum pname)
    : TextureLevelCase(ctx, name, desc, target, type)
    , m_pname(pname)
{
}

void TextureLevelTypeCase::generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
{
    generateInternalFormatTextureGenerationGroup(iterations, m_target);
}

bool TextureLevelTypeCase::checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
{
    bool allOk = true;

    if (spec.levels.empty())
    {
        allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, m_pname, GL_NONE, m_type);
    }
    else
    {
        for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
        {
            const int queryLevel = spec.levels[levelNdx].level;
            const int refValue   = getComponentType(spec.levels[levelNdx].internalFormat);

            allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, m_pname, refValue, m_type);
        }
    }

    return allOk;
}

int TextureLevelTypeCase::getComponentType(glw::GLenum internalFormat)
{
    const tcu::TextureFormat format             = glu::mapGLInternalFormat(internalFormat);
    const tcu::TextureChannelClass channelClass = tcu::getTextureChannelClass(format.type);
    glw::GLenum channelType                     = GL_NONE;

    // depth-stencil special cases
    if (format.type == tcu::TextureFormat::UNSIGNED_INT_24_8)
    {
        if (m_pname == GL_TEXTURE_DEPTH_TYPE)
            return GL_UNSIGNED_NORMALIZED;
        else
            return GL_NONE;
    }
    else if (format.type == tcu::TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV)
    {
        if (m_pname == GL_TEXTURE_DEPTH_TYPE)
            return GL_FLOAT;
        else
            return GL_NONE;
    }
    else
    {
        switch (channelClass)
        {
        case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
            channelType = GL_SIGNED_NORMALIZED;
            break;
        case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
            channelType = GL_UNSIGNED_NORMALIZED;
            break;
        case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
            channelType = GL_INT;
            break;
        case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
            channelType = GL_UNSIGNED_INT;
            break;
        case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
            channelType = GL_FLOAT;
            break;
        default:
            DE_ASSERT(false);
        }
    }

    switch (m_pname)
    {
    case GL_TEXTURE_RED_TYPE:
        if (format.order == tcu::TextureFormat::R || format.order == tcu::TextureFormat::RG ||
            format.order == tcu::TextureFormat::RGB || format.order == tcu::TextureFormat::RGBA ||
            format.order == tcu::TextureFormat::BGRA || format.order == tcu::TextureFormat::ARGB ||
            format.order == tcu::TextureFormat::sRGB || format.order == tcu::TextureFormat::sRGBA)
            return channelType;
        else
            return GL_NONE;

    case GL_TEXTURE_GREEN_TYPE:
        if (format.order == tcu::TextureFormat::RG || format.order == tcu::TextureFormat::RGB ||
            format.order == tcu::TextureFormat::RGBA || format.order == tcu::TextureFormat::BGRA ||
            format.order == tcu::TextureFormat::ARGB || format.order == tcu::TextureFormat::sRGB ||
            format.order == tcu::TextureFormat::sRGBA)
            return channelType;
        else
            return GL_NONE;

    case GL_TEXTURE_BLUE_TYPE:
        if (format.order == tcu::TextureFormat::RGB || format.order == tcu::TextureFormat::RGBA ||
            format.order == tcu::TextureFormat::BGRA || format.order == tcu::TextureFormat::ARGB ||
            format.order == tcu::TextureFormat::sRGB || format.order == tcu::TextureFormat::sRGBA)
            return channelType;
        else
            return GL_NONE;

    case GL_TEXTURE_ALPHA_TYPE:
        if (format.order == tcu::TextureFormat::RGBA || format.order == tcu::TextureFormat::BGRA ||
            format.order == tcu::TextureFormat::ARGB || format.order == tcu::TextureFormat::sRGBA)
            return channelType;
        else
            return GL_NONE;

    case GL_TEXTURE_DEPTH_TYPE:
        if (format.order == tcu::TextureFormat::D || format.order == tcu::TextureFormat::DS)
            return channelType;
        else
            return GL_NONE;

    default:
        DE_ASSERT(false);
        return 0;
    }
}

class TextureLevelCompressedCase : public TextureLevelCase
{
public:
    TextureLevelCompressedCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCase(ctx, name, desc, target, type)
    {
    }

private:
    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
    {
        generateCompressedTextureGenerationGroup(iterations, m_target);
    }

    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        bool allOk = true;

        if (spec.levels.empty())
        {
            allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_COMPRESSED, 0, m_type);
        }
        else
        {
            for (int levelNdx = 0; levelNdx < (int)spec.levels.size(); ++levelNdx)
            {
                const int queryLevel = spec.levels[levelNdx].level;
                const int refValue   = (spec.levels[levelNdx].compressed) ? (1) : (0);

                allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, queryLevel, GL_TEXTURE_COMPRESSED,
                                                          refValue, m_type);
            }
        }

        return allOk;
    }
};

static bool checkSupport(Context &ctx)
{
    auto ctxType = ctx.getRenderContext().getType();
    return contextSupports(ctxType, glu::ApiType::es(3, 2)) || contextSupports(ctxType, glu::ApiType::core(4, 5)) ||
           ctx.getContextInfo().isExtensionSupported("GL_EXT_texture_buffer");
}

class TextureLevelBufferDataStoreCase : public TextureLevelCase
{
public:
    TextureLevelBufferDataStoreCase(Context &ctx, const char *name, const char *desc, glw::GLenum target,
                                    QueryType type)
        : TextureLevelCase(ctx, name, desc, target, type)
    {
    }

private:
    void init(void)
    {
        if (!checkSupport(m_context))
            throw tcu::NotSupportedError("Test requires GL_EXT_texture_buffer extension");
        TextureLevelCase::init();
    }

    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
    {
        generateTextureBufferGenerationGroup(iterations, m_target);
    }

    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        bool allOk = true;

        if (spec.levels.empty())
        {
            allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_BUFFER_DATA_STORE_BINDING, 0,
                                                      m_type);
        }
        else
        {
            allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_BUFFER_DATA_STORE_BINDING,
                                                      m_texBuffer, m_type);
        }

        return allOk;
    }
};

class TextureLevelBufferDataOffsetCase : public TextureLevelCase
{
public:
    TextureLevelBufferDataOffsetCase(Context &ctx, const char *name, const char *desc, glw::GLenum target,
                                     QueryType type)
        : TextureLevelCase(ctx, name, desc, target, type)
    {
    }

private:
    void init(void)
    {
        if (!checkSupport(m_context))
            throw tcu::NotSupportedError("Test requires GL_EXT_texture_buffer extension");
        TextureLevelCase::init();
    }

    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
    {
        generateTextureBufferGenerationGroup(iterations, m_target);
    }

    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        bool allOk = true;

        if (spec.levels.empty())
        {
            allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_BUFFER_OFFSET, 0, m_type);
        }
        else
        {
            const int refValue = spec.texBufferDataOffset;

            allOk &=
                verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_BUFFER_OFFSET, refValue, m_type);
        }

        return allOk;
    }
};

class TextureLevelBufferDataSizeCase : public TextureLevelCase
{
public:
    TextureLevelBufferDataSizeCase(Context &ctx, const char *name, const char *desc, glw::GLenum target, QueryType type)
        : TextureLevelCase(ctx, name, desc, target, type)
    {
    }

private:
    void init(void)
    {
        if (!checkSupport(m_context))
            throw tcu::NotSupportedError("Test requires GL_EXT_texture_buffer extension");
        TextureLevelCase::init();
    }

    void generateTestIterations(std::vector<TextureGenerationSpec> &iterations)
    {
        generateTextureBufferGenerationGroup(iterations, m_target);
    }

    bool checkTextureState(glu::CallLogWrapper &gl, const TextureGenerationSpec &spec)
    {
        bool allOk = true;

        if (spec.levels.empty())
        {
            allOk &= verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_BUFFER_SIZE, 0, m_type);
        }
        else
        {
            const int refValue = spec.texBufferDataSize;

            allOk &=
                verifyTextureLevelParameterEqual(gl, spec.queryTarget, 0, GL_TEXTURE_BUFFER_SIZE, refValue, m_type);
        }

        return allOk;
    }
};

} // namespace

static const char *getVerifierSuffix(QueryType type)
{
    switch (type)
    {
    case QUERY_TEXTURE_LEVEL_FLOAT:
        return "_float";
    case QUERY_TEXTURE_LEVEL_INTEGER:
        return "_integer";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

TextureLevelStateQueryTests::TextureLevelStateQueryTests(Context &context)
    : TestCaseGroup(context, "texture_level", "GetTexLevelParameter tests")
{
}

TextureLevelStateQueryTests::~TextureLevelStateQueryTests(void)
{
}

void TextureLevelStateQueryTests::init(void)
{
    static const QueryType verifiers[] = {
        QUERY_TEXTURE_LEVEL_INTEGER,
        QUERY_TEXTURE_LEVEL_FLOAT,
    };

#define FOR_EACH_VERIFIER(X)                                                                  \
    do                                                                                        \
    {                                                                                         \
        for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx) \
        {                                                                                     \
            const std::string verifierSuffix = getVerifierSuffix(verifiers[verifierNdx]);     \
            const QueryType verifier         = verifiers[verifierNdx];                        \
            targetGroup->addChild(X);                                                         \
        }                                                                                     \
    } while (0)

    static const struct
    {
        const char *name;
        glw::GLenum target;
    } textureTargets[] = {
        {
            "texture_2d",
            GL_TEXTURE_2D,
        },
        {
            "texture_3d",
            GL_TEXTURE_3D,
        },
        {
            "texture_2d_array",
            GL_TEXTURE_2D_ARRAY,
        },
        {
            "texture_cube_map",
            GL_TEXTURE_CUBE_MAP,
        },
        {
            "texture_2d_multisample",
            GL_TEXTURE_2D_MULTISAMPLE,
        },
        {
            "texture_2d_multisample_array",
            GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
        }, // GL_OES_texture_storage_multisample_2d_array
        {
            "texture_buffer",
            GL_TEXTURE_BUFFER,
        }, // GL_EXT_texture_buffer
        {
            "texture_cube_array",
            GL_TEXTURE_CUBE_MAP_ARRAY,
        }, // GL_EXT_texture_cube_map_array
    };

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(textureTargets); ++targetNdx)
    {
        tcu::TestCaseGroup *const targetGroup =
            new tcu::TestCaseGroup(m_testCtx, textureTargets[targetNdx].name, textureTargets[targetNdx].name);
        addChild(targetGroup);

        FOR_EACH_VERIFIER(new TextureLevelSampleCase(m_context, ("samples" + verifierSuffix).c_str(),
                                                     "Verify TEXTURE_SAMPLES", textureTargets[targetNdx].target,
                                                     verifier));
        FOR_EACH_VERIFIER(new TextureLevelFixedSamplesCase(
            m_context, ("fixed_sample_locations" + verifierSuffix).c_str(), "Verify TEXTURE_FIXED_SAMPLE_LOCATIONS",
            textureTargets[targetNdx].target, verifier));
        FOR_EACH_VERIFIER(new TextureLevelWidthCase(m_context, ("width" + verifierSuffix).c_str(),
                                                    "Verify TEXTURE_WIDTH", textureTargets[targetNdx].target,
                                                    verifier));
        FOR_EACH_VERIFIER(new TextureLevelHeightCase(m_context, ("height" + verifierSuffix).c_str(),
                                                     "Verify TEXTURE_HEIGHT", textureTargets[targetNdx].target,
                                                     verifier));
        FOR_EACH_VERIFIER(new TextureLevelDepthCase(m_context, ("depth" + verifierSuffix).c_str(),
                                                    "Verify TEXTURE_DEPTH", textureTargets[targetNdx].target,
                                                    verifier));
        FOR_EACH_VERIFIER(new TextureLevelInternalFormatCase(m_context, ("internal_format" + verifierSuffix).c_str(),
                                                             "Verify TEXTURE_INTERNAL_FORMAT",
                                                             textureTargets[targetNdx].target, verifier));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("red_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_RED_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_RED_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("green_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_GREEN_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_GREEN_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("blue_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_BLUE_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_BLUE_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("alpha_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_ALPHA_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_ALPHA_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("depth_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_DEPTH_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_DEPTH_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("stencil_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_STENCIL_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_STENCIL_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelSizeCase(m_context, ("shared_size" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_SHARED_SIZE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_SHARED_SIZE));
        FOR_EACH_VERIFIER(new TextureLevelTypeCase(m_context, ("red_type" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_RED_TYPE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_RED_TYPE));
        FOR_EACH_VERIFIER(new TextureLevelTypeCase(m_context, ("green_type" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_GREEN_TYPE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_GREEN_TYPE));
        FOR_EACH_VERIFIER(new TextureLevelTypeCase(m_context, ("blue_type" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_BLUE_TYPE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_BLUE_TYPE));
        FOR_EACH_VERIFIER(new TextureLevelTypeCase(m_context, ("alpha_type" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_ALPHA_TYPE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_ALPHA_TYPE));
        FOR_EACH_VERIFIER(new TextureLevelTypeCase(m_context, ("depth_type" + verifierSuffix).c_str(),
                                                   "Verify TEXTURE_DEPTH_TYPE", textureTargets[targetNdx].target,
                                                   verifier, GL_TEXTURE_DEPTH_TYPE));
        FOR_EACH_VERIFIER(new TextureLevelCompressedCase(m_context, ("compressed" + verifierSuffix).c_str(),
                                                         "Verify TEXTURE_COMPRESSED", textureTargets[targetNdx].target,
                                                         verifier));
        FOR_EACH_VERIFIER(new TextureLevelBufferDataStoreCase(
            m_context, ("buffer_data_store_binding" + verifierSuffix).c_str(),
            "Verify TEXTURE_BUFFER_DATA_STORE_BINDING", textureTargets[targetNdx].target, verifier));
        FOR_EACH_VERIFIER(new TextureLevelBufferDataOffsetCase(m_context, ("buffer_offset" + verifierSuffix).c_str(),
                                                               "Verify TEXTURE_BUFFER_OFFSET",
                                                               textureTargets[targetNdx].target, verifier));
        FOR_EACH_VERIFIER(new TextureLevelBufferDataSizeCase(m_context, ("buffer_size" + verifierSuffix).c_str(),
                                                             "Verify TEXTURE_BUFFER_SIZE",
                                                             textureTargets[targetNdx].target, verifier));
    }

#undef FOR_EACH_VERIFIER
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
