/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2015 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 Program Pipeline State Query tests.
 *//*--------------------------------------------------------------------*/

#include "es31fProgramPipelineStateQueryTests.hpp"
#include "es31fInfoLogQueryShared.hpp"
#include "glsStateQueryUtil.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using namespace gls::StateQueryUtil;

static const char *getVerifierSuffix(QueryType type)
{
    switch (type)
    {
    case QUERY_PIPELINE_INTEGER:
        return "get_program_pipelineiv";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

static const char *const s_vertexSource =
    "#version 310 es\n"
    "out highp vec4 v_color;\n"
    "void main()\n"
    "{\n"
    "    gl_Position = vec4(float(gl_VertexID) * 0.5, float(gl_VertexID+1) * 0.5, 0.0, 1.0);\n"
    "    v_color = vec4(float(gl_VertexID), 1.0, 0.0, 1.0);\n"
    "}\n";
static const char *const s_fragmentSource = "#version 310 es\n"
                                            "in highp vec4 v_color;\n"
                                            "layout(location=0) out highp vec4 o_color;\n"
                                            "void main()\n"
                                            "{\n"
                                            "    o_color = v_color;\n"
                                            "}\n";
static const char *const s_computeSource  = "#version 310 es\n"
                                            "layout (local_size_x = 1, local_size_y = 1) in;\n"
                                            "layout(binding = 0) buffer Output\n"
                                            "{\n"
                                            "    highp float val;\n"
                                            "} sb_out;\n"
                                            "\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    sb_out.val = 1.0;\n"
                                            "}\n";

class ActiveProgramCase : public TestCase
{
public:
    ActiveProgramCase(Context &context, QueryType verifier, const char *name, const char *desc);
    IterateResult iterate(void);

private:
    const QueryType m_verifier;
};

ActiveProgramCase::ActiveProgramCase(Context &context, QueryType verifier, const char *name, const char *desc)
    : TestCase(context, name, desc)
    , m_verifier(verifier)
{
}

ActiveProgramCase::IterateResult ActiveProgramCase::iterate(void)
{
    const glu::ShaderProgram vtxProgram(m_context.getRenderContext(), glu::ProgramSources()
                                                                          << glu::ProgramSeparable(true)
                                                                          << glu::VertexSource(s_vertexSource));
    const glu::ShaderProgram frgProgram(m_context.getRenderContext(), glu::ProgramSources()
                                                                          << glu::ProgramSeparable(true)
                                                                          << glu::FragmentSource(s_fragmentSource));
    const glu::ProgramPipeline pipeline(m_context.getRenderContext());
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "VtxProg", "Vertex program");
        m_testCtx.getLog() << vtxProgram;
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "FrgProg", "Fragment program");
        m_testCtx.getLog() << frgProgram;
    }

    if (!vtxProgram.isOk() || !frgProgram.isOk())
        throw tcu::TestError("failed to build program");

    gl.enableLogging(true);
    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    gl.glBindProgramPipeline(pipeline.getPipeline());
    gl.glUseProgramStages(pipeline.getPipeline(), GL_VERTEX_SHADER_BIT, vtxProgram.getProgram());
    gl.glUseProgramStages(pipeline.getPipeline(), GL_FRAGMENT_SHADER_BIT, frgProgram.getProgram());
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen pipeline");
    gl.glBindProgramPipeline(0);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "unbind pipeline");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");
        verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), GL_ACTIVE_PROGRAM, 0, m_verifier);
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Set", "Set");

        gl.glActiveShaderProgram(pipeline.getPipeline(), frgProgram.getProgram());
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen pipeline");
        verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), GL_ACTIVE_PROGRAM, (int)frgProgram.getProgram(),
                                   m_verifier);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class PipelineProgramCase : public TestCase
{
public:
    PipelineProgramCase(Context &context, QueryType verifier, const char *name, const char *desc, glw::GLenum stage);
    IterateResult iterate(void);

private:
    const QueryType m_verifier;
    const glw::GLenum m_targetStage;
};

PipelineProgramCase::PipelineProgramCase(Context &context, QueryType verifier, const char *name, const char *desc,
                                         glw::GLenum stage)
    : TestCase(context, name, desc)
    , m_verifier(verifier)
    , m_targetStage(stage)
{
}

PipelineProgramCase::IterateResult PipelineProgramCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    const int stageBit = (m_targetStage == GL_VERTEX_SHADER)   ? (GL_VERTEX_SHADER_BIT) :
                         (m_targetStage == GL_FRAGMENT_SHADER) ? (GL_FRAGMENT_SHADER_BIT) :
                                                                 (GL_COMPUTE_SHADER_BIT);
    glu::ProgramSources sources;

    if (m_targetStage == GL_VERTEX_SHADER)
        sources << glu::ProgramSeparable(true) << glu::VertexSource(s_vertexSource);
    else if (m_targetStage == GL_FRAGMENT_SHADER)
        sources << glu::ProgramSeparable(true) << glu::FragmentSource(s_fragmentSource);
    else if (m_targetStage == GL_COMPUTE_SHADER)
        sources << glu::ProgramSeparable(true) << glu::ComputeSource(s_computeSource);
    else
        DE_ASSERT(false);

    gl.enableLogging(true);

    {
        glu::ShaderProgram program(m_context.getRenderContext(), sources);

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "program", "Program");
            m_testCtx.getLog() << program;
        }

        if (!program.isOk())
            throw tcu::TestError("failed to build program");

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");
            glu::ProgramPipeline pipeline(m_context.getRenderContext());

            gl.glBindProgramPipeline(pipeline.getPipeline());
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup pipeline");

            verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), m_targetStage, 0, m_verifier);
        }

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Set", "Set");
            glu::ProgramPipeline pipeline(m_context.getRenderContext());

            gl.glBindProgramPipeline(pipeline.getPipeline());
            gl.glUseProgramStages(pipeline.getPipeline(), stageBit, program.getProgram());
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup pipeline");

            verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), m_targetStage, program.getProgram(),
                                       m_verifier);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class ValidateStatusCase : public TestCase
{
public:
    ValidateStatusCase(Context &context, QueryType verifier, const char *name, const char *desc);
    IterateResult iterate(void);

private:
    const QueryType m_verifier;
};

ValidateStatusCase::ValidateStatusCase(Context &context, QueryType verifier, const char *name, const char *desc)
    : TestCase(context, name, desc)
    , m_verifier(verifier)
{
}

ValidateStatusCase::IterateResult ValidateStatusCase::iterate(void)
{
    glu::ShaderProgram vtxProgram(m_context.getRenderContext(), glu::ProgramSources()
                                                                    << glu::ProgramSeparable(true)
                                                                    << glu::VertexSource(s_vertexSource));
    glu::ShaderProgram frgProgram(m_context.getRenderContext(), glu::ProgramSources()
                                                                    << glu::ProgramSeparable(true)
                                                                    << glu::FragmentSource(s_fragmentSource));
    glu::ProgramPipeline pipeline(m_context.getRenderContext());
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "VtxProg", "Vertex program");
        m_testCtx.getLog() << vtxProgram;
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "FrgProg", "Fragment program");
        m_testCtx.getLog() << frgProgram;
    }

    if (!vtxProgram.isOk() || !frgProgram.isOk())
        throw tcu::TestError("failed to build program");

    gl.enableLogging(true);

    gl.glBindProgramPipeline(pipeline.getPipeline());
    gl.glUseProgramStages(pipeline.getPipeline(), GL_VERTEX_SHADER_BIT, vtxProgram.getProgram());
    gl.glUseProgramStages(pipeline.getPipeline(), GL_FRAGMENT_SHADER_BIT, frgProgram.getProgram());
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen pipeline");
    gl.glBindProgramPipeline(0);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "unbind pipeline");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");
        verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), GL_VALIDATE_STATUS, 0, m_verifier);
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Set", "Validate");

        gl.glValidateProgramPipeline(pipeline.getPipeline());
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen pipeline");
        verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), GL_VALIDATE_STATUS, GL_TRUE, m_verifier);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class InfoLogCase : public TestCase
{
public:
    InfoLogCase(Context &context, const char *name, const char *desc);
    IterateResult iterate(void);
};

InfoLogCase::InfoLogCase(Context &context, const char *name, const char *desc) : TestCase(context, name, desc)
{
}

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

    static const char *const s_incompatibleFragmentSource = "#version 310 es\n"
                                                            "in mediump vec2 v_colorB;\n"
                                                            "in mediump vec2 v_colorC;\n"
                                                            "layout(location=0) out highp vec4 o_color;\n"
                                                            "void main()\n"
                                                            "{\n"
                                                            "    o_color = v_colorB.xxyy + v_colorC.yyxy;\n"
                                                            "}\n";

    glu::ShaderProgram vtxProgram(m_context.getRenderContext(), glu::ProgramSources()
                                                                    << glu::ProgramSeparable(true)
                                                                    << glu::VertexSource(s_vertexSource));
    glu::ShaderProgram frgProgram(m_context.getRenderContext(),
                                  glu::ProgramSources() << glu::ProgramSeparable(true)
                                                        << glu::FragmentSource(s_incompatibleFragmentSource));
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "VtxProg", "Vertex program");
        m_testCtx.getLog() << vtxProgram;
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "FrgProg", "Fragment program");
        m_testCtx.getLog() << frgProgram;
    }

    if (!vtxProgram.isOk() || !frgProgram.isOk())
        throw tcu::TestError("failed to build program");

    gl.enableLogging(true);

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");
        glu::ProgramPipeline pipeline(m_context.getRenderContext());
        std::string buf(3, 'X');
        int written = -1;

        verifyStatePipelineInteger(result, gl, pipeline.getPipeline(), GL_INFO_LOG_LENGTH, 0, QUERY_PIPELINE_INTEGER);

        gl.glGetProgramPipelineInfoLog(pipeline.getPipeline(), 2, &written, &buf[0]);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "query log");

        if (written == -1)
            result.fail("'length' was not written to");
        else if (written != 0)
            result.fail("'length' was not 0");
        else if (buf[0] != '\0')
            result.fail("log was not 0-sized null-terminated string");
        else if (buf[1] != 'X' || buf[2] != 'X')
            result.fail("buffer after returned length modified");
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "ValidationFail", "Failed validation");
        glu::ProgramPipeline pipeline(m_context.getRenderContext());
        StateQueryMemoryWriteGuard<glw::GLint> logLen;

        gl.glBindProgramPipeline(pipeline.getPipeline());
        gl.glUseProgramStages(pipeline.getPipeline(), GL_VERTEX_SHADER_BIT, vtxProgram.getProgram());
        gl.glUseProgramStages(pipeline.getPipeline(), GL_FRAGMENT_SHADER_BIT, frgProgram.getProgram());
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen pipeline");

        gl.glBindProgramPipeline(0);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "unbind pipeline");
        gl.glValidateProgramPipeline(pipeline.getPipeline());
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen pipeline");

        gl.glGetProgramPipelineiv(pipeline.getPipeline(), GL_INFO_LOG_LENGTH, &logLen);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "get INFO_LOG_LENGTH");

        if (logLen.verifyValidity(result))
            verifyInfoLogQuery(result, gl, logLen, pipeline.getPipeline(),
                               &glu::CallLogWrapper::glGetProgramPipelineInfoLog, "glGetProgramPipelineInfoLog");
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

} // namespace

ProgramPipelineStateQueryTests::ProgramPipelineStateQueryTests(Context &context)
    : TestCaseGroup(context, "program_pipeline", "Program Pipeline State Query tests")
{
}

ProgramPipelineStateQueryTests::~ProgramPipelineStateQueryTests(void)
{
}

void ProgramPipelineStateQueryTests::init(void)
{
    static const QueryType verifiers[] = {
        QUERY_PIPELINE_INTEGER,
    };

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

    FOR_EACH_VERIFIER(new ActiveProgramCase(
        m_context, verifier, (std::string("active_program_") + verifierSuffix).c_str(), "Test ACTIVE_PROGRAM"));
    FOR_EACH_VERIFIER(new PipelineProgramCase(m_context, verifier,
                                              (std::string("vertex_shader_") + verifierSuffix).c_str(),
                                              "Test VERTEX_SHADER", GL_VERTEX_SHADER));
    FOR_EACH_VERIFIER(new PipelineProgramCase(m_context, verifier,
                                              (std::string("fragment_shader_") + verifierSuffix).c_str(),
                                              "Test FRAGMENT_SHADER", GL_FRAGMENT_SHADER));
    FOR_EACH_VERIFIER(new PipelineProgramCase(m_context, verifier,
                                              (std::string("compute_shader_") + verifierSuffix).c_str(),
                                              "Test COMPUTE_SHADER", GL_COMPUTE_SHADER));
    FOR_EACH_VERIFIER(new ValidateStatusCase(
        m_context, verifier, (std::string("validate_status_") + verifierSuffix).c_str(), "Test VALIDATE_STATUS"));

#undef FOR_EACH_VERIFIER

    this->addChild(new InfoLogCase(m_context, "info_log", "Test info log"));
}

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