/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 Module
 * -------------------------------------------------
 *
 * Copyright 2015 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 GL_EXT_debug_marker tests
 *//*--------------------------------------------------------------------*/

#include "es2fDebugMarkerTests.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "tcuTestLog.hpp"
#include "deRandom.hpp"
#include "deUniquePtr.hpp"

namespace deqp
{
namespace gles2
{
namespace Functional
{

namespace
{

using std::vector;
using tcu::TestLog;

void checkSupport(const glu::ContextInfo &ctxInfo)
{
    if (!ctxInfo.isExtensionSupported("GL_EXT_debug_marker"))
    {
#if (DE_OS == DE_OS_ANDROID)
        TCU_THROW(TestError, "Support for GL_EXT_debug_marker is mandatory on Android");
#else
        TCU_THROW(NotSupportedError, "GL_EXT_debug_marker is not supported");
#endif
    }
    // else no exception thrown
}

class IsSupportedCase : public TestCase
{
public:
    IsSupportedCase(Context &context) : TestCase(context, "supported", "Is GL_EXT_debug_marker supported")
    {
    }

    IterateResult iterate(void)
    {
        checkSupport(m_context.getContextInfo());
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "GL_EXT_debug_marker is supported");
        return STOP;
    }
};

void getSimpleRndString(vector<char> &dst, de::Random &rnd, int maxLen)
{
    const char s_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ -_";

    dst.resize(rnd.getInt(0, (int)maxLen));

    for (size_t ndx = 0; ndx < dst.size(); ndx++)
        dst[ndx] = rnd.choose<char>(DE_ARRAY_BEGIN(s_chars), DE_ARRAY_END(s_chars));
}

void getComplexRndString(vector<char> &dst, de::Random &rnd, int maxLen)
{
    dst.resize(rnd.getInt(0, (int)maxLen));

    for (size_t ndx = 0; ndx < dst.size(); ndx++)
        dst[ndx] = (char)rnd.getUint8();
}

enum CallType
{
    CALL_TYPE_PUSH_GROUP = 0,
    CALL_TYPE_POP_GROUP,
    CALL_TYPE_INSERT_MARKER,

    CALL_TYPE_LAST
};

class RandomCase : public TestCase
{
public:
    RandomCase(Context &context) : TestCase(context, "random", "Random GL_EXT_debug_marker usage")
    {
    }

    void init(void)
    {
        checkSupport(m_context.getContextInfo());
    }

    IterateResult iterate(void)
    {
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();
        const int numIters       = 1000;
        const int maxMsgLen      = 4096;
        de::Random rnd(0xaf829c0);

        for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
        {
            const CallType callType = CallType(rnd.getInt(0, CALL_TYPE_LAST - 1));

            if (callType == CALL_TYPE_PUSH_GROUP || callType == CALL_TYPE_INSERT_MARKER)
            {
                const bool nullTerminate = rnd.getBool();
                const bool passLength    = rnd.getBool();
                const bool complexMsg    = rnd.getBool();
                vector<char> message;

                if (complexMsg)
                    getComplexRndString(message, rnd, maxMsgLen);
                else
                    getSimpleRndString(message, rnd, maxMsgLen);

                if (nullTerminate)
                    message.push_back(char(0));

                {
                    const glw::GLsizei length =
                        passLength ? glw::GLsizei(nullTerminate ? message.size() - 1 : message.size()) : 0;

                    if (callType == CALL_TYPE_PUSH_GROUP)
                        gl.pushGroupMarkerEXT(length, &message[0]);
                    else
                        gl.insertEventMarkerEXT(length, &message[0]);
                }
            }
            else
                gl.popGroupMarkerEXT();
        }

        GLU_EXPECT_NO_ERROR(gl.getError(), "Debug marker calls must not set error state");

        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "All calls passed");
        return STOP;
    }
};

class InvalidCase : public TestCase
{
public:
    InvalidCase(Context &context) : TestCase(context, "invalid", "Invalid GL_EXT_debug_marker usage")
    {
    }

    void init(void)
    {
        checkSupport(m_context.getContextInfo());
    }

    IterateResult iterate(void)
    {
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();

        m_testCtx.getLog()
            << TestLog::Message
            << "Note: GL_EXT_debug_marker calls must not report an error even if invalid arguments are supplied."
            << TestLog::EndMessage;

        gl.pushGroupMarkerEXT(-1, "foo");
        gl.insertEventMarkerEXT(-1, "foo");
        gl.pushGroupMarkerEXT(0, DE_NULL);
        gl.insertEventMarkerEXT(0, DE_NULL);
        gl.pushGroupMarkerEXT(-1, DE_NULL);
        gl.insertEventMarkerEXT(-1, DE_NULL);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Debug marker calls must not set error state");

        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "All calls passed");
        return STOP;
    }
};

} // namespace

tcu::TestCaseGroup *createDebugMarkerTests(Context &context)
{
    de::MovePtr<tcu::TestCaseGroup> debugMarkerGroup(
        new tcu::TestCaseGroup(context.getTestContext(), "debug_marker", "GL_EXT_debug_marker tests"));

    debugMarkerGroup->addChild(new IsSupportedCase(context));
    debugMarkerGroup->addChild(new RandomCase(context));
    debugMarkerGroup->addChild(new InvalidCase(context));

    return debugMarkerGroup.release();
}

} // namespace Functional
} // namespace gles2
} // namespace deqp
