/*-------------------------------------------------------------------------
 * 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 Config query tests.
 *//*--------------------------------------------------------------------*/

#include "teglQueryContextTests.hpp"
#include "teglRenderCase.hpp"
#include "teglRenderCase.hpp"
#include "egluCallLogWrapper.hpp"
#include "egluStrUtil.hpp"
#include "tcuCommandLine.hpp"
#include "tcuTestLog.hpp"
#include "tcuTestContext.hpp"

#include "egluUtil.hpp"
#include "egluNativeDisplay.hpp"
#include "egluNativeWindow.hpp"
#include "egluNativePixmap.hpp"

#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"

#include "deUniquePtr.hpp"
#include "deSTLUtil.hpp"

#include <vector>

namespace deqp
{
namespace egl
{

using eglu::ConfigInfo;
using std::vector;
using tcu::TestLog;
using namespace eglw;

static EGLint getClientTypeFromAPIBit(EGLint apiBit)
{
    switch (apiBit)
    {
    case EGL_OPENGL_BIT:
        return EGL_OPENGL_API;
    case EGL_OPENGL_ES_BIT:
        return EGL_OPENGL_ES_API;
    case EGL_OPENGL_ES2_BIT:
        return EGL_OPENGL_ES_API;
    case EGL_OPENGL_ES3_BIT:
        return EGL_OPENGL_ES_API;
    case EGL_OPENVG_BIT:
        return EGL_OPENVG_API;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

static EGLint getMinClientMajorVersion(EGLint apiBit)
{
    switch (apiBit)
    {
    case EGL_OPENGL_BIT:
        return 1;
    case EGL_OPENGL_ES_BIT:
        return 1;
    case EGL_OPENGL_ES2_BIT:
        return 2;
    case EGL_OPENGL_ES3_BIT:
        return 3;
    case EGL_OPENVG_BIT:
        return 1;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

class GetCurrentContextCase : public SingleContextRenderCase, private eglu::CallLogWrapper
{
public:
    GetCurrentContextCase(EglTestContext &eglTestCtx, const char *name, const char *description,
                          const eglu::FilterList &filters, EGLint surfaceTypeMask)
        : SingleContextRenderCase(eglTestCtx, name, description, getBuildClientAPIMask(), surfaceTypeMask, filters)
        , eglu::CallLogWrapper(eglTestCtx.getLibrary(), m_testCtx.getLog())
    {
    }

    void executeForContext(EGLDisplay display, EGLContext context, EGLSurface surface, const Config &config)
    {
        const Library &egl = m_eglTestCtx.getLibrary();
        TestLog &log       = m_testCtx.getLog();

        DE_UNREF(display);
        DE_UNREF(surface);
        DE_UNREF(config);

        enableLogging(true);

        const EGLContext gotContext = eglGetCurrentContext();
        EGLU_CHECK_MSG(egl, "eglGetCurrentContext");

        if (gotContext == context)
        {
            log << TestLog::Message << "  Pass" << TestLog::EndMessage;
        }
        else if (gotContext == EGL_NO_CONTEXT)
        {
            log << TestLog::Message << "  Fail, got EGL_NO_CONTEXT" << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected EGL_NO_CONTEXT");
        }
        else if (gotContext != context)
        {
            log << TestLog::Message << "  Fail, call returned the wrong context. Expected: " << tcu::toHex(context)
                << ", got: " << tcu::toHex(gotContext) << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid context");
        }

        enableLogging(false);
    }
};

class GetCurrentSurfaceCase : public SingleContextRenderCase, private eglu::CallLogWrapper
{
public:
    GetCurrentSurfaceCase(EglTestContext &eglTestCtx, const char *name, const char *description,
                          const eglu::FilterList &filters, EGLint surfaceTypeMask)
        : SingleContextRenderCase(eglTestCtx, name, description, getBuildClientAPIMask(), surfaceTypeMask, filters)
        , eglu::CallLogWrapper(eglTestCtx.getLibrary(), m_testCtx.getLog())
    {
    }

    void executeForContext(EGLDisplay display, EGLContext context, EGLSurface surface, const Config &config)
    {
        const Library &egl = m_eglTestCtx.getLibrary();
        TestLog &log       = m_testCtx.getLog();

        DE_UNREF(display);
        DE_UNREF(context);
        DE_UNREF(config);

        enableLogging(true);

        const EGLContext gotReadSurface = eglGetCurrentSurface(EGL_READ);
        EGLU_CHECK_MSG(egl, "eglGetCurrentSurface(EGL_READ)");

        const EGLContext gotDrawSurface = eglGetCurrentSurface(EGL_DRAW);
        EGLU_CHECK_MSG(egl, "eglGetCurrentSurface(EGL_DRAW)");

        if (gotReadSurface == surface && gotDrawSurface == surface)
        {
            log << TestLog::Message << "  Pass" << TestLog::EndMessage;
        }
        else
        {
            log << TestLog::Message << "  Fail, read surface: " << tcu::toHex(gotReadSurface)
                << ", draw surface: " << tcu::toHex(gotDrawSurface) << ", expected: " << tcu::toHex(surface)
                << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid surface");
        }

        enableLogging(false);
    }
};

class GetCurrentDisplayCase : public SingleContextRenderCase, private eglu::CallLogWrapper
{
public:
    GetCurrentDisplayCase(EglTestContext &eglTestCtx, const char *name, const char *description,
                          const eglu::FilterList &filters, EGLint surfaceTypeMask)
        : SingleContextRenderCase(eglTestCtx, name, description, getBuildClientAPIMask(), surfaceTypeMask, filters)
        , eglu::CallLogWrapper(eglTestCtx.getLibrary(), m_testCtx.getLog())
    {
    }

    void executeForContext(EGLDisplay display, EGLContext context, EGLSurface surface, const Config &config)
    {
        const Library &egl = m_eglTestCtx.getLibrary();
        TestLog &log       = m_testCtx.getLog();

        DE_UNREF(surface && context);
        DE_UNREF(config);

        enableLogging(true);

        const EGLDisplay gotDisplay = eglGetCurrentDisplay();
        EGLU_CHECK_MSG(egl, "eglGetCurrentDisplay");

        if (gotDisplay == display)
        {
            log << TestLog::Message << "  Pass" << TestLog::EndMessage;
        }
        else if (gotDisplay == EGL_NO_DISPLAY)
        {
            log << TestLog::Message << "  Fail, got EGL_NO_DISPLAY" << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected EGL_NO_DISPLAY");
        }
        else if (gotDisplay != display)
        {
            log << TestLog::Message << "  Fail, call returned the wrong display. Expected: " << tcu::toHex(display)
                << ", got: " << tcu::toHex(gotDisplay) << TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid display");
        }

        enableLogging(false);
    }
};

class QueryContextCase : public SingleContextRenderCase, private eglu::CallLogWrapper
{
public:
    QueryContextCase(EglTestContext &eglTestCtx, const char *name, const char *description,
                     const eglu::FilterList &filters, EGLint surfaceTypeMask)
        : SingleContextRenderCase(eglTestCtx, name, description, getBuildClientAPIMask(), surfaceTypeMask, filters)
        , eglu::CallLogWrapper(eglTestCtx.getLibrary(), m_testCtx.getLog())
    {
    }

    EGLint getContextAttrib(EGLDisplay display, EGLContext context, EGLint attrib)
    {
        const Library &egl = m_eglTestCtx.getLibrary();
        EGLint value;
        EGLU_CHECK_CALL(egl, queryContext(display, context, attrib, &value));

        return value;
    }

    void executeForContext(EGLDisplay display, EGLContext context, EGLSurface surface, const Config &config)
    {
        const Library &egl          = m_eglTestCtx.getLibrary();
        TestLog &log                = m_testCtx.getLog();
        const eglu::Version version = eglu::getVersion(egl, display);

        DE_UNREF(surface);
        enableLogging(true);

        // Config ID
        {
            const EGLint configID        = getContextAttrib(display, context, EGL_CONFIG_ID);
            const EGLint surfaceConfigID = eglu::getConfigAttribInt(egl, display, config.config, EGL_CONFIG_ID);

            if (configID != surfaceConfigID)
            {
                log << TestLog::Message << "  Fail, config ID doesn't match the one used to create the context."
                    << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid config ID");
            }
        }

        // Client API type
        if (version >= eglu::Version(1, 2))
        {
            const EGLint clientType = getContextAttrib(display, context, EGL_CONTEXT_CLIENT_TYPE);

            if (clientType != getClientTypeFromAPIBit(config.apiBits))
            {
                log << TestLog::Message << "  Fail, client API type doesn't match." << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid client API type");
            }
        }

        // Client API version
        if (version >= eglu::Version(1, 3))
        {
            const EGLint clientVersion = getContextAttrib(display, context, EGL_CONTEXT_CLIENT_VERSION);

            // \todo [2014-10-21 mika] Query actual supported api version from client api to make this check stricter.
            if (clientVersion < getMinClientMajorVersion(config.apiBits))
            {
                log << TestLog::Message << "  Fail, client API version doesn't match." << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid client API version");
            }
        }

        // Render buffer
        if (version >= eglu::Version(1, 2))
        {
            const EGLint renderBuffer = getContextAttrib(display, context, EGL_RENDER_BUFFER);

            if (config.surfaceTypeBit == EGL_PIXMAP_BIT && renderBuffer != EGL_SINGLE_BUFFER)
            {
                log << TestLog::Message << "  Fail, render buffer should be EGL_SINGLE_BUFFER for a pixmap surface."
                    << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
            }
            else if (config.surfaceTypeBit == EGL_PBUFFER_BIT && renderBuffer != EGL_BACK_BUFFER)
            {
                log << TestLog::Message << "  Fail, render buffer should be EGL_BACK_BUFFER for a pbuffer surface."
                    << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
            }
            else if (config.surfaceTypeBit == EGL_WINDOW_BIT && renderBuffer != EGL_SINGLE_BUFFER &&
                     renderBuffer != EGL_BACK_BUFFER)
            {
                log << TestLog::Message
                    << "  Fail, render buffer should be either EGL_SINGLE_BUFFER or EGL_BACK_BUFFER for a window "
                       "surface."
                    << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid render buffer");
            }
        }

        enableLogging(false);

        log << TestLog::Message << "  Pass" << TestLog::EndMessage;
    }
};

class QueryAPICase : public TestCase, private eglu::CallLogWrapper
{
public:
    QueryAPICase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : TestCase(eglTestCtx, name, description)
        , CallLogWrapper(eglTestCtx.getLibrary(), eglTestCtx.getTestContext().getLog())
    {
    }

    void init(void)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    }

    IterateResult iterate(void)
    {
        const Library &egl                  = m_eglTestCtx.getLibrary();
        EGLDisplay display                  = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
        tcu::TestLog &log                   = m_testCtx.getLog();
        const EGLenum apis[]                = {EGL_OPENGL_API, EGL_OPENGL_ES_API, EGL_OPENVG_API};
        const vector<EGLenum> supportedAPIs = eglu::getClientAPIs(egl, display);

        enableLogging(true);

        {
            const EGLenum api = eglQueryAPI();

            if (api != EGL_OPENGL_ES_API &&
                (de::contains(supportedAPIs.begin(), supportedAPIs.end(), EGL_OPENGL_ES_API)))
            {
                log << TestLog::Message
                    << "  Fail, initial value should be EGL_OPENGL_ES_API if OpenGL ES is supported."
                    << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid default value");
            }
            else if (api != EGL_NONE && !(de::contains(supportedAPIs.begin(), supportedAPIs.end(), EGL_OPENGL_ES_API)))
            {
                log << TestLog::Message << "  Fail, initial value should be EGL_NONE if OpenGL ES is not supported."
                    << TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid default value");
            }
        }

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(apis); ndx++)
        {
            const EGLenum api = apis[ndx];

            log << TestLog::Message << TestLog::EndMessage;

            if (de::contains(supportedAPIs.begin(), supportedAPIs.end(), api))
            {
                egl.bindAPI(api);

                if (api != egl.queryAPI())
                {
                    log << TestLog::Message << "  Fail, return value does not match previously bound API."
                        << TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid return value");
                }
            }
            else
            {
                log << TestLog::Message << eglu::getAPIStr(api) << " not supported." << TestLog::EndMessage;
            }
        }

        enableLogging(false);
        eglTerminate(display);
        return STOP;
    }
};

QueryContextTests::QueryContextTests(EglTestContext &eglTestCtx)
    : TestCaseGroup(eglTestCtx, "query_context", "Rendering context query tests")
{
}

QueryContextTests::~QueryContextTests(void)
{
}

template <class QueryContextClass>
void createQueryContextGroups(EglTestContext &eglTestCtx, tcu::TestCaseGroup *group)
{
    std::vector<RenderFilterList> filterLists;

    getDefaultRenderFilterLists(filterLists, eglu::FilterList());

    for (std::vector<RenderFilterList>::const_iterator listIter = filterLists.begin(); listIter != filterLists.end();
         listIter++)
        group->addChild(
            new QueryContextClass(eglTestCtx, listIter->getName(), "", *listIter, listIter->getSurfaceTypeMask()));
}

void QueryContextTests::init(void)
{
    {
        tcu::TestCaseGroup *simpleGroup = new tcu::TestCaseGroup(m_testCtx, "simple", "Simple API tests");
        addChild(simpleGroup);

        simpleGroup->addChild(new QueryAPICase(m_eglTestCtx, "query_api", "eglQueryAPI() test"));
    }

    // eglGetCurrentContext
    {
        tcu::TestCaseGroup *getCurrentContextGroup =
            new tcu::TestCaseGroup(m_testCtx, "get_current_context", "eglGetCurrentContext() tests");
        addChild(getCurrentContextGroup);

        createQueryContextGroups<GetCurrentContextCase>(m_eglTestCtx, getCurrentContextGroup);
    }

    // eglGetCurrentSurface
    {
        tcu::TestCaseGroup *getCurrentSurfaceGroup =
            new tcu::TestCaseGroup(m_testCtx, "get_current_surface", "eglGetCurrentSurface() tests");
        addChild(getCurrentSurfaceGroup);

        createQueryContextGroups<GetCurrentSurfaceCase>(m_eglTestCtx, getCurrentSurfaceGroup);
    }

    // eglGetCurrentDisplay
    {
        tcu::TestCaseGroup *getCurrentDisplayGroup =
            new tcu::TestCaseGroup(m_testCtx, "get_current_display", "eglGetCurrentDisplay() tests");
        addChild(getCurrentDisplayGroup);

        createQueryContextGroups<GetCurrentDisplayCase>(m_eglTestCtx, getCurrentDisplayGroup);
    }

    // eglQueryContext
    {
        tcu::TestCaseGroup *queryContextGroup =
            new tcu::TestCaseGroup(m_testCtx, "query_context", "eglQueryContext() tests");
        addChild(queryContextGroup);

        createQueryContextGroups<QueryContextCase>(m_eglTestCtx, queryContextGroup);
    }
}

} // namespace egl
} // namespace deqp
