/*-------------------------------------------------------------------------
 * 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 FBO colorbuffer tests.
 *//*--------------------------------------------------------------------*/

#include "es31fFboColorbufferTests.hpp"
#include "es31fFboTestCase.hpp"
#include "es31fFboTestUtil.hpp"

#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"

#include "tcuCommandLine.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRGBA.hpp"
#include "tcuTestLog.hpp"
#include "tcuTextureUtil.hpp"

#include "sglrContextUtil.hpp"

#include "deRandom.hpp"
#include "deString.h"

#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::string;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::TestLog;
using tcu::UVec4;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using namespace FboTestUtil;

const tcu::RGBA MIN_THRESHOLD(12, 12, 12, 12);

static tcu::Vec4 generateRandomColor(de::Random &random)
{
    tcu::Vec4 retVal;

    retVal[0] = random.getFloat();
    retVal[1] = random.getFloat();
    retVal[2] = random.getFloat();
    retVal[3] = 1.0f;

    return retVal;
}

static tcu::CubeFace getCubeFaceFromNdx(int ndx)
{
    switch (ndx)
    {
    case 0:
        return tcu::CUBEFACE_POSITIVE_X;
    case 1:
        return tcu::CUBEFACE_NEGATIVE_X;
    case 2:
        return tcu::CUBEFACE_POSITIVE_Y;
    case 3:
        return tcu::CUBEFACE_NEGATIVE_Y;
    case 4:
        return tcu::CUBEFACE_POSITIVE_Z;
    case 5:
        return tcu::CUBEFACE_NEGATIVE_Z;
    default:
        DE_ASSERT(false);
        return tcu::CUBEFACE_LAST;
    }
}

class FboColorbufferCase : public FboTestCase
{
public:
    FboColorbufferCase(Context &context, const char *name, const char *desc, const uint32_t format)
        : FboTestCase(context, name, desc)
        , m_format(format)
    {
    }

    bool compare(const tcu::Surface &reference, const tcu::Surface &result)
    {
        const tcu::RGBA threshold(tcu::max(getFormatThreshold(m_format), MIN_THRESHOLD));

        m_testCtx.getLog() << TestLog::Message << "Comparing images, threshold: " << threshold << TestLog::EndMessage;

        return tcu::bilinearCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(),
                                    result.getAccess(), threshold, tcu::COMPARE_LOG_RESULT);
    }

protected:
    const uint32_t m_format;
};

class FboColorTex2DCase : public FboColorbufferCase
{
public:
    FboColorTex2DCase(Context &context, const char *name, const char *description, uint32_t texFmt,
                      const IVec2 &texSize)
        : FboColorbufferCase(context, name, description, texFmt)
        , m_texFmt(texFmt)
        , m_texSize(texSize)
    {
    }

protected:
    void preCheck(void)
    {
        checkFormatSupport(m_texFmt);
    }

    void render(tcu::Surface &dst)
    {
        tcu::TextureFormat texFmt      = glu::mapGLInternalFormat(m_texFmt);
        tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(texFmt);

        Texture2DShader texToFboShader(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt),
                                       fmtInfo.valueMax - fmtInfo.valueMin, fmtInfo.valueMin);
        uint32_t texToFboShaderID = getCurrentContext()->createProgram(&texToFboShader);
        uint32_t fbo;
        uint32_t tex;

        // Setup shader
        texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);

        //  Generate fbo
        {
            glu::TransferFormat transferFmt = glu::getTransferFormat(texFmt);
            uint32_t format                 = m_texFmt;
            const IVec2 &size               = m_texSize;

            glGenFramebuffers(1, &fbo);
            glGenTextures(1, &tex);

            glBindTexture(GL_TEXTURE_2D, tex);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, format, size.x(), size.y(), 0, transferFmt.format, transferFmt.dataType,
                         DE_NULL);

            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
            checkError();
            checkFramebufferStatus(GL_FRAMEBUFFER);
        }

        // Render texture to fbo
        {
            const uint32_t format   = GL_RGBA;
            const uint32_t dataType = GL_UNSIGNED_BYTE;
            const int texW          = 128;
            const int texH          = 128;
            uint32_t tmpTex         = 0;
            const IVec2 &viewport   = m_texSize;
            tcu::TextureLevel data(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

            tcu::fillWithComponentGradients(data.getAccess(), Vec4(0.0f), Vec4(1.0f));

            glGenTextures(1, &tmpTex);
            glBindTexture(GL_TEXTURE_2D, tmpTex);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            glViewport(0, 0, viewport.x(), viewport.y());
            sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(1.0f, 1.0f, 0.0f));
        }

        readPixels(dst, 0, 0, getWidth(), getHeight(), texFmt, fmtInfo.lookupScale, fmtInfo.lookupBias);
        checkError();
    }

private:
    uint32_t m_texFmt;
    IVec2 m_texSize;
};

class FboColorTexCubeArrayCase : public FboColorbufferCase
{
public:
    FboColorTexCubeArrayCase(Context &context, const char *name, const char *description, uint32_t texFmt,
                             const IVec3 &texSize)
        : FboColorbufferCase(context, name, description, texFmt)
        , m_texSize(texSize)
    {
        DE_ASSERT(texSize.z() % 6 == 0);
    }

protected:
    void preCheck(void)
    {
        auto ctxType = m_context.getRenderContext().getType();
        if (!glu::contextSupports(ctxType, glu::ApiType::core(4, 5)) &&
            !glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) &&
            !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array"))
            TCU_THROW(
                NotSupportedError,
                "Test requires extension GL_EXT_texture_cube_map_array or a context version equal or higher than 3.2");

        checkFormatSupport(m_format);
    }

    void render(tcu::Surface &dst)
    {
        TestLog &log = m_testCtx.getLog();
        de::Random rnd(deStringHash(getName()) ^ 0xed607a89 ^ m_testCtx.getCommandLine().getBaseSeed());
        tcu::TextureFormat texFmt      = glu::mapGLInternalFormat(m_format);
        tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(texFmt);

        Texture2DShader texToFboShader(DataTypes() << glu::TYPE_SAMPLER_2D, getFragmentOutputType(texFmt),
                                       fmtInfo.valueMax - fmtInfo.valueMin, fmtInfo.valueMin);
        TextureCubeArrayShader arrayTexShader(glu::getSamplerCubeArrayType(texFmt), glu::TYPE_FLOAT_VEC4,
                                              glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType()));

        uint32_t texToFboShaderID = getCurrentContext()->createProgram(&texToFboShader);
        uint32_t arrayTexShaderID = getCurrentContext()->createProgram(&arrayTexShader);

        // Setup textures
        texToFboShader.setUniforms(*getCurrentContext(), texToFboShaderID);
        arrayTexShader.setTexScaleBias(fmtInfo.lookupScale, fmtInfo.lookupBias);

        // Framebuffers.
        std::vector<uint32_t> fbos;
        uint32_t tex;

        {
            glu::TransferFormat transferFmt = glu::getTransferFormat(texFmt);
            bool isFilterable               = glu::isGLInternalColorFormatFilterable(m_format);
            const IVec3 &size               = m_texSize;

            log << TestLog::Message << "Creating a cube map array texture (" << size.x() << "x" << size.y()
                << ", depth: " << size.z() << ")" << TestLog::EndMessage;

            glGenTextures(1, &tex);

            glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
            glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, isFilterable ? GL_LINEAR : GL_NEAREST);
            glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, isFilterable ? GL_LINEAR : GL_NEAREST);
            glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_format, size.x(), size.y(), size.z(), 0, transferFmt.format,
                         transferFmt.dataType, DE_NULL);

            log << TestLog::Message << "Creating a framebuffer object for each layer-face" << TestLog::EndMessage;

            // Generate an FBO for each layer-face
            for (int ndx = 0; ndx < m_texSize.z(); ndx++)
            {
                uint32_t layerFbo;

                glGenFramebuffers(1, &layerFbo);
                glBindFramebuffer(GL_FRAMEBUFFER, layerFbo);
                glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, ndx);
                checkError();
                checkFramebufferStatus(GL_FRAMEBUFFER);

                fbos.push_back(layerFbo);
            }
        }

        log << TestLog::Message << "Rendering test images to layer-faces in randomized order" << TestLog::EndMessage;

        {
            std::vector<int> order(fbos.size());

            for (size_t n = 0; n < order.size(); n++)
                order[n] = (int)n;
            rnd.shuffle(order.begin(), order.end());

            for (size_t ndx = 0; ndx < order.size(); ndx++)
            {
                const int layerFace     = order[ndx];
                const uint32_t format   = GL_RGBA;
                const uint32_t dataType = GL_UNSIGNED_BYTE;
                const int texW          = 128;
                const int texH          = 128;
                uint32_t tmpTex         = 0;
                const uint32_t fbo      = fbos[layerFace];
                const IVec3 &viewport   = m_texSize;
                tcu::TextureLevel data(glu::mapGLTransferFormat(format, dataType), texW, texH, 1);

                tcu::fillWithGrid(data.getAccess(), 8, generateRandomColor(rnd), Vec4(0.0f));

                glGenTextures(1, &tmpTex);
                glBindTexture(GL_TEXTURE_2D, tmpTex);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexImage2D(GL_TEXTURE_2D, 0, format, texW, texH, 0, format, dataType, data.getAccess().getDataPtr());

                glBindFramebuffer(GL_FRAMEBUFFER, fbo);
                glViewport(0, 0, viewport.x(), viewport.y());
                sglr::drawQuad(*getCurrentContext(), texToFboShaderID, Vec3(-1.0f, -1.0f, 0.0f),
                               Vec3(1.0f, 1.0f, 0.0f));
                checkError();

                // Render to framebuffer
                {
                    const Vec3 p0            = Vec3(float(ndx % 2) - 1.0f, float(ndx / 2) - 1.0f, 0.0f);
                    const Vec3 p1            = p0 + Vec3(1.0f, 1.0f, 0.0f);
                    const int layer          = layerFace / 6;
                    const tcu::CubeFace face = getCubeFaceFromNdx(layerFace % 6);

                    glBindFramebuffer(GL_FRAMEBUFFER, 0);
                    glViewport(0, 0, getWidth(), getHeight());

                    glActiveTexture(GL_TEXTURE0);
                    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);

                    arrayTexShader.setLayer(layer);
                    arrayTexShader.setFace(face);
                    arrayTexShader.setUniforms(*getCurrentContext(), arrayTexShaderID);

                    sglr::drawQuad(*getCurrentContext(), arrayTexShaderID, p0, p1);
                    checkError();
                }
            }
        }

        readPixels(dst, 0, 0, getWidth(), getHeight());
    }

private:
    IVec3 m_texSize;
};

FboColorTests::FboColorTests(Context &context) : TestCaseGroup(context, "color", "Colorbuffer tests")
{
}

FboColorTests::~FboColorTests(void)
{
}

void FboColorTests::init(void)
{
    static const uint32_t colorFormats[] = {
        // RGBA formats
        GL_RGBA32I, GL_RGBA32UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA8, GL_RGBA8I, GL_RGBA8UI, GL_SRGB8_ALPHA8, GL_RGB10_A2,
        GL_RGB10_A2UI, GL_RGBA4, GL_RGB5_A1,

        // RGB formats
        GL_RGB8, GL_RGB565,

        // RG formats
        GL_RG32I, GL_RG32UI, GL_RG16I, GL_RG16UI, GL_RG8, GL_RG8I, GL_RG8UI,

        // R formats
        GL_R32I, GL_R32UI, GL_R16I, GL_R16UI, GL_R8, GL_R8I, GL_R8UI,

        // GL_EXT_color_buffer_float
        GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F, GL_RG32F, GL_RG16F, GL_R32F, GL_R16F,

        // GL_EXT_color_buffer_half_float
        GL_RGB16F};

    static const uint32_t unorm16ColorFormats[] = {GL_R16, GL_RG16, GL_RGBA16};

    // .texcubearray
    {
        tcu::TestCaseGroup *texCubeArrayGroup =
            new tcu::TestCaseGroup(m_testCtx, "texcubearray", "Cube map array texture tests");
        addChild(texCubeArrayGroup);

        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(colorFormats); fmtNdx++)
            texCubeArrayGroup->addChild(new FboColorTexCubeArrayCase(m_context, getFormatName(colorFormats[fmtNdx]), "",
                                                                     colorFormats[fmtNdx], IVec3(128, 128, 12)));
    }

    // .tex2d
    {
        tcu::TestCaseGroup *tex2dGroup = new tcu::TestCaseGroup(m_testCtx, "tex2d", "Render to texture");
        addChild(tex2dGroup);

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(unorm16ColorFormats); ndx++)
            tex2dGroup->addChild(new FboColorTex2DCase(m_context, getFormatName(unorm16ColorFormats[ndx]), "",
                                                       unorm16ColorFormats[ndx], IVec2(129, 117)));
    }
}

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