/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL 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 Color clear case.
 *//*--------------------------------------------------------------------*/

#include "teglColorClearCase.hpp"
#include "tcuTestLog.hpp"
#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"
#include "egluUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuPixelFormat.hpp"
#include "glwFunctions.hpp"
#include "deThread.hpp"
#include "deSemaphore.hpp"
#include "deSharedPtr.hpp"
#include "teglGLES1RenderUtil.hpp"
#include "teglGLES2RenderUtil.hpp"
#include "teglVGRenderUtil.hpp"

#include <memory>
#include <iterator>

namespace deqp
{
namespace egl
{

using std::vector;
using tcu::RGBA;
using tcu::TestLog;
using namespace eglw;

// Utilities.

struct ClearOp
{
    ClearOp(int x_, int y_, int width_, int height_, const tcu::RGBA &color_)
        : x(x_)
        , y(y_)
        , width(width_)
        , height(height_)
        , color(color_)
    {
    }

    ClearOp(void) : x(0), y(0), width(0), height(0), color(0)
    {
    }

    int x;
    int y;
    int width;
    int height;
    tcu::RGBA color;
};

struct ApiFunctions
{
    glw::Functions gl;
};

static ClearOp computeRandomClear(de::Random &rnd, int width, int height)
{
    int w = rnd.getInt(1, width);
    int h = rnd.getInt(1, height);
    int x = rnd.getInt(0, width - w);
    int y = rnd.getInt(0, height - h);
    tcu::RGBA col(rnd.getUint32());

    return ClearOp(x, y, w, h, col);
}

static void renderReference(tcu::Surface &dst, const vector<ClearOp> &clears, const tcu::PixelFormat &pixelFormat)
{
    for (vector<ClearOp>::const_iterator clearIter = clears.begin(); clearIter != clears.end(); clearIter++)
    {
        tcu::PixelBufferAccess access =
            tcu::getSubregion(dst.getAccess(), clearIter->x, clearIter->y, 0, clearIter->width, clearIter->height, 1);
        tcu::clear(access, pixelFormat.convertColor(clearIter->color).toIVec());
    }
}

static void renderClear(EGLint api, const ApiFunctions &func, const ClearOp &clear)
{
    switch (api)
    {
    case EGL_OPENGL_ES_BIT:
        gles1::clear(clear.x, clear.y, clear.width, clear.height, clear.color.toVec());
        break;
    case EGL_OPENGL_ES2_BIT:
        gles2::clear(func.gl, clear.x, clear.y, clear.width, clear.height, clear.color.toVec());
        break;
    case EGL_OPENGL_ES3_BIT_KHR:
        gles2::clear(func.gl, clear.x, clear.y, clear.width, clear.height, clear.color.toVec());
        break;
    case EGL_OPENVG_BIT:
        vg::clear(clear.x, clear.y, clear.width, clear.height, clear.color.toVec());
        break;
    default:
        DE_ASSERT(false);
    }
}

static void finish(EGLint api, const ApiFunctions &func)
{
    switch (api)
    {
    case EGL_OPENGL_ES_BIT:
        gles1::finish();
        break;
    case EGL_OPENGL_ES2_BIT:
        gles2::finish(func.gl);
        break;
    case EGL_OPENGL_ES3_BIT_KHR:
        gles2::finish(func.gl);
        break;
    case EGL_OPENVG_BIT:
        vg::finish();
        break;
    default:
        DE_ASSERT(false);
    }
}

static void readPixels(EGLint api, const ApiFunctions &func, tcu::Surface &dst)
{
    switch (api)
    {
    case EGL_OPENGL_ES_BIT:
        gles1::readPixels(dst, 0, 0, dst.getWidth(), dst.getHeight());
        break;
    case EGL_OPENGL_ES2_BIT:
        gles2::readPixels(func.gl, dst, 0, 0, dst.getWidth(), dst.getHeight());
        break;
    case EGL_OPENGL_ES3_BIT_KHR:
        gles2::readPixels(func.gl, dst, 0, 0, dst.getWidth(), dst.getHeight());
        break;
    case EGL_OPENVG_BIT:
        vg::readPixels(dst, 0, 0, dst.getWidth(), dst.getHeight());
        break;
    default:
        DE_ASSERT(false);
    }
}

static tcu::PixelFormat getPixelFormat(const Library &egl, EGLDisplay display, EGLConfig config)
{
    tcu::PixelFormat pixelFmt;

    egl.getConfigAttrib(display, config, EGL_RED_SIZE, &pixelFmt.redBits);
    egl.getConfigAttrib(display, config, EGL_GREEN_SIZE, &pixelFmt.greenBits);
    egl.getConfigAttrib(display, config, EGL_BLUE_SIZE, &pixelFmt.blueBits);
    egl.getConfigAttrib(display, config, EGL_ALPHA_SIZE, &pixelFmt.alphaBits);

    return pixelFmt;
}

// SingleThreadColorClearCase

SingleThreadColorClearCase::SingleThreadColorClearCase(EglTestContext &eglTestCtx, const char *name,
                                                       const char *description, EGLint api, EGLint surfaceType,
                                                       const eglu::FilterList &filters, int numContextsPerApi)
    : MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi)
{
}

void SingleThreadColorClearCase::executeForContexts(EGLDisplay display, EGLSurface surface, const Config &config,
                                                    const std::vector<std::pair<EGLint, EGLContext>> &contexts)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    const tcu::IVec2 surfaceSize = eglu::getSurfaceSize(egl, display, surface);
    const int width              = surfaceSize.x();
    const int height             = surfaceSize.y();

    TestLog &log = m_testCtx.getLog();

    tcu::Surface refFrame(width, height);
    tcu::Surface frame(width, height);
    tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config);

    de::Random rnd(deStringHash(getName()));
    vector<ClearOp> clears;
    const int ctxClears = 2;
    const int numIters  = 3;

    ApiFunctions funcs;

    m_eglTestCtx.initGLFunctions(&funcs.gl, glu::ApiType::es(2, 0));

    // Clear to black using first context.
    {
        EGLint api         = contexts[0].first;
        EGLContext context = contexts[0].second;
        ClearOp clear(0, 0, width, height, RGBA::black());

        egl.makeCurrent(display, surface, surface, context);
        EGLU_CHECK_MSG(egl, "eglMakeCurrent");

        renderClear(api, funcs, clear);
        finish(api, funcs);
        clears.push_back(clear);
    }

    // Render.
    for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
    {
        for (vector<std::pair<EGLint, EGLContext>>::const_iterator ctxIter = contexts.begin();
             ctxIter != contexts.end(); ctxIter++)
        {
            EGLint api         = ctxIter->first;
            EGLContext context = ctxIter->second;

            egl.makeCurrent(display, surface, surface, context);
            EGLU_CHECK_MSG(egl, "eglMakeCurrent");

            for (int clearNdx = 0; clearNdx < ctxClears; clearNdx++)
            {
                ClearOp clear = computeRandomClear(rnd, width, height);

                renderClear(api, funcs, clear);
                clears.push_back(clear);
            }

            finish(api, funcs);
        }
    }

    // Read pixels using first context. \todo [pyry] Randomize?
    {
        EGLint api         = contexts[0].first;
        EGLContext context = contexts[0].second;

        egl.makeCurrent(display, surface, surface, context);
        EGLU_CHECK_MSG(egl, "eglMakeCurrent");

        readPixels(api, funcs, frame);
    }

    egl.makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    EGLU_CHECK_MSG(egl, "eglMakeCurrent");

    // Render reference.
    renderReference(refFrame, clears, pixelFmt);

    // Compare images
    {
        tcu::RGBA eps = RGBA(1, 1, 1, 1);
        if (pixelFmt.alphaBits == 1)
            eps = RGBA(1, 1, 1, 127);
        else if (pixelFmt.alphaBits == 2)
            eps = RGBA(1, 1, 1, 63);

        bool imagesOk = tcu::pixelThresholdCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame,
                                                   eps + pixelFmt.getColorThreshold(), tcu::COMPARE_LOG_RESULT);

        if (!imagesOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
    }
}

// MultiThreadColorClearCase

enum
{
    NUM_CLEARS_PER_PACKET = 2 //!< Number of clears performed in one context activation in one thread.
};

class ColorClearThread;

typedef de::SharedPtr<ColorClearThread> ColorClearThreadSp;
typedef de::SharedPtr<de::Semaphore> SemaphoreSp;

struct ClearPacket
{
    ClearPacket(void)
    {
    }

    ClearOp clears[NUM_CLEARS_PER_PACKET];
    SemaphoreSp wait;
    SemaphoreSp signal;
};

class ColorClearThread : public de::Thread
{
public:
    ColorClearThread(const Library &egl, EGLDisplay display, EGLSurface surface, EGLContext context, EGLint api,
                     const ApiFunctions &funcs, const std::vector<ClearPacket> &packets)
        : m_egl(egl)
        , m_display(display)
        , m_surface(surface)
        , m_context(context)
        , m_api(api)
        , m_funcs(funcs)
        , m_packets(packets)
    {
    }

    void run(void)
    {
        for (std::vector<ClearPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end();
             packetIter++)
        {
            // Wait until it is our turn.
            packetIter->wait->decrement();

            // Acquire context.
            m_egl.makeCurrent(m_display, m_surface, m_surface, m_context);

            // Execute clears.
            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(packetIter->clears); ndx++)
                renderClear(m_api, m_funcs, packetIter->clears[ndx]);

            finish(m_api, m_funcs);
            // Release context.
            m_egl.makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

            // Signal completion.
            packetIter->signal->increment();
        }
        m_egl.releaseThread();
    }

private:
    const Library &m_egl;
    EGLDisplay m_display;
    EGLSurface m_surface;
    EGLContext m_context;
    EGLint m_api;
    const ApiFunctions &m_funcs;
    const std::vector<ClearPacket> &m_packets;
};

MultiThreadColorClearCase::MultiThreadColorClearCase(EglTestContext &eglTestCtx, const char *name,
                                                     const char *description, EGLint api, EGLint surfaceType,
                                                     const eglu::FilterList &filters, int numContextsPerApi)
    : MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi)
{
}

void MultiThreadColorClearCase::executeForContexts(EGLDisplay display, EGLSurface surface, const Config &config,
                                                   const std::vector<std::pair<EGLint, EGLContext>> &contexts)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    const tcu::IVec2 surfaceSize = eglu::getSurfaceSize(egl, display, surface);
    const int width              = surfaceSize.x();
    const int height             = surfaceSize.y();

    TestLog &log = m_testCtx.getLog();

    tcu::Surface refFrame(width, height);
    tcu::Surface frame(width, height);
    tcu::PixelFormat pixelFmt = getPixelFormat(egl, display, config.config);

    de::Random rnd(deStringHash(getName()));

    ApiFunctions funcs;

    m_eglTestCtx.initGLFunctions(&funcs.gl, glu::ApiType::es(2, 0));

    // Create clear packets.
    const int numPacketsPerThread = 2;
    int numThreads                = (int)contexts.size();
    int numPackets                = numThreads * numPacketsPerThread;

    vector<SemaphoreSp> semaphores(numPackets + 1);
    vector<vector<ClearPacket>> packets(numThreads);
    vector<ColorClearThreadSp> threads(numThreads);

    // Initialize semaphores.
    for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem)
        *sem = SemaphoreSp(new de::Semaphore(0));

    // Create packets.
    for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
    {
        packets[threadNdx].resize(numPacketsPerThread);

        for (int packetNdx = 0; packetNdx < numPacketsPerThread; packetNdx++)
        {
            ClearPacket &packet = packets[threadNdx][packetNdx];

            // Threads take turns with packets.
            packet.wait   = semaphores[packetNdx * numThreads + threadNdx];
            packet.signal = semaphores[packetNdx * numThreads + threadNdx + 1];

            for (int clearNdx = 0; clearNdx < DE_LENGTH_OF_ARRAY(packet.clears); clearNdx++)
            {
                // First clear is always full-screen black.
                if (threadNdx == 0 && packetNdx == 0 && clearNdx == 0)
                    packet.clears[clearNdx] = ClearOp(0, 0, width, height, RGBA::black());
                else
                    packet.clears[clearNdx] = computeRandomClear(rnd, width, height);
            }
        }
    }

    // Create and launch threads (actual rendering starts once first semaphore is signaled).
    for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
    {
        threads[threadNdx] = ColorClearThreadSp(new ColorClearThread(
            egl, display, surface, contexts[threadNdx].second, contexts[threadNdx].first, funcs, packets[threadNdx]));
        threads[threadNdx]->start();
    }

    // Signal start and wait until complete.
    semaphores.front()->increment();
    semaphores.back()->decrement();

    // Read pixels using first context. \todo [pyry] Randomize?
    {
        EGLint api         = contexts[0].first;
        EGLContext context = contexts[0].second;

        egl.makeCurrent(display, surface, surface, context);
        EGLU_CHECK_MSG(egl, "eglMakeCurrent");

        readPixels(api, funcs, frame);
    }

    egl.makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    EGLU_CHECK_MSG(egl, "eglMakeCurrent");

    // Join threads.
    for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
        threads[threadNdx]->join();

    // Render reference.
    for (int packetNdx = 0; packetNdx < numPacketsPerThread; packetNdx++)
    {
        for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
        {
            const ClearPacket &packet = packets[threadNdx][packetNdx];
            for (int clearNdx = 0; clearNdx < DE_LENGTH_OF_ARRAY(packet.clears); clearNdx++)
            {
                tcu::PixelBufferAccess access =
                    tcu::getSubregion(refFrame.getAccess(), packet.clears[clearNdx].x, packet.clears[clearNdx].y, 0,
                                      packet.clears[clearNdx].width, packet.clears[clearNdx].height, 1);
                tcu::clear(access, pixelFmt.convertColor(packet.clears[clearNdx].color).toIVec());
            }
        }
    }

    // Compare images
    {
        tcu::RGBA eps = RGBA(1, 1, 1, 1);
        if (pixelFmt.alphaBits == 1)
            eps = RGBA(1, 1, 1, 127);
        else if (pixelFmt.alphaBits == 2)
            eps = RGBA(1, 1, 1, 63);

        bool imagesOk = tcu::pixelThresholdCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame,
                                                   eps + pixelFmt.getColorThreshold(), tcu::COMPARE_LOG_RESULT);

        if (!imagesOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
    }
}

} // namespace egl
} // namespace deqp
