/*-------------------------------------------------------------------------
 * 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 Shader Storage Buffer Object (SSBO) tests.
*//*--------------------------------------------------------------------*/
#include "es31fNegativeSSBOBlockTests.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"
#include "tcuStringTemplate.hpp"
#include "gluShaderProgram.hpp"
#include <map>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace NegativeTestShared
{
namespace
{
using glu::CallLogWrapper;
using tcu::TestLog;
using namespace glw;
namespace args
{
enum ArgMember
{
    ARGMEMBER_FORMAT = 0,
    ARGMEMBER_BINDING_POINT,
    ARGMEMBER_MATRIX_ORDER,
    ARGMEMBER_MEMBER_TYPE,
    ARGMEMBER_NAME,
    ARGMEMBER_FIXED_ARRAY,
    ARGMEMBER_VARIABLE_ARRAY,
    ARGMEMBER_REORDER
};

// key pair ssbo arg data
struct SsboArgData
{
    ArgMember member;
    std::string data;

    SsboArgData(const ArgMember &member_, const std::string &data_)
    {
        member = member_;
        data   = data_;
    }
};

// class which manages string based argument used to build varying ssbo interface blocks and members
class SsboArgs
{
public:
    SsboArgs(const std::string version, tcu::TestLog &log);

    void setSingleValue(const SsboArgData argData);
    bool setAllValues(const std::vector<SsboArgData> argDataList);

    bool getMemberReorder(void) const;

    void resetValues(void);

    std::map<std::string, std::string> populateArgsMap(void) const;

private:
    std::string m_negativeContextVersion;
    std::string m_stdFormat;
    std::string m_bindingPoint;
    std::string m_matrixOrder;
    std::string m_memberType;
    std::string m_memberName;
    std::string m_memberFixedArrayerName;
    std::string m_memberVariableArray;
    bool m_memberReorder;
    int m_numberMembers;
    tcu::TestLog &m_testLog;

    void setDefaultValues(void);
};

//constructor which ensure a proper context is passed into the struct
SsboArgs::SsboArgs(const std::string version, tcu::TestLog &log)
    : m_negativeContextVersion(version)
    , m_numberMembers(8)
    , m_testLog(log)
{
    setDefaultValues();
}

void SsboArgs::setSingleValue(const SsboArgData argData)
{
    std::string message;

    switch (argData.member)
    {
    case ARGMEMBER_FORMAT:
        m_stdFormat = argData.data;
        return;
    case ARGMEMBER_BINDING_POINT:
        m_bindingPoint = argData.data;
        return;
    case ARGMEMBER_MATRIX_ORDER:
        m_matrixOrder = argData.data;
        return;
    case ARGMEMBER_MEMBER_TYPE:
        m_memberType = argData.data;
        return;
    case ARGMEMBER_NAME:
        m_memberName = argData.data;
        return;
    case ARGMEMBER_FIXED_ARRAY:
        m_memberFixedArrayerName = argData.data;
        return;
    case ARGMEMBER_VARIABLE_ARRAY:
        m_memberVariableArray = argData.data;
        return;
    case ARGMEMBER_REORDER:
        if (argData.data == "true")
        {
            m_memberReorder = true;
        }
        return;
    default:
        message = "auto loop argument data member not recognised.";
        m_testLog << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;
    }
}

bool SsboArgs::setAllValues(const std::vector<SsboArgData> argDataList)
{
    std::string message;

    if ((argDataList.size() == 0) || (argDataList.size() > (size_t)m_numberMembers))
    {
        message = "set of args does not match the number of args struct changeable members.";
        m_testLog << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;

        return false;
    }
    else
    {
        for (unsigned int idx = 0; idx < argDataList.size(); idx++)
        {
            setSingleValue(argDataList[idx]);
        }
    }

    return true;
}

bool SsboArgs::getMemberReorder(void) const
{
    return m_memberReorder;
}

void SsboArgs::resetValues(void)
{
    setDefaultValues();
}

//converts SsboArgs member variable into a map object to be used by tcu::StringTemplate
std::map<std::string, std::string> SsboArgs::populateArgsMap(void) const
{
    std::map<std::string, std::string> argsMap;

    // key placeholders located at specific points in the ssbo block
    argsMap["NEGATIVE_CONTEXT_VERSION"] = m_negativeContextVersion;
    argsMap["STD_FORMAT"]               = m_stdFormat;
    argsMap["BINDING_POINT"]            = m_bindingPoint;
    argsMap["MATRIX_ORDER"]             = m_matrixOrder;
    argsMap["MEMBER_TYPE"]              = m_memberType;
    argsMap["MEMBER_NAME"]              = m_memberName;
    argsMap["MEMBER_FIXED_ARRAY"]       = m_memberFixedArrayerName;
    argsMap["MEMBER_VARIABLE_ARRAY"]    = m_memberVariableArray;

    return argsMap;
}

// default values i.e. same shader template
void SsboArgs::setDefaultValues(void)
{
    m_stdFormat              = "std430";
    m_bindingPoint           = "0";
    m_matrixOrder            = "column_major";
    m_memberType             = "int";
    m_memberName             = "matrix";
    m_memberFixedArrayerName = "10";
    m_memberVariableArray    = "";
    m_memberReorder          = false;
}
} // namespace args

std::string generateVaryingSSBOShader(const glw::GLenum shaderType, const args::SsboArgs &args, tcu::TestLog &log)
{
    std::map<std::string, std::string> argsMap;
    std::ostringstream source;
    std::string sourceString;
    std::stringstream ssboString;
    std::string message;

    if (args.getMemberReorder())
    {
        ssboString << "    mediump vec4 array_1[${MEMBER_FIXED_ARRAY}];\n"
                   << "    highp mat4 ${MEMBER_NAME};\n"
                   << "    lowp ${MEMBER_TYPE} data;\n"
                   << "    mediump float array_2[${MEMBER_VARIABLE_ARRAY}];\n";
    }
    else
    {
        ssboString << "    lowp ${MEMBER_TYPE} data;\n"
                   << "    highp mat4 ${MEMBER_NAME};\n"
                   << "    mediump vec4 array_1[${MEMBER_FIXED_ARRAY}];\n"
                   << "    mediump float array_2[${MEMBER_VARIABLE_ARRAY}];\n";
    }

    argsMap = args.populateArgsMap();

    switch (shaderType)
    {
    case GL_VERTEX_SHADER:
    {
        source << "${NEGATIVE_CONTEXT_VERSION}\n"
               << "layout (location = 0) in highp vec4 position;\n"
               << "layout (location = 1) in mediump vec4 colour;\n"
               << "out mediump vec4 vertex_colour;\n"
               << "layout (${STD_FORMAT}, binding = ${BINDING_POINT}, ${MATRIX_ORDER}) buffer ssbo_block\n"
               << "{\n";

        source << ssboString.str();

        source << "} ssbo;\n"
               << "void main()\n"
               << "{\n"
               << "    mediump vec4 variable;\n"
               << "    gl_Position = ssbo.${MEMBER_NAME} * position;\n"
               << "    for (int idx = 0; idx < ${MEMBER_FIXED_ARRAY}; idx++)\n"
               << "    {\n"
               << "        variable += ssbo.array_1[idx];\n"
               << "    }\n"
               << "    vertex_colour = colour + variable;\n"
               << "}\n";

        sourceString = source.str();
        sourceString = tcu::StringTemplate(sourceString).specialize(argsMap);

        return sourceString;
    }

    case GL_FRAGMENT_SHADER:
    {
        source << "${NEGATIVE_CONTEXT_VERSION}\n"
               << "in mediump vec4 vertex_colour;\n"
               << "layout (location = 0) out mediump vec4 fragment_colour;\n"
               << "layout (${STD_FORMAT}, binding = ${BINDING_POINT}, ${MATRIX_ORDER}) buffer ssbo_block\n"
               << "{\n";

        source << ssboString.str();

        source << "} ssbo;\n"
               << "void main()\n"
               << "{\n"
               << "    mediump vec4 variable;\n"
               << "    variable * ssbo.${MEMBER_NAME};\n"
               << "    for (int idx = 0; idx < ${MEMBER_FIXED_ARRAY}; idx++)\n"
               << "    {\n"
               << "        variable += ssbo.array_1[idx];\n"
               << "    }\n"
               << "    fragment_colour = vertex_colour + variable;\n"
               << "}\n";

        sourceString = source.str();
        sourceString = tcu::StringTemplate(sourceString).specialize(argsMap);

        return sourceString;
    }

    case GL_GEOMETRY_SHADER:
    {
        // TODO:
        return sourceString;
    }

    case GL_TESS_CONTROL_SHADER:
    {
        // TODO:
        return sourceString;
    }

    case GL_TESS_EVALUATION_SHADER:
    {
        // TODO:
        return sourceString;
    }

    case GL_COMPUTE_SHADER:
    {
        // TODO:
        return sourceString;
    }

    default:
    {
        message = "shader type not recognised.";
        log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;
    }
    }

    return std::string();
}

void logProgramInfo(NegativeTestContext &ctx, GLint program)
{
    GLint maxLength = 0;
    std::string message;
    tcu::TestLog &log = ctx.getLog();

    ctx.glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);

    message = "Program log:";
    log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;

    if (maxLength == 0)
    {
        message = "No available info log.";
        log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;
        return;
    }

    std::vector<GLchar> infoLog(maxLength);
    ctx.glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);

    std::string programLogMessage(&infoLog[0], maxLength);
    log << tcu::TestLog::Message << programLogMessage << tcu::TestLog::EndMessage;
}

void ssbo_block_matching(NegativeTestContext &ctx)
{
    const bool isES32              = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const glu::GLSLVersion version = isES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    tcu::TestLog &log              = ctx.getLog();
    std::string message;
    std::string versionString(glu::getGLSLVersionDeclaration(version));
    args::SsboArgs ssboArgs(versionString, log);
    GLint shaderVertexGL;
    std::string shaderVertexString;
    const char *shaderVertexCharPtr;

    // List of arguments used to create varying ssbo objects in the fragment shader
    const args::SsboArgData argDataArrayFrag[] = {args::SsboArgData(args::ARGMEMBER_FORMAT, "std140"),
                                                  args::SsboArgData(args::ARGMEMBER_BINDING_POINT, "10"),
                                                  args::SsboArgData(args::ARGMEMBER_MATRIX_ORDER, "row_major"),
                                                  args::SsboArgData(args::ARGMEMBER_MEMBER_TYPE, "vec2"),
                                                  args::SsboArgData(args::ARGMEMBER_NAME, "name_changed"),
                                                  args::SsboArgData(args::ARGMEMBER_FIXED_ARRAY, "20"),
                                                  args::SsboArgData(args::ARGMEMBER_VARIABLE_ARRAY, "5"),
                                                  args::SsboArgData(args::ARGMEMBER_REORDER, "true")};
    std::vector<args::SsboArgData> argDataVectorFrag(
        argDataArrayFrag, argDataArrayFrag + sizeof(argDataArrayFrag) / sizeof(argDataArrayFrag[0]));

    // create default vertex shader
    shaderVertexString  = generateVaryingSSBOShader(GL_VERTEX_SHADER, ssboArgs, log);
    shaderVertexCharPtr = shaderVertexString.c_str();
    shaderVertexGL      = ctx.glCreateShader(GL_VERTEX_SHADER);

    // log
    message = shaderVertexString;
    log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;

    // compile
    ctx.glShaderSource(shaderVertexGL, 1, &shaderVertexCharPtr, DE_NULL);
    ctx.glCompileShader(shaderVertexGL);

    for (std::size_t idx = 0; idx < argDataVectorFrag.size(); ++idx)
    {
        GLint linkStatus = -1;
        GLint program;
        GLint shaderFragmentGL;
        std::string shaderFragmentString;
        const char *shaderFragmentCharPtr;

        ctx.beginSection("Multiple shaders created using SSBO's sharing the same name but not matching layouts");

        program = ctx.glCreateProgram();

        // reset args to default and make a single change
        ssboArgs.resetValues();
        ssboArgs.setSingleValue(argDataVectorFrag[idx]);

        // create fragment shader
        shaderFragmentString  = generateVaryingSSBOShader(GL_FRAGMENT_SHADER, ssboArgs, log);
        shaderFragmentCharPtr = shaderFragmentString.c_str();
        shaderFragmentGL      = ctx.glCreateShader(GL_FRAGMENT_SHADER);

        // log
        message = shaderFragmentString;
        log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;

        // compile
        ctx.glShaderSource(shaderFragmentGL, 1, &shaderFragmentCharPtr, DE_NULL);
        ctx.glCompileShader(shaderFragmentGL);

        // attach shaders to program and attempt to link
        ctx.glAttachShader(program, shaderVertexGL);
        ctx.glAttachShader(program, shaderFragmentGL);
        ctx.glLinkProgram(program);
        ctx.glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

        logProgramInfo(ctx, program);

        if (linkStatus == GL_TRUE)
        {
            ctx.fail("Program should not have linked");
        }

        // clean up resources
        ctx.glDeleteShader(shaderFragmentGL);
        ctx.glDeleteProgram(program);

        ctx.endSection();
    }

    // clean up default resources
    ctx.glDeleteShader(shaderVertexGL);
}

void ssbo_block_shared_qualifier(NegativeTestContext &ctx)
{
    const bool isES32              = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const glu::GLSLVersion version = isES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    tcu::TestLog &log              = ctx.getLog();
    std::string message;
    std::string versionString(glu::getGLSLVersionDeclaration(version));
    args::SsboArgs ssboArgs(versionString, log);
    bool result;
    GLint shaderVertexGL;
    std::string shaderVertexString;
    const char *shaderVertexCharPtr;

    // default args used in vertex shader ssbo
    const args::SsboArgData argDataArrayVert[] = {args::SsboArgData(args::ARGMEMBER_FORMAT, "shared"),
                                                  args::SsboArgData(args::ARGMEMBER_BINDING_POINT, "0"),
                                                  args::SsboArgData(args::ARGMEMBER_MATRIX_ORDER, "column_major"),
                                                  args::SsboArgData(args::ARGMEMBER_FIXED_ARRAY, "10"),
                                                  args::SsboArgData(args::ARGMEMBER_VARIABLE_ARRAY, "10"),
                                                  args::SsboArgData(args::ARGMEMBER_REORDER, "false")};
    std::vector<args::SsboArgData> argDataVectorVert(
        argDataArrayVert, argDataArrayVert + sizeof(argDataArrayVert) / sizeof(argDataArrayVert[0]));

    // args changed in fragment shader ssbo
    const args::SsboArgData argDataArrayFrag[] = {args::SsboArgData(args::ARGMEMBER_MATRIX_ORDER, "row_major"),
                                                  args::SsboArgData(args::ARGMEMBER_VARIABLE_ARRAY, ""),
                                                  args::SsboArgData(args::ARGMEMBER_FIXED_ARRAY, "20")};
    std::vector<args::SsboArgData> argDataVectorFrag(
        argDataArrayFrag, argDataArrayFrag + sizeof(argDataArrayFrag) / sizeof(argDataArrayFrag[0]));

    // set default vertex ssbo args
    result = ssboArgs.setAllValues(argDataVectorVert);

    if (result == false)
    {
        message = "Invalid use of args.setAllValues()";
        log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;
        return;
    }

    // create default vertex shader
    shaderVertexString  = generateVaryingSSBOShader(GL_VERTEX_SHADER, ssboArgs, log);
    shaderVertexCharPtr = shaderVertexString.c_str();
    shaderVertexGL      = ctx.glCreateShader(GL_VERTEX_SHADER);

    // log
    message = shaderVertexString;
    log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;

    // compile
    ctx.glShaderSource(shaderVertexGL, 1, &shaderVertexCharPtr, DE_NULL);
    ctx.glCompileShader(shaderVertexGL);

    for (std::size_t idx = 0; idx < argDataVectorFrag.size(); idx++)
    {
        GLint linkStatus = -1;
        GLint program;
        GLint shaderFragmentGL;
        std::string shaderFragmentString;
        const char *shaderFragmentCharPtr;

        ctx.beginSection("Multiple shaders created using SSBO's sharing the same name but not matching layouts");

        program = ctx.glCreateProgram();

        // reset args to default and make a single change
        ssboArgs.setAllValues(argDataVectorVert);
        ssboArgs.setSingleValue(argDataVectorFrag[idx]);

        // create fragment shader
        shaderFragmentString  = generateVaryingSSBOShader(GL_FRAGMENT_SHADER, ssboArgs, log);
        shaderFragmentCharPtr = shaderFragmentString.c_str();
        shaderFragmentGL      = ctx.glCreateShader(GL_FRAGMENT_SHADER);

        // log
        message = shaderFragmentString;
        log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;

        // compile
        ctx.glShaderSource(shaderFragmentGL, 1, &shaderFragmentCharPtr, DE_NULL);
        ctx.glCompileShader(shaderFragmentGL);

        // attach shaders to the program and attempt to link
        ctx.glAttachShader(program, shaderVertexGL);
        ctx.glAttachShader(program, shaderFragmentGL);
        ctx.glLinkProgram(program);
        ctx.glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

        logProgramInfo(ctx, program);

        if (linkStatus == GL_TRUE)
        {
            ctx.fail("Program should not have linked");
        }

        // clean up resources
        ctx.glDeleteShader(shaderFragmentGL);
        ctx.glDeleteProgram(program);

        ctx.endSection();
    }

    // clean up default resources
    ctx.glDeleteShader(shaderVertexGL);
}
} // namespace

std::vector<FunctionContainer> getNegativeSSBOBlockTestFunctions(void)
{
    const FunctionContainer funcs[] = {
        {ssbo_block_matching, "ssbo_block_interface_matching_tests", "Invalid Shader Linkage"},
        {ssbo_block_shared_qualifier, "ssbo_using_shared_qualifier_tests", "Invalid Shader Linkage"},
    };

    return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}
} // namespace NegativeTestShared
} // namespace Functional
} // namespace gles31
} // namespace deqp
