/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2016 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 Negative Tessellation tests.
 *//*--------------------------------------------------------------------*/

#include "es31fNegativeTessellationTests.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"
#include "tcuStringTemplate.hpp"

namespace deqp
{

using std::map;
using std::string;

namespace gles31
{
namespace Functional
{
namespace NegativeTestShared
{

using tcu::TestLog;
using namespace glw;

static const char *vertexShaderSource = "${GLSL_VERSION_STRING}\n"
                                        "${GLSL_PER_VERTEX_OUT}\n"
                                        "void main (void)\n"
                                        "{\n"
                                        "    gl_Position = vec4(0.0);\n"
                                        "}\n";

static const char *fragmentShaderSource = "${GLSL_VERSION_STRING}\n"
                                          "precision mediump float;\n"
                                          "layout(location = 0) out mediump vec4 fragColor;\n"
                                          "\n"
                                          "void main (void)\n"
                                          "{\n"
                                          "    fragColor = vec4(1.0);\n"
                                          "}\n";

static const char *tessControlShaderSource =
    "${GLSL_VERSION_STRING}\n"
    "${GLSL_TESS_EXTENSION_STRING}\n"
    "${GLSL_PER_VERTEX_IN_ARR}\n"
    "${GLSL_PER_VERTEX_OUT_ARR}\n"
    "layout (vertices=3) out;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
    "}\n";

static const char *tessEvalShaderSource = "${GLSL_VERSION_STRING}\n"
                                          "${GLSL_TESS_EXTENSION_STRING}\n"
                                          "${GLSL_PER_VERTEX_IN_ARR}\n"
                                          "${GLSL_PER_VERTEX_OUT}\n"
                                          "layout(triangles) in;\n"
                                          "\n"
                                          "void main()\n"
                                          "{\n"
                                          "    gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position;\n"
                                          "}\n";

static void checkExtensionSupport(NegativeTestContext &ctx, const char *extName)
{
    if (!ctx.getContextInfo().isExtensionSupported(extName))
        throw tcu::NotSupportedError(string(extName) + " not supported");
}

static void checkTessellationSupport(NegativeTestContext &ctx)
{
    if (glu::isContextTypeES(ctx.getRenderContext().getType()))
        checkExtensionSupport(ctx, "GL_EXT_tessellation_shader");
}

// Helper for constructing tessellation pipeline sources.
static glu::ProgramSources makeTessPipelineSources(const std::string &vertexSrc, const std::string &fragmentSrc,
                                                   const std::string &tessCtrlSrc, const std::string &tessEvalSrc)
{
    glu::ProgramSources sources;
    sources.sources[glu::SHADERTYPE_VERTEX].push_back(vertexSrc);
    sources.sources[glu::SHADERTYPE_FRAGMENT].push_back(fragmentSrc);

    if (!tessCtrlSrc.empty())
        sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].push_back(tessCtrlSrc);

    if (!tessEvalSrc.empty())
        sources.sources[glu::SHADERTYPE_TESSELLATION_EVALUATION].push_back(tessEvalSrc);

    return sources;
}

map<string, string> constructSpecializationMap(NegativeTestContext &ctx)
{
    glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(ctx.getRenderContext().getType());
    bool isES31                  = (glslVersion == glu::GLSL_VERSION_310_ES);
    string ext                   = isES31 ? "#extension GL_EXT_tessellation_shader : require" : "";
    return {{"GLSL_VERSION_STRING", getGLSLVersionDeclaration(glslVersion)},
            {"GLSL_TESS_EXTENSION_STRING", ext},
            {"GLSL_PER_VERTEX_OUT", ""}, // needed for GL4.5
            {"GLSL_PER_VERTEX_IN_ARR", ""},
            {"GLSL_PER_VERTEX_OUT_ARR", ""}};
}

// Incomplete active tess shaders
void single_tessellation_stage(NegativeTestContext &ctx)
{
    // this case does not apply to GL4.5
    if (!glu::isContextTypeES(ctx.getRenderContext().getType()))
        return;

    const bool requireTES    = !ctx.getContextInfo().isExtensionSupported("GL_NV_gpu_shader5");
    map<string, string> args = constructSpecializationMap(ctx);

    checkTessellationSupport(ctx);

    {
        glu::ShaderProgram program(
            ctx.getRenderContext(),
            makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                    tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                    tcu::StringTemplate(tessControlShaderSource).specialize(args),
                                    "")); // missing tessEvalShaderSource
        tcu::TestLog &log = ctx.getLog();
        log << program;

        ctx.beginSection("A link error is generated if a non-separable program has a tessellation control shader but "
                         "no tessellation evaluation shader, unless GL_NV_gpu_shader5 is supported.");

        if (requireTES && program.isOk())
            ctx.fail("Program was not expected to link");
        else if (!requireTES && !program.isOk())
            ctx.fail("Program was expected to link");

        ctx.endSection();
    }

    {
        glu::ShaderProgram program(
            ctx.getRenderContext(),
            makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                    tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                    tcu::StringTemplate(tessControlShaderSource).specialize(args),
                                    "") // missing tessEvalShaderSource
                << glu::ProgramSeparable(true));
        tcu::TestLog &log = ctx.getLog();
        log << program;

        if (!program.isOk())
            TCU_THROW(TestError, "failed to build program");

        ctx.glUseProgram(program.getProgram());
        ctx.expectError(GL_NO_ERROR);

        ctx.beginSection("GL_INVALID_OPERATION is generated if current program state has tessellation control shader "
                         "but no tessellation evaluation shader, unless GL_NV_gpu_shader5 is supported.");
        ctx.glDrawArrays(GL_PATCHES, 0, 3);
        ctx.expectError(requireTES ? GL_INVALID_OPERATION : GL_NO_ERROR);
        ctx.endSection();

        ctx.glUseProgram(0);
    }

    {
        glu::ShaderProgram program(ctx.getRenderContext(),
                                   makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                                           tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                                           "", // missing tessControlShaderSource
                                                           tcu::StringTemplate(tessEvalShaderSource).specialize(args)));
        tcu::TestLog &log = ctx.getLog();
        log << program;

        ctx.beginSection("A link error is generated if a non-separable program has a tessellation evaluation shader "
                         "but no tessellation control shader.");

        if (program.isOk())
            ctx.fail("Program was not expected to link");

        ctx.endSection();
    }

    {
        glu::ShaderProgram program(ctx.getRenderContext(),
                                   makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                                           tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                                           "", // missing tessControlShaderSource
                                                           tcu::StringTemplate(tessEvalShaderSource).specialize(args))
                                       << glu::ProgramSeparable(true));
        tcu::TestLog &log = ctx.getLog();
        log << program;

        if (!program.isOk())
            TCU_THROW(TestError, "failed to build program");

        ctx.glUseProgram(program.getProgram());
        ctx.expectError(GL_NO_ERROR);

        ctx.beginSection("GL_INVALID_OPERATION is generated if current program state has tessellation evaluation "
                         "shader but no tessellation control shader.");
        ctx.glDrawArrays(GL_PATCHES, 0, 3);
        ctx.expectError(GL_INVALID_OPERATION);
        ctx.endSection();

        ctx.glUseProgram(0);
    }
}

// Complete active tess shaders invalid primitive mode
void invalid_primitive_mode(NegativeTestContext &ctx)
{
    checkTessellationSupport(ctx);

    map<string, string> args = constructSpecializationMap(ctx);
    glu::ShaderProgram program(ctx.getRenderContext(),
                               makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                                       tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                                       tcu::StringTemplate(tessControlShaderSource).specialize(args),
                                                       tcu::StringTemplate(tessEvalShaderSource).specialize(args)));
    tcu::TestLog &log = ctx.getLog();
    log << program;

    ctx.glUseProgram(program.getProgram());
    ctx.expectError(GL_NO_ERROR);

    ctx.beginSection(
        "GL_INVALID_OPERATION is generated if tessellation is active and primitive mode is not GL_PATCHES.");
    ctx.glDrawArrays(GL_TRIANGLES, 0, 3);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.glUseProgram(0);
}

void tessellation_not_active(NegativeTestContext &ctx)
{
    checkTessellationSupport(ctx);

    const glw::GLenum tessErr =
        ctx.getContextInfo().isExtensionSupported("GL_NV_gpu_shader5") ? GL_NO_ERROR : GL_INVALID_OPERATION;
    map<string, string> args = constructSpecializationMap(ctx);
    glu::ShaderProgram program(ctx.getRenderContext(),
                               makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                                       tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                                       "",   // missing tessControlShaderSource
                                                       "")); // missing tessEvalShaderSource
    tcu::TestLog &log = ctx.getLog();
    log << program;

    GLuint vao = 0;
    if (!glu::isContextTypeES(ctx.getRenderContext().getType()))
    {
        ctx.glGenVertexArrays(1, &vao);
        ctx.glBindVertexArray(vao);
    }

    ctx.glUseProgram(program.getProgram());
    ctx.expectError(GL_NO_ERROR);

    ctx.beginSection("GL_INVALID_OPERATION is generated if tessellation is not active and primitive mode is "
                     "GL_PATCHES, unless GL_NV_gpu_shader5 is supported.");
    ctx.glDrawArrays(GL_PATCHES, 0, 3);
    ctx.expectError(tessErr);
    ctx.endSection();

    ctx.glUseProgram(0);

    if (vao)
        ctx.glDeleteVertexArrays(1, &vao);
}

void invalid_program_state(NegativeTestContext &ctx)
{
    checkTessellationSupport(ctx);

    const glu::RenderContext &rc = ctx.getRenderContext();
    map<string, string> args     = constructSpecializationMap(ctx);

    // for gl4.5 we need to add per vertex sections
    if (!glu::isContextTypeES(rc.getType()))
    {
        args["GLSL_PER_VERTEX_OUT"]     = "out gl_PerVertex { vec4 gl_Position; };\n";
        args["GLSL_PER_VERTEX_IN_ARR"]  = "in gl_PerVertex { vec4 gl_Position; } gl_in[];\n";
        args["GLSL_PER_VERTEX_OUT_ARR"] = "out gl_PerVertex { vec4 gl_Position; } gl_out[];\n";
    }

    glu::FragmentSource frgSource(tcu::StringTemplate(fragmentShaderSource).specialize(args));
    glu::TessellationControlSource tessCtrlSource(tcu::StringTemplate(tessControlShaderSource).specialize(args));
    glu::TessellationEvaluationSource tessEvalSource(tcu::StringTemplate(tessEvalShaderSource).specialize(args));

    glu::ProgramPipeline pipeline(rc);

    glu::ShaderProgram fragProgram(rc, glu::ProgramSources() << glu::ProgramSeparable(true) << frgSource);
    glu::ShaderProgram tessCtrlProgram(rc, glu::ProgramSources() << glu::ProgramSeparable(true) << tessCtrlSource);
    glu::ShaderProgram tessEvalProgram(rc, glu::ProgramSources() << glu::ProgramSeparable(true) << tessEvalSource);

    tcu::TestLog &log = ctx.getLog();
    log << fragProgram << tessCtrlProgram << tessEvalProgram;

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

    ctx.glBindProgramPipeline(pipeline.getPipeline());
    ctx.expectError(GL_NO_ERROR);

    ctx.glUseProgramStages(pipeline.getPipeline(), GL_FRAGMENT_SHADER_BIT, fragProgram.getProgram());
    ctx.glUseProgramStages(pipeline.getPipeline(), GL_TESS_CONTROL_SHADER_BIT, tessCtrlProgram.getProgram());
    ctx.glUseProgramStages(pipeline.getPipeline(), GL_TESS_EVALUATION_SHADER_BIT, tessEvalProgram.getProgram());
    ctx.expectError(GL_NO_ERROR);

    ctx.beginSection("GL_INVALID_OPERATION is generated if tessellation is active and vertex shader is missing.");
    ctx.glDrawArrays(GL_PATCHES, 0, 3);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.glBindProgramPipeline(0);
    ctx.expectError(GL_NO_ERROR);
}

void tessellation_control_invalid_vertex_count(NegativeTestContext &ctx)
{
    checkTessellationSupport(ctx);

    const char *const tessControlVertLimitSource =
        "${GLSL_VERSION_STRING}\n"
        "${GLSL_TESS_EXTENSION_STRING}\n"
        "layout (vertices=${GL_MAX_PATCH_LIMIT}) out;\n"
        "void main()\n"
        "{\n"
        "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
        "}\n";

    map<string, string> args = constructSpecializationMap(ctx);
    int maxPatchVertices     = 0;

    ctx.beginSection("Output vertex count exceeds GL_MAX_PATCH_VERTICES.");
    ctx.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
    ctx.expectError(GL_NO_ERROR);

    std::ostringstream oss;
    oss << (maxPatchVertices + 1);
    args["GL_MAX_PATCH_LIMIT"] = oss.str();

    glu::ShaderProgram program(ctx.getRenderContext(),
                               makeTessPipelineSources(tcu::StringTemplate(vertexShaderSource).specialize(args),
                                                       tcu::StringTemplate(fragmentShaderSource).specialize(args),
                                                       tcu::StringTemplate(tessControlVertLimitSource).specialize(args),
                                                       tcu::StringTemplate(tessEvalShaderSource).specialize(args)));
    tcu::TestLog &log = ctx.getLog();
    log << program;

    bool testFailed = program.getProgramInfo().linkOk;

    if (testFailed)
        ctx.fail("Program was not expected to link");

    ctx.endSection();
}

void invalid_get_programiv(NegativeTestContext &ctx)
{
    checkTessellationSupport(ctx);

    GLuint program  = ctx.glCreateProgram();
    GLint params[1] = {0};

    ctx.beginSection("GL_INVALID_OPERATION is generated if GL_TESS_CONTROL_OUTPUT_VERTICES is queried for a program "
                     "which has not been linked properly.");
    ctx.glGetProgramiv(program, GL_TESS_CONTROL_OUTPUT_VERTICES, &params[0]);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.beginSection("GL_INVALID_OPERATION is generated if GL_TESS_GEN_MODE is queried for a program which has not "
                     "been linked properly.");
    ctx.glGetProgramiv(program, GL_TESS_GEN_MODE, &params[0]);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.beginSection("GL_INVALID_OPERATION is generated if GL_TESS_GEN_SPACING is queried for a program which has not "
                     "been linked properly.");
    ctx.glGetProgramiv(program, GL_TESS_GEN_SPACING, &params[0]);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.beginSection("GL_INVALID_OPERATION is generated if GL_TESS_GEN_VERTEX_ORDER is queried for a program which has "
                     "not been linked properly.");
    ctx.glGetProgramiv(program, GL_TESS_GEN_VERTEX_ORDER, &params[0]);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.beginSection("GL_INVALID_OPERATION is generated if GL_TESS_GEN_POINT_MODE is queried for a program which has "
                     "not been linked properly.");
    ctx.glGetProgramiv(program, GL_TESS_GEN_POINT_MODE, &params[0]);
    ctx.expectError(GL_INVALID_OPERATION);
    ctx.endSection();

    ctx.glDeleteProgram(program);
}

void invalid_patch_parameteri(NegativeTestContext &ctx)
{
    checkTessellationSupport(ctx);

    ctx.beginSection("GL_INVALID_ENUM is generated if pname is not GL_PATCH_VERTICES.");
    ctx.glPatchParameteri(-1, 1);
    ctx.expectError(GL_INVALID_ENUM);
    ctx.endSection();

    ctx.beginSection("GL_INVALID_VALUE is generated if value is less than or equal to zero.");
    ctx.glPatchParameteri(GL_PATCH_VERTICES, 0);
    ctx.expectError(GL_INVALID_VALUE);
    ctx.endSection();

    int maxPatchVertices = 0;
    ctx.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
    ctx.expectError(GL_NO_ERROR);

    ctx.beginSection("GL_INVALID_VALUE is generated if value is greater than GL_MAX_PATCH_VERTICES.");
    ctx.glPatchParameteri(GL_PATCH_VERTICES, maxPatchVertices + 1);
    ctx.expectError(GL_INVALID_VALUE);
    ctx.endSection();
}

std::vector<FunctionContainer> getNegativeTessellationTestFunctions(void)
{
    const FunctionContainer funcs[] = {
        {single_tessellation_stage, "single_tessellation_stage",
         "Invalid program state with single tessellation stage"},
        {invalid_primitive_mode, "invalid_primitive_mode", "Invalid primitive mode when tessellation is active"},
        {tessellation_not_active, "tessellation_not_active", "Use of GL_PATCHES when tessellation is not active"},
        {invalid_program_state, "invalid_program_state",
         "Invalid program state when tessellation active but no vertex shader present"},
        {invalid_get_programiv, "get_programiv", "Invalid glGetProgramiv() usage"},
        {invalid_patch_parameteri, "invalid_program_queries", "Invalid glPatchParameteri() usage"},
        {tessellation_control_invalid_vertex_count, "tessellation_control_invalid_vertex_count",
         "Exceed vertex count limit in tessellation control shader"},
    };

    return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}

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