/*-------------------------------------------------------------------------
 * 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 Shader state query tests
 *//*--------------------------------------------------------------------*/

#include "es31fShaderStateQueryTests.hpp"
#include "es31fInfoLogQueryShared.hpp"
#include "glsStateQueryUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluStrUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static inline std::string brokenShaderSource(const glu::ContextType &contextType)
{
    const std::string glslVersionDecl = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType));

    return glslVersionDecl + "\n"
                             "broken, this should not compile,\n"
                             "{";
}

class BaseTypeCase : public TestCase
{
public:
    struct TestTypeInfo
    {
        glw::GLenum glType;
        const char *declarationStr;
        const char *accessStr;
    };

    BaseTypeCase(Context &ctx, const char *name, const char *desc, const char *extension);

private:
    IterateResult iterate(void);
    virtual std::vector<TestTypeInfo> getInfos(void) const = 0;
    virtual void checkRequirements(void) const;

    const char *const m_extension;
};

BaseTypeCase::BaseTypeCase(Context &ctx, const char *name, const char *desc, const char *extension)
    : TestCase(ctx, name, desc)
    , m_extension(extension)
{
}

BaseTypeCase::IterateResult BaseTypeCase::iterate(void)
{
    static const char *const vertexSourceTemplate   = "${VERSIONDECL}\n"
                                                      "in highp vec4 a_position;\n"
                                                      "void main(void)\n"
                                                      "{\n"
                                                      "    gl_Position = a_position;\n"
                                                      "}\n";
    static const char *const fragmentSourceTemplate = "${VERSIONDECL}\n"
                                                      "${EXTENSIONSTATEMENT}"
                                                      "${DECLARATIONSTR};\n"
                                                      "layout(location = 0) out highp vec4 dEQP_FragColor;\n"
                                                      "void main(void)\n"
                                                      "{\n"
                                                      "    dEQP_FragColor = vec4(${ACCESSSTR});\n"
                                                      "}\n";

    tcu::ResultCollector result(m_testCtx.getLog());
    std::vector<TestTypeInfo> samplerTypes = getInfos();
    auto ctxType                           = m_context.getRenderContext().getType();
    const bool isES32orGL45                = glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
                              glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

    if (m_extension && !isES32orGL45 && !m_context.getContextInfo().isExtensionSupported(m_extension))
        throw tcu::NotSupportedError("Test requires " + std::string(m_extension));
    checkRequirements();

    for (int typeNdx = 0; typeNdx < (int)samplerTypes.size(); ++typeNdx)
    {
        const tcu::ScopedLogSection section(
            m_testCtx.getLog(), std::string(glu::getShaderVarTypeStr(samplerTypes[typeNdx].glType).toString()),
            "Uniform type " + glu::getShaderVarTypeStr(samplerTypes[typeNdx].glType).toString());

        std::map<std::string, std::string> shaderArgs;
        shaderArgs["DECLARATIONSTR"] = samplerTypes[typeNdx].declarationStr;
        shaderArgs["ACCESSSTR"]      = samplerTypes[typeNdx].accessStr;
        shaderArgs["EXTENSIONSTATEMENT"] =
            (m_extension && !isES32orGL45) ? (std::string() + "#extension " + m_extension + " : require\n") : ("");
        shaderArgs["VERSIONDECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(ctxType));

        const std::string fragmentSource = tcu::StringTemplate(fragmentSourceTemplate).specialize(shaderArgs);
        const std::string vertexSource   = tcu::StringTemplate(vertexSourceTemplate).specialize(shaderArgs);
        const glw::Functions &gl         = m_context.getRenderContext().getFunctions();
        glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources()
                                                                     << glu::VertexSource(vertexSource)
                                                                     << glu::FragmentSource(fragmentSource));

        m_testCtx.getLog() << tcu::TestLog::Message << "Building program with uniform sampler of type "
                           << glu::getShaderVarTypeStr(samplerTypes[typeNdx].glType) << tcu::TestLog::EndMessage;

        if (!program.isOk())
        {
            m_testCtx.getLog() << program;
            result.fail("could not build shader");
        }
        else
        {
            // only one uniform -- uniform at index 0
            int uniforms = 0;
            gl.getProgramiv(program.getProgram(), GL_ACTIVE_UNIFORMS, &uniforms);

            if (uniforms != 1)
                result.fail("Unexpected GL_ACTIVE_UNIFORMS, expected 1");
            else
            {
                // check type
                const glw::GLuint uniformIndex = 0;
                glw::GLint type                = 0;

                m_testCtx.getLog() << tcu::TestLog::Message << "Verifying uniform type." << tcu::TestLog::EndMessage;
                gl.getActiveUniformsiv(program.getProgram(), 1, &uniformIndex, GL_UNIFORM_TYPE, &type);

                if (type != (glw::GLint)samplerTypes[typeNdx].glType)
                {
                    std::ostringstream buf;
                    buf << "Invalid type, expected " << samplerTypes[typeNdx].glType << ", got " << type;
                    result.fail(buf.str());
                }
            }
        }

        GLU_EXPECT_NO_ERROR(gl.getError(), "");
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

void BaseTypeCase::checkRequirements(void) const
{
}

class CoreSamplerTypeCase : public BaseTypeCase
{
public:
    CoreSamplerTypeCase(Context &ctx, const char *name, const char *desc);

private:
    std::vector<TestTypeInfo> getInfos(void) const;
};

CoreSamplerTypeCase::CoreSamplerTypeCase(Context &ctx, const char *name, const char *desc)
    : BaseTypeCase(ctx, name, desc, DE_NULL)
{
}

std::vector<BaseTypeCase::TestTypeInfo> CoreSamplerTypeCase::getInfos(void) const
{
    static const TestTypeInfo samplerTypes[] = {
        {GL_SAMPLER_2D_MULTISAMPLE, "uniform highp sampler2DMS u_sampler",
         "texelFetch(u_sampler, ivec2(gl_FragCoord.xy), 0)"},
        {GL_INT_SAMPLER_2D_MULTISAMPLE, "uniform highp isampler2DMS u_sampler",
         "texelFetch(u_sampler, ivec2(gl_FragCoord.xy), 0)"},
        {GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE, "uniform highp usampler2DMS u_sampler",
         "texelFetch(u_sampler, ivec2(gl_FragCoord.xy), 0)"},
    };

    std::vector<TestTypeInfo> infos;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(samplerTypes); ++ndx)
        infos.push_back(samplerTypes[ndx]);

    return infos;
}

class MSArraySamplerTypeCase : public BaseTypeCase
{
public:
    MSArraySamplerTypeCase(Context &ctx, const char *name, const char *desc);

private:
    std::vector<TestTypeInfo> getInfos(void) const;
};

MSArraySamplerTypeCase::MSArraySamplerTypeCase(Context &ctx, const char *name, const char *desc)
    : BaseTypeCase(ctx, name, desc, "GL_OES_texture_storage_multisample_2d_array")
{
}

std::vector<BaseTypeCase::TestTypeInfo> MSArraySamplerTypeCase::getInfos(void) const
{
    static const TestTypeInfo samplerTypes[] = {
        {GL_SAMPLER_2D_MULTISAMPLE_ARRAY, "uniform highp sampler2DMSArray u_sampler",
         "texelFetch(u_sampler, ivec3(gl_FragCoord.xyz), 0)"},
        {GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, "uniform highp isampler2DMSArray u_sampler",
         "texelFetch(u_sampler, ivec3(gl_FragCoord.xyz), 0)"},
        {GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, "uniform highp usampler2DMSArray u_sampler",
         "texelFetch(u_sampler, ivec3(gl_FragCoord.xyz), 0)"},
    };

    std::vector<TestTypeInfo> infos;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(samplerTypes); ++ndx)
        infos.push_back(samplerTypes[ndx]);

    return infos;
}

class TextureBufferSamplerTypeCase : public BaseTypeCase
{
public:
    TextureBufferSamplerTypeCase(Context &ctx, const char *name, const char *desc);

private:
    std::vector<TestTypeInfo> getInfos(void) const;
};

TextureBufferSamplerTypeCase::TextureBufferSamplerTypeCase(Context &ctx, const char *name, const char *desc)
    : BaseTypeCase(ctx, name, desc, "GL_EXT_texture_buffer")
{
}

std::vector<BaseTypeCase::TestTypeInfo> TextureBufferSamplerTypeCase::getInfos(void) const
{
    static const TestTypeInfo samplerTypes[] = {
        {GL_SAMPLER_BUFFER, "uniform highp samplerBuffer u_sampler", "texelFetch(u_sampler, int(gl_FragCoord.x))"},
        {GL_INT_SAMPLER_BUFFER, "uniform highp isamplerBuffer u_sampler", "texelFetch(u_sampler, int(gl_FragCoord.x))"},
        {GL_UNSIGNED_INT_SAMPLER_BUFFER, "uniform highp usamplerBuffer u_sampler",
         "texelFetch(u_sampler, int(gl_FragCoord.x))"},
    };

    std::vector<TestTypeInfo> infos;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(samplerTypes); ++ndx)
        infos.push_back(samplerTypes[ndx]);

    return infos;
}

class TextureBufferImageTypeCase : public BaseTypeCase
{
public:
    TextureBufferImageTypeCase(Context &ctx, const char *name, const char *desc);

private:
    std::vector<TestTypeInfo> getInfos(void) const;
    void checkRequirements(void) const;
};

TextureBufferImageTypeCase::TextureBufferImageTypeCase(Context &ctx, const char *name, const char *desc)
    : BaseTypeCase(ctx, name, desc, "GL_EXT_texture_buffer")
{
}

std::vector<BaseTypeCase::TestTypeInfo> TextureBufferImageTypeCase::getInfos(void) const
{
    static const TestTypeInfo samplerTypes[] = {
        {GL_IMAGE_BUFFER, "layout(binding=0, rgba8) readonly uniform highp imageBuffer u_image",
         "imageLoad(u_image, int(gl_FragCoord.x))"},
        {GL_INT_IMAGE_BUFFER, "layout(binding=0, r32i) readonly uniform highp iimageBuffer u_image",
         "imageLoad(u_image, int(gl_FragCoord.x))"},
        {GL_UNSIGNED_INT_IMAGE_BUFFER, "layout(binding=0, r32ui) readonly uniform highp uimageBuffer u_image",
         "imageLoad(u_image, int(gl_FragCoord.x))"},
    };

    std::vector<TestTypeInfo> infos;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(samplerTypes); ++ndx)
        infos.push_back(samplerTypes[ndx]);

    return infos;
}

void TextureBufferImageTypeCase::checkRequirements(void) const
{
    if (m_context.getContextInfo().getInt(GL_MAX_FRAGMENT_IMAGE_UNIFORMS) < 1)
        throw tcu::NotSupportedError("Test requires fragment images");
}

class CubeArraySamplerTypeCase : public BaseTypeCase
{
public:
    CubeArraySamplerTypeCase(Context &ctx, const char *name, const char *desc);

private:
    std::vector<TestTypeInfo> getInfos(void) const;
};

CubeArraySamplerTypeCase::CubeArraySamplerTypeCase(Context &ctx, const char *name, const char *desc)
    : BaseTypeCase(ctx, name, desc, "GL_EXT_texture_cube_map_array")
{
}

std::vector<BaseTypeCase::TestTypeInfo> CubeArraySamplerTypeCase::getInfos(void) const
{
    static const TestTypeInfo samplerTypes[] = {
        {GL_SAMPLER_CUBE_MAP_ARRAY, "uniform highp samplerCubeArray u_sampler",
         "texture(u_sampler, gl_FragCoord.xxyz)"},
        {GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW, "uniform highp samplerCubeArrayShadow u_sampler",
         "texture(u_sampler, gl_FragCoord.xxyz, 0.5)"},
        {GL_INT_SAMPLER_CUBE_MAP_ARRAY, "uniform highp isamplerCubeArray u_sampler",
         "texture(u_sampler, gl_FragCoord.xxyz)"},
        {GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY, "uniform highp usamplerCubeArray u_sampler",
         "texture(u_sampler, gl_FragCoord.xxyz)"},
    };

    std::vector<TestTypeInfo> infos;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(samplerTypes); ++ndx)
        infos.push_back(samplerTypes[ndx]);

    return infos;
}

class CubeArrayImageTypeCase : public BaseTypeCase
{
public:
    CubeArrayImageTypeCase(Context &ctx, const char *name, const char *desc);

private:
    std::vector<TestTypeInfo> getInfos(void) const;
    void checkRequirements(void) const;
};

CubeArrayImageTypeCase::CubeArrayImageTypeCase(Context &ctx, const char *name, const char *desc)
    : BaseTypeCase(ctx, name, desc, "GL_EXT_texture_cube_map_array")
{
}

std::vector<BaseTypeCase::TestTypeInfo> CubeArrayImageTypeCase::getInfos(void) const
{
    static const TestTypeInfo samplerTypes[] = {
        {GL_IMAGE_CUBE_MAP_ARRAY, "layout(binding=0, rgba8) readonly uniform highp imageCubeArray u_image",
         "imageLoad(u_image, ivec3(gl_FragCoord.xyx))"},
        {GL_INT_IMAGE_CUBE_MAP_ARRAY, "layout(binding=0, r32i) readonly uniform highp iimageCubeArray u_image",
         "imageLoad(u_image, ivec3(gl_FragCoord.xyx))"},
        {GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY,
         "layout(binding=0, r32ui) readonly uniform highp uimageCubeArray u_image",
         "imageLoad(u_image, ivec3(gl_FragCoord.xyx))"},
    };

    std::vector<TestTypeInfo> infos;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(samplerTypes); ++ndx)
        infos.push_back(samplerTypes[ndx]);

    return infos;
}

void CubeArrayImageTypeCase::checkRequirements(void) const
{
    if (m_context.getContextInfo().getInt(GL_MAX_FRAGMENT_IMAGE_UNIFORMS) < 1)
        throw tcu::NotSupportedError("Test requires fragment images");
}

class ShaderLogCase : public TestCase
{
public:
    ShaderLogCase(Context &ctx, const char *name, const char *desc, glu::ShaderType shaderType);

private:
    void init(void);
    IterateResult iterate(void);

    const glu::ShaderType m_shaderType;
};

ShaderLogCase::ShaderLogCase(Context &ctx, const char *name, const char *desc, glu::ShaderType shaderType)
    : TestCase(ctx, name, desc)
    , m_shaderType(shaderType)
{
}

void ShaderLogCase::init(void)
{
    auto ctxType            = m_context.getRenderContext().getType();
    const bool isES32orGL45 = glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
                              glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

    switch (m_shaderType)
    {
    case glu::SHADERTYPE_VERTEX:
    case glu::SHADERTYPE_FRAGMENT:
    case glu::SHADERTYPE_COMPUTE:
        break;

    case glu::SHADERTYPE_GEOMETRY:
        if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader") && !isES32orGL45)
            throw tcu::NotSupportedError(
                "Test requires GL_EXT_geometry_shader extension or an OpenGL ES 3.2 or higher context.");
        break;

    case glu::SHADERTYPE_TESSELLATION_CONTROL:
    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
        if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") && !isES32orGL45)
            throw tcu::NotSupportedError(
                "Test requires GL_EXT_tessellation_shader extension or an OpenGL ES 3.2 or higher context.");
        break;

    default:
        DE_ASSERT(false);
        break;
    }
}

ShaderLogCase::IterateResult ShaderLogCase::iterate(void)
{
    using gls::StateQueryUtil::StateQueryMemoryWriteGuard;

    tcu::ResultCollector result(m_testCtx.getLog());
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    uint32_t shader                = 0;
    const std::string source       = brokenShaderSource(m_context.getRenderContext().getType());
    const char *const brokenSource = source.c_str();
    StateQueryMemoryWriteGuard<glw::GLint> logLen;

    gl.enableLogging(true);

    m_testCtx.getLog() << tcu::TestLog::Message << "Trying to compile broken shader source."
                       << tcu::TestLog::EndMessage;

    shader = gl.glCreateShader(glu::getGLShaderType(m_shaderType));
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "create shader");

    gl.glShaderSource(shader, 1, &brokenSource, DE_NULL);
    gl.glCompileShader(shader);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "compile");

    gl.glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
    logLen.verifyValidity(result);

    if (!logLen.isUndefined())
        verifyInfoLogQuery(result, gl, logLen, shader, &glu::CallLogWrapper::glGetShaderInfoLog, "glGetShaderInfoLog");

    gl.glDeleteShader(shader);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "delete");

    result.setTestContextResult(m_testCtx);
    return STOP;
}

} // namespace

ShaderStateQueryTests::ShaderStateQueryTests(Context &context)
    : TestCaseGroup(context, "shader", "Shader state query tests")
{
}

ShaderStateQueryTests::~ShaderStateQueryTests(void)
{
}

void ShaderStateQueryTests::init(void)
{
    addChild(new CoreSamplerTypeCase(m_context, "sampler_type", "Sampler type cases"));
    addChild(new MSArraySamplerTypeCase(m_context, "sampler_type_multisample_array", "MSAA array sampler type cases"));
    addChild(new TextureBufferSamplerTypeCase(m_context, "sampler_type_texture_buffer",
                                              "Texture buffer sampler type cases"));
    addChild(new TextureBufferImageTypeCase(m_context, "image_type_texture_buffer", "Texture buffer image type cases"));
    addChild(new CubeArraySamplerTypeCase(m_context, "sampler_type_cube_array", "Cube array sampler type cases"));
    addChild(new CubeArrayImageTypeCase(m_context, "image_type_cube_array", "Cube array image type cases"));

    // shader info log tests
    // \note, there exists similar tests in gles3 module. However, the gles31 could use a different
    //        shader compiler with different INFO_LOG bugs.
    {
        static const struct
        {
            const char *caseName;
            glu::ShaderType caseType;
        } shaderTypes[] = {
            {"info_log_vertex", glu::SHADERTYPE_VERTEX},
            {"info_log_fragment", glu::SHADERTYPE_FRAGMENT},
            {"info_log_geometry", glu::SHADERTYPE_GEOMETRY},
            {"info_log_tess_ctrl", glu::SHADERTYPE_TESSELLATION_CONTROL},
            {"info_log_tess_eval", glu::SHADERTYPE_TESSELLATION_EVALUATION},
            {"info_log_compute", glu::SHADERTYPE_COMPUTE},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(shaderTypes); ++ndx)
            addChild(new ShaderLogCase(m_context, shaderTypes[ndx].caseName, "", shaderTypes[ndx].caseType));
    }
}

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