/*-------------------------------------------------------------------------
 * 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 built-in constant tests.
 *//*--------------------------------------------------------------------*/

#include "es31fShaderBuiltinConstantTests.hpp"
#include "glsShaderExecUtil.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "tcuTestLog.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

using std::string;
using std::vector;
using tcu::TestLog;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static int getInteger(const glw::Functions &gl, uint32_t pname)
{
    int value = -1;
    gl.getIntegerv(pname, &value);
    GLU_EXPECT_NO_ERROR(gl.getError(),
                        ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
    return value;
}

template <uint32_t Pname>
static int getInteger(const glw::Functions &gl)
{
    return getInteger(gl, Pname);
}

static int getVectorsFromComps(const glw::Functions &gl, uint32_t pname)
{
    int value = -1;
    gl.getIntegerv(pname, &value);
    GLU_EXPECT_NO_ERROR(gl.getError(),
                        ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
    TCU_CHECK_MSG(value % 4 == 0,
                  ("Expected " + glu::getGettableStateStr((int)pname).toString() + " to be divisible by 4").c_str());
    return value / 4;
}

template <uint32_t Pname>
static int getVectorsFromComps(const glw::Functions &gl)
{
    return getVectorsFromComps(gl, Pname);
}

static tcu::IVec3 getIVec3(const glw::Functions &gl, uint32_t pname)
{
    tcu::IVec3 value(-1);
    for (int ndx = 0; ndx < 3; ndx++)
        gl.getIntegeri_v(pname, (glw::GLuint)ndx, &value[ndx]);
    GLU_EXPECT_NO_ERROR(gl.getError(),
                        ("glGetIntegeri_v(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
    return value;
}

template <uint32_t Pname>
static tcu::IVec3 getIVec3(const glw::Functions &gl)
{
    return getIVec3(gl, Pname);
}

static std::string makeCaseName(const std::string &varName)
{
    DE_ASSERT(varName.length() > 3);
    DE_ASSERT(varName.substr(0, 3) == "gl_");

    std::ostringstream name;
    name << de::toLower(char(varName[3]));

    for (size_t ndx = 4; ndx < varName.length(); ndx++)
    {
        const char c = char(varName[ndx]);
        if (de::isUpper(c))
            name << '_' << de::toLower(c);
        else
            name << c;
    }

    return name.str();
}

enum
{
    VS = (1 << glu::SHADERTYPE_VERTEX),
    TC = (1 << glu::SHADERTYPE_TESSELLATION_CONTROL),
    TE = (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION),
    GS = (1 << glu::SHADERTYPE_GEOMETRY),
    FS = (1 << glu::SHADERTYPE_FRAGMENT),
    CS = (1 << glu::SHADERTYPE_COMPUTE),

    SHADER_TYPES = VS | TC | TE | GS | FS | CS
};

template <typename DataType>
class ShaderBuiltinConstantCase : public TestCase
{
public:
    typedef DataType (*GetConstantValueFunc)(const glw::Functions &gl);

    ShaderBuiltinConstantCase(Context &context, const char *varName, GetConstantValueFunc getValue,
                              const char *requiredExt);
    ~ShaderBuiltinConstantCase(void);

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

private:
    bool verifyInShaderType(glu::ShaderType shaderType, DataType reference);

    const std::string m_varName;
    const GetConstantValueFunc m_getValue;
    const std::string m_requiredExt;
};

template <typename DataType>
ShaderBuiltinConstantCase<DataType>::ShaderBuiltinConstantCase(Context &context, const char *varName,
                                                               GetConstantValueFunc getValue, const char *requiredExt)
    : TestCase(context, makeCaseName(varName).c_str(), varName)
    , m_varName(varName)
    , m_getValue(getValue)
    , m_requiredExt(requiredExt ? requiredExt : "")
{
    DE_ASSERT(!requiredExt == m_requiredExt.empty());
}

template <typename DataType>
ShaderBuiltinConstantCase<DataType>::~ShaderBuiltinConstantCase(void)
{
}

template <typename DataType>
void ShaderBuiltinConstantCase<DataType>::init(void)
{
    const bool supportsES32orGL45 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
                                    contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
    {
        if (m_varName == "gl_MaxVertexOutputVectors" || m_varName == "gl_MaxFragmentInputVectors")
        {
            std::string message =
                "The test requires a GLES context. The constant '" + m_varName + "' is not supported.";
            TCU_THROW(NotSupportedError, message.c_str());
        }
    }

    if (m_requiredExt == "GL_OES_sample_variables" || m_requiredExt == "GL_EXT_geometry_shader" ||
        m_requiredExt == "GL_EXT_tessellation_shader")
    {
        if (!supportsES32orGL45)
        {
            const std::string message =
                "The test requires a GLES 3.2 or GL 4.5 context or support for the extension " + m_requiredExt + ".";
            TCU_CHECK_AND_THROW(NotSupportedError,
                                m_context.getContextInfo().isExtensionSupported(m_requiredExt.c_str()),
                                message.c_str());
        }
    }
    else if (!m_requiredExt.empty() && !m_context.getContextInfo().isExtensionSupported(m_requiredExt.c_str()))
        throw tcu::NotSupportedError(m_requiredExt + " not supported");

    if (!supportsES32orGL45 &&
        (m_varName == "gl_MaxTessControlImageUniforms" || m_varName == "gl_MaxTessEvaluationImageUniforms" ||
         m_varName == "gl_MaxTessControlAtomicCounters" || m_varName == "gl_MaxTessEvaluationAtomicCounters" ||
         m_varName == "gl_MaxTessControlAtomicCounterBuffers" ||
         m_varName == "gl_MaxTessEvaluationAtomicCounterBuffers"))
    {
        std::string message =
            "The test requires a GLES 3.2 or GL 4.5 context. The constant '" + m_varName + "' is not supported.";
        TCU_THROW(NotSupportedError, message.c_str());
    }
}

static gls::ShaderExecUtil::ShaderExecutor *createGetConstantExecutor(const glu::RenderContext &renderCtx,
                                                                      glu::ShaderType shaderType,
                                                                      glu::DataType dataType,
                                                                      const std::string &varName,
                                                                      const std::string &extName)
{
    using namespace gls::ShaderExecUtil;

    const bool supportsES32orGL45 = contextSupports(renderCtx.getType(), glu::ApiType::es(3, 2)) ||
                                    contextSupports(renderCtx.getType(), glu::ApiType::core(4, 5));
    ShaderSpec shaderSpec;

    shaderSpec.version = glu::getContextTypeGLSLVersion(renderCtx.getType());
    shaderSpec.source  = string("result = ") + varName + ";\n";

    shaderSpec.outputs.push_back(Symbol("result", glu::VarType(dataType, glu::PRECISION_HIGHP)));

    if (!extName.empty() &&
        !(supportsES32orGL45 && (extName == "GL_OES_sample_variables" || extName == "GL_EXT_geometry_shader" ||
                                 extName == "GL_EXT_tessellation_shader")))
        shaderSpec.globalDeclarations = "#extension " + extName + " : require\n";

    return createExecutor(renderCtx, shaderType, shaderSpec);
}

template <typename DataType>
static void logVarValue(tcu::TestLog &log, const std::string &varName, DataType value)
{
    log << TestLog::Message << varName << " = " << value << TestLog::EndMessage;
}

template <>
void logVarValue<int>(tcu::TestLog &log, const std::string &varName, int value)
{
    log << TestLog::Integer(varName, varName, "", QP_KEY_TAG_NONE, value);
}

template <typename DataType>
bool ShaderBuiltinConstantCase<DataType>::verifyInShaderType(glu::ShaderType shaderType, DataType reference)
{
    using namespace gls::ShaderExecUtil;

    const de::UniquePtr<ShaderExecutor> shaderExecutor(createGetConstantExecutor(
        m_context.getRenderContext(), shaderType, glu::dataTypeOf<DataType>(), m_varName, m_requiredExt));
    DataType result     = DataType(-1);
    void *const outputs = &result;

    if (!shaderExecutor->isOk())
    {
        shaderExecutor->log(m_testCtx.getLog());
        TCU_FAIL("Compile failed");
    }

    shaderExecutor->useProgram();
    shaderExecutor->execute(1, DE_NULL, &outputs);

    logVarValue(m_testCtx.getLog(), m_varName, result);

    if (result != reference)
    {
        m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << m_varName << " = " << reference
                           << TestLog::EndMessage << TestLog::Message << "Test shader:" << TestLog::EndMessage;
        shaderExecutor->log(m_testCtx.getLog());
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid builtin constant value");
        return false;
    }
    else
        return true;
}

template <typename DataType>
TestCase::IterateResult ShaderBuiltinConstantCase<DataType>::iterate(void)
{
    const DataType reference = m_getValue(m_context.getRenderContext().getFunctions());

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
    {
        if ((SHADER_TYPES & (1 << shaderType)) != 0)
        {
            const char *const shaderTypeName = glu::getShaderTypeName(glu::ShaderType(shaderType));
            const tcu::ScopedLogSection section(m_testCtx.getLog(), shaderTypeName, shaderTypeName);

            try
            {
                const bool isOk = verifyInShaderType(glu::ShaderType(shaderType), reference);
                DE_ASSERT(isOk || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
                DE_UNREF(isOk);
            }
            catch (const tcu::NotSupportedError &e)
            {
                m_testCtx.getLog() << TestLog::Message << "Not checking " << shaderTypeName << ": " << e.what()
                                   << TestLog::EndMessage;
            }
            catch (const tcu::TestError &e)
            {
                m_testCtx.getLog() << TestLog::Message << e.what() << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, e.getMessage());
            }
        }
    }

    return STOP;
}

} // namespace

ShaderBuiltinConstantTests::ShaderBuiltinConstantTests(Context &context)
    : TestCaseGroup(context, "builtin_constants", "Built-in Constant Tests")
{
}

ShaderBuiltinConstantTests::~ShaderBuiltinConstantTests(void)
{
}

void ShaderBuiltinConstantTests::init(void)
{
    // Core builtin constants
    {
        static const struct
        {
            const char *varName;
            ShaderBuiltinConstantCase<int>::GetConstantValueFunc getValue;
        } intConstants[] = {
            {"gl_MaxVertexAttribs", getInteger<GL_MAX_VERTEX_ATTRIBS>},
            {"gl_MaxVertexUniformVectors", getInteger<GL_MAX_VERTEX_UNIFORM_VECTORS>},
            {"gl_MaxVertexOutputVectors", getVectorsFromComps<GL_MAX_VERTEX_OUTPUT_COMPONENTS>},
            {"gl_MaxFragmentInputVectors", getVectorsFromComps<GL_MAX_FRAGMENT_INPUT_COMPONENTS>},
            {"gl_MaxFragmentUniformVectors", getInteger<GL_MAX_FRAGMENT_UNIFORM_VECTORS>},
            {"gl_MaxDrawBuffers", getInteger<GL_MAX_DRAW_BUFFERS>},

            {"gl_MaxVertexTextureImageUnits", getInteger<GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS>},
            {"gl_MaxCombinedTextureImageUnits", getInteger<GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS>},
            {"gl_MaxTextureImageUnits", getInteger<GL_MAX_TEXTURE_IMAGE_UNITS>},

            {"gl_MinProgramTexelOffset", getInteger<GL_MIN_PROGRAM_TEXEL_OFFSET>},
            {"gl_MaxProgramTexelOffset", getInteger<GL_MAX_PROGRAM_TEXEL_OFFSET>},

            {"gl_MaxImageUnits", getInteger<GL_MAX_IMAGE_UNITS>},
            {"gl_MaxVertexImageUniforms", getInteger<GL_MAX_VERTEX_IMAGE_UNIFORMS>},
            {"gl_MaxFragmentImageUniforms", getInteger<GL_MAX_FRAGMENT_IMAGE_UNIFORMS>},
            {"gl_MaxComputeImageUniforms", getInteger<GL_MAX_COMPUTE_IMAGE_UNIFORMS>},
            {"gl_MaxCombinedImageUniforms", getInteger<GL_MAX_COMBINED_IMAGE_UNIFORMS>},

            {"gl_MaxCombinedShaderOutputResources", getInteger<GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES>},

            {"gl_MaxComputeUniformComponents", getInteger<GL_MAX_COMPUTE_UNIFORM_COMPONENTS>},
            {"gl_MaxComputeTextureImageUnits", getInteger<GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS>},

            {"gl_MaxComputeAtomicCounters", getInteger<GL_MAX_COMPUTE_ATOMIC_COUNTERS>},
            {"gl_MaxComputeAtomicCounterBuffers", getInteger<GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS>},

            {"gl_MaxVertexAtomicCounters", getInteger<GL_MAX_VERTEX_ATOMIC_COUNTERS>},
            {"gl_MaxFragmentAtomicCounters", getInteger<GL_MAX_FRAGMENT_ATOMIC_COUNTERS>},
            {"gl_MaxCombinedAtomicCounters", getInteger<GL_MAX_COMBINED_ATOMIC_COUNTERS>},
            {"gl_MaxAtomicCounterBindings", getInteger<GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS>},

            {"gl_MaxVertexAtomicCounterBuffers", getInteger<GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS>},
            {"gl_MaxFragmentAtomicCounterBuffers", getInteger<GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS>},
            {"gl_MaxCombinedAtomicCounterBuffers", getInteger<GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS>},
            {"gl_MaxAtomicCounterBufferSize", getInteger<GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE>},
        };

        static const struct
        {
            const char *varName;
            ShaderBuiltinConstantCase<tcu::IVec3>::GetConstantValueFunc getValue;
        } ivec3Constants[] = {
            {"gl_MaxComputeWorkGroupCount", getIVec3<GL_MAX_COMPUTE_WORK_GROUP_COUNT>},
            {"gl_MaxComputeWorkGroupSize", getIVec3<GL_MAX_COMPUTE_WORK_GROUP_SIZE>},
        };

        tcu::TestCaseGroup *const coreGroup = new tcu::TestCaseGroup(m_testCtx, "core", "Core Specification");
        addChild(coreGroup);

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
            coreGroup->addChild(new ShaderBuiltinConstantCase<int>(m_context, intConstants[ndx].varName,
                                                                   intConstants[ndx].getValue, DE_NULL));

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ivec3Constants); ndx++)
            coreGroup->addChild(new ShaderBuiltinConstantCase<tcu::IVec3>(m_context, ivec3Constants[ndx].varName,
                                                                          ivec3Constants[ndx].getValue, DE_NULL));
    }

    // OES_sample_variables
    {
        tcu::TestCaseGroup *const sampleVarGroup =
            new tcu::TestCaseGroup(m_testCtx, "sample_variables", "GL_OES_sample_variables");
        addChild(sampleVarGroup);
        sampleVarGroup->addChild(new ShaderBuiltinConstantCase<int>(
            m_context, "gl_MaxSamples", getInteger<GL_MAX_SAMPLES>, "GL_OES_sample_variables"));
    }

    // EXT_geometry_shader
    {
        static const struct
        {
            const char *varName;
            ShaderBuiltinConstantCase<int>::GetConstantValueFunc getValue;
        } intConstants[] = {
            {"gl_MaxGeometryInputComponents", getInteger<GL_MAX_GEOMETRY_INPUT_COMPONENTS>},
            {"gl_MaxGeometryOutputComponents", getInteger<GL_MAX_GEOMETRY_OUTPUT_COMPONENTS>},
            {"gl_MaxGeometryImageUniforms", getInteger<GL_MAX_GEOMETRY_IMAGE_UNIFORMS>},
            {"gl_MaxGeometryTextureImageUnits", getInteger<GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS>},
            {"gl_MaxGeometryOutputVertices", getInteger<GL_MAX_GEOMETRY_OUTPUT_VERTICES>},
            {"gl_MaxGeometryTotalOutputComponents", getInteger<GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS>},
            {"gl_MaxGeometryUniformComponents", getInteger<GL_MAX_GEOMETRY_UNIFORM_COMPONENTS>},
            {"gl_MaxGeometryAtomicCounters", getInteger<GL_MAX_GEOMETRY_ATOMIC_COUNTERS>},
            {"gl_MaxGeometryAtomicCounterBuffers", getInteger<GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS>},
        };

        tcu::TestCaseGroup *const geomGroup =
            new tcu::TestCaseGroup(m_testCtx, "geometry_shader", "GL_EXT_geometry_shader");
        addChild(geomGroup);

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
            geomGroup->addChild(new ShaderBuiltinConstantCase<int>(
                m_context, intConstants[ndx].varName, intConstants[ndx].getValue, "GL_EXT_geometry_shader"));
    }

    // EXT_tessellation_shader
    {
        static const struct
        {
            const char *varName;
            ShaderBuiltinConstantCase<int>::GetConstantValueFunc getValue;
        } intConstants[] = {
            {"gl_MaxTessControlInputComponents", getInteger<GL_MAX_TESS_CONTROL_INPUT_COMPONENTS>},
            {"gl_MaxTessControlOutputComponents", getInteger<GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS>},
            {"gl_MaxTessControlTextureImageUnits", getInteger<GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS>},
            {"gl_MaxTessControlUniformComponents", getInteger<GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS>},
            {"gl_MaxTessControlTotalOutputComponents", getInteger<GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS>},

            {"gl_MaxTessControlImageUniforms", getInteger<GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS>},
            {"gl_MaxTessEvaluationImageUniforms", getInteger<GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS>},
            {"gl_MaxTessControlAtomicCounters", getInteger<GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS>},
            {"gl_MaxTessEvaluationAtomicCounters", getInteger<GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS>},
            {"gl_MaxTessControlAtomicCounterBuffers", getInteger<GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS>},
            {"gl_MaxTessEvaluationAtomicCounterBuffers", getInteger<GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS>},

            {"gl_MaxTessEvaluationInputComponents", getInteger<GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS>},
            {"gl_MaxTessEvaluationOutputComponents", getInteger<GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS>},
            {"gl_MaxTessEvaluationTextureImageUnits", getInteger<GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS>},
            {"gl_MaxTessEvaluationUniformComponents", getInteger<GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS>},

            {"gl_MaxTessPatchComponents", getInteger<GL_MAX_TESS_PATCH_COMPONENTS>},

            {"gl_MaxPatchVertices", getInteger<GL_MAX_PATCH_VERTICES>},
            {"gl_MaxTessGenLevel", getInteger<GL_MAX_TESS_GEN_LEVEL>},
        };

        tcu::TestCaseGroup *const tessGroup =
            new tcu::TestCaseGroup(m_testCtx, "tessellation_shader", "GL_EXT_tessellation_shader");
        addChild(tessGroup);

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(intConstants); ndx++)
            tessGroup->addChild(new ShaderBuiltinConstantCase<int>(
                m_context, intConstants[ndx].varName, intConstants[ndx].getValue, "GL_EXT_tessellation_shader"));
    }
}

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