//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// EGLProtectedContentTest.cpp:
//   EGL extension EGL_EXT_protected_content
//

#include <gtest/gtest.h>
#include "common/unsafe_buffers.h"

#include <chrono>
#include <iostream>
#include <thread>
#include "test_utils/ANGLETest.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"

constexpr bool kSleepForVisualVerification = false;

using namespace std::chrono_literals;

using namespace angle;

class EGLProtectedContentTest : public ANGLETest<>
{
  public:
    EGLProtectedContentTest() : mDisplay(EGL_NO_DISPLAY) {}

    void testSetUp() override
    {
        EGLAttrib dispattrs[] = {
            EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(),
            EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE,
            static_cast<EGLAttrib>(mOSWindow ? mOSWindow->getNativeDisplayPlatformType()
                                             : GetPbufferOnlyDefaultPlatformType()),
            EGL_NONE};
        mDisplay              = eglGetPlatformDisplay(GetEglPlatform(),
                                                      reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
        EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
        EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
        mMajorVersion = GetParam().majorVersion;
    }

    void testTearDown() override
    {
        if (mOSWindow != nullptr)
        {
            mOSWindow->destroy();
            OSWindow::Delete(&mOSWindow);
        }

        if (mDisplay != EGL_NO_DISPLAY)
        {
            eglTerminate(mDisplay);
            eglReleaseThread();
            mDisplay = EGL_NO_DISPLAY;
        }
        ASSERT_EGL_SUCCESS() << "Error during test TearDown";
    }

    bool chooseConfig(EGLConfig *config, bool window = false, bool mutableRenderBuffer = false)
    {
        EGLint clientVersion = mMajorVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT;
        EGLint attribs[]     = {
            EGL_RED_SIZE,
            8,
            EGL_GREEN_SIZE,
            8,
            EGL_BLUE_SIZE,
            8,
            EGL_ALPHA_SIZE,
            8,
            EGL_RENDERABLE_TYPE,
            clientVersion,
            EGL_SURFACE_TYPE,
            (window ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT) |
                (mutableRenderBuffer ? EGL_MUTABLE_RENDER_BUFFER_BIT_KHR : EGL_PBUFFER_BIT),
            EGL_BIND_TO_TEXTURE_RGBA,
            EGL_TRUE,
            EGL_NONE};

        EGLint count = 0;
        bool result  = eglChooseConfig(mDisplay, attribs, config, 1, &count);
        EXPECT_EGL_TRUE(result);
        EXPECT_EGL_TRUE(count > 0);
        return result;
    }

    bool createContext(EGLBoolean isProtected, EGLConfig config, EGLContext *context)
    {
        bool result                 = false;
        EGLint attribsProtected[]   = {EGL_CONTEXT_MAJOR_VERSION, mMajorVersion,
                                       EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, EGL_NONE};
        EGLint attribsUnProtected[] = {EGL_CONTEXT_MAJOR_VERSION, mMajorVersion, EGL_NONE};

        *context = eglCreateContext(mDisplay, config, nullptr,
                                    (isProtected ? attribsProtected : attribsUnProtected));
        result   = (*context != EGL_NO_CONTEXT);
        EXPECT_TRUE(result);
        return result;
    }

    bool createPbufferSurface(EGLBoolean isProtected, EGLConfig config, EGLSurface *surface)
    {
        bool result                 = false;
        EGLint attribsProtected[]   = {EGL_WIDTH,
                                       kWidth,
                                       EGL_HEIGHT,
                                       kHeight,
                                       EGL_TEXTURE_FORMAT,
                                       EGL_TEXTURE_RGBA,
                                       EGL_TEXTURE_TARGET,
                                       EGL_TEXTURE_2D,
                                       EGL_PROTECTED_CONTENT_EXT,
                                       EGL_TRUE,
                                       EGL_NONE};
        EGLint attribsUnProtected[] = {EGL_WIDTH,
                                       kWidth,
                                       EGL_HEIGHT,
                                       kHeight,
                                       EGL_TEXTURE_FORMAT,
                                       EGL_TEXTURE_RGBA,
                                       EGL_TEXTURE_TARGET,
                                       EGL_TEXTURE_2D,
                                       EGL_NONE};

        *surface = eglCreatePbufferSurface(mDisplay, config,
                                           (isProtected ? attribsProtected : attribsUnProtected));
        result   = (*surface != EGL_NO_SURFACE);
        EXPECT_TRUE(result);
        return result;
    }

    bool createWindowSurface(EGLBoolean isProtected,
                             EGLConfig config,
                             EGLNativeWindowType win,
                             EGLSurface *surface)
    {
        bool result                 = false;
        EGLint attribsProtected[]   = {EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, EGL_NONE};
        EGLint attribsUnProtected[] = {EGL_NONE};

        *surface = eglCreateWindowSurface(mDisplay, config, win,
                                          (isProtected ? attribsProtected : attribsUnProtected));
        result   = (*surface != EGL_NO_SURFACE);
        EXPECT_TRUE(result);
        return result;
    }

    bool createImage(EGLBoolean isProtected,
                     EGLContext context,
                     EGLenum target,
                     EGLClientBuffer buffer,
                     EGLImage *image)
    {
        bool result                  = false;
        EGLAttrib attribsProtected[] = {EGL_PROTECTED_CONTENT_EXT, EGL_TRUE, EGL_NONE};

        *image = eglCreateImage(mDisplay, context, target, buffer,
                                (isProtected ? attribsProtected : nullptr));
        EXPECT_EGL_SUCCESS();
        result = (*image != EGL_NO_SURFACE);
        EXPECT_TRUE(result);
        return result;
    }

    bool createTexture(EGLBoolean isProtected, GLuint *textureId)
    {
        bool result    = false;
        GLuint texture = 0;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        EXPECT_GL_NO_ERROR();
        if (isProtected)
        {
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_PROTECTED_EXT, GL_TRUE);
            // GL_INVALID_OPERATION expected when context is not protected too.
            GLenum error = glGetError();
            if (error == GL_INVALID_OPERATION)
            {
                return false;
            }
        }
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
        EXPECT_GL_NO_ERROR();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        result = (texture != 0);
        EXPECT_TRUE(result);
        *textureId = texture;
        return result;
    }

    bool createTextureFromImage(EGLImage image, GLuint *textureId)
    {
        bool result    = false;
        GLuint texture = 0;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
        EXPECT_GL_NO_ERROR();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        result = (texture != 0);
        EXPECT_TRUE(result);
        *textureId = texture;
        return result;
    }

    bool createTextureFromPbuffer(EGLSurface pBuffer, GLuint *textureId)
    {
        bool result    = false;
        GLuint texture = 0;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        EXPECT_GL_NO_ERROR();
        EXPECT_TRUE(texture != 0);
        result = eglBindTexImage(mDisplay, pBuffer, EGL_BACK_BUFFER);
        glViewport(0, 0, kWidth, kHeight);
        *textureId = texture;
        return result;
    }

    bool fillTexture(GLuint textureId, GLColor color)
    {
        GLuint pixels[kWidth * kHeight];
        for (uint32_t i = 0; i < (kWidth * kHeight); i++)
        {
            ANGLE_UNSAFE_TODO(pixels[i]) = *(GLuint *)(color.data());
        }
        glBindTexture(GL_TEXTURE_2D, textureId);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                        (void *)pixels);
        EXPECT_GL_NO_ERROR();
        return true;
    }

    bool renderTexture(GLuint textureId)
    {
        const char *kVertexShader   = R"(
            precision highp float;
            attribute vec4 position;
            varying vec2 texcoord;

            void main()
            {
                gl_Position = vec4(position.xy, 0.0, 1.0);
                texcoord = (position.xy * 0.5) + 0.5;
            }
        )";
        const char *kFragmentShader = R"(
            precision highp float;
            uniform sampler2D tex;
            varying vec2 texcoord;

            void main()
            {
                gl_FragColor = texture2D(tex, texcoord);
            }
        )";

        GLuint program = CompileProgram(kVertexShader, kFragmentShader);
        glUseProgram(program);
        glBindTexture(GL_TEXTURE_2D, textureId);
        glActiveTexture(GL_TEXTURE0);
        GLint texture2DUniformLocation = glGetUniformLocation(program, "tex");
        glUniform1i(texture2DUniformLocation, 0);
        drawQuad(program, "position", 0.5f);
        glDeleteProgram(program);
        EXPECT_GL_NO_ERROR();
        return true;
    }

    bool createRenderbuffer(GLuint *renderbuffer)
    {
        bool result   = false;
        *renderbuffer = 0;
        glGenRenderbuffers(1, renderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, *renderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, kWidth, kHeight);
        EXPECT_GL_NO_ERROR();
        result = (*renderbuffer != 0);
        EXPECT_TRUE(result);
        return result;
    }

    bool createRenderbufferFromImage(EGLImage image, GLuint *renderbuffer)
    {
        bool result   = false;
        *renderbuffer = 0;
        glGenRenderbuffers(1, renderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, *renderbuffer);
        glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
        EXPECT_GL_NO_ERROR();
        result = (*renderbuffer != 0);
        EXPECT_TRUE(result);
        return result;
    }

    bool createAndroidClientBuffer(bool useProtected,
                                   bool useRenderbuffer,
                                   bool useTexture,
                                   EGLClientBuffer *clientBuffer)
    {
        bool result = false;
        EGLint nativeBufferUsage =
            0 | (useProtected ? EGL_NATIVE_BUFFER_USAGE_PROTECTED_BIT_ANDROID : 0) |
            (useRenderbuffer ? EGL_NATIVE_BUFFER_USAGE_RENDERBUFFER_BIT_ANDROID : 0) |
            (useTexture ? EGL_NATIVE_BUFFER_USAGE_TEXTURE_BIT_ANDROID : 0);

        EGLint attribs[] = {EGL_WIDTH,
                            kWidth,
                            EGL_HEIGHT,
                            kHeight,
                            EGL_RED_SIZE,
                            8,
                            EGL_GREEN_SIZE,
                            8,
                            EGL_BLUE_SIZE,
                            8,
                            EGL_ALPHA_SIZE,
                            8,
                            EGL_NATIVE_BUFFER_USAGE_ANDROID,
                            nativeBufferUsage,
                            EGL_NONE};

        *clientBuffer = eglCreateNativeClientBufferANDROID(attribs);
        EXPECT_EGL_SUCCESS();
        result = (*clientBuffer != nullptr);
        EXPECT_TRUE(result);
        return result;
    }

    void pbufferTest(bool isProtectedContext, bool isProtectedSurface);

    void checkSwapBuffersResult(const std::string color,
                                bool isProtectedContext,
                                bool isProtectedSurface)
    {
        if (kSleepForVisualVerification)
        {
            std::this_thread::sleep_for(1s);
        }
        if (isProtectedContext)
        {
            if (isProtectedSurface)
            {
                std::cout << "Operator should see color: " << color << std::endl;
            }
            else
            {
                std::cout << "Operator should see color: BLACK" << std::endl;
            }
        }
        else
        {
            if (isProtectedSurface)
            {
                std::cout << "Operator should see color: BLACK" << std::endl;
            }
            else
            {
                std::cout << "Operator should see color: " << color << std::endl;
            }
        }
    }

    OSWindow *mOSWindow         = nullptr;
    EGLDisplay mDisplay         = EGL_NO_DISPLAY;
    EGLint mMajorVersion        = 0;
    static const EGLint kWidth  = 16;
    static const EGLint kHeight = 16;
};

class EGLProtectedContentWindowTest : public EGLProtectedContentTest
{
  public:
    bool chooseConfig(EGLConfig *config, bool mutableRenderBuffer = false)
    {
        return EGLProtectedContentTest::chooseConfig(config, true, mutableRenderBuffer);
    }

    void testSetUp() override
    {
        mOSWindow = OSWindow::New();
        mOSWindow->initialize("ProtectedContentTest", kWidth, kHeight);

        EGLProtectedContentTest::testSetUp();
    }

    void windowTest(bool isProtectedContext,
                    bool isProtectedSurface,
                    bool mutableRenderBuffer = false);
    void textureTest(bool isProtectedContext, bool isProtectedTexture);
    void textureFromImageTest(bool isProtectedContext, bool isProtectedTexture);
    void textureFromPbufferTest(bool isProtectedContext, bool isProtectedTexture);
    void textureFromAndroidNativeBufferTest(bool isProtectedContext, bool isProtectedTexture);
};

void EGLProtectedContentTest::pbufferTest(bool isProtectedContext, bool isProtectedSurface)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface pBufferSurface = EGL_NO_SURFACE;
    EXPECT_TRUE(createPbufferSurface(isProtectedSurface, config, &pBufferSurface));
    ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed.";

    EXPECT_TRUE(eglMakeCurrent(mDisplay, pBufferSurface, pBufferSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    glClearColor(1.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glFinish();
    ASSERT_GL_NO_ERROR() << "glFinish failed";
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, pBufferSurface);
    pBufferSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Unprotected context with Unprotected PbufferSurface
TEST_P(EGLProtectedContentTest, UnprotectedContextWithUnprotectedPbufferSurface)
{
    pbufferTest(false, false);
}

void EGLProtectedContentWindowTest::windowTest(bool isProtectedContext,
                                               bool isProtectedSurface,
                                               bool mutableRenderBuffer)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));
    ANGLE_SKIP_TEST_IF(mutableRenderBuffer &&
                       !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config, mutableRenderBuffer));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface windowSurface          = EGL_NO_SURFACE;
    EGLBoolean createWinSurfaceResult = createWindowSurface(
        isProtectedSurface, config, mOSWindow->getNativeWindow(), &windowSurface);
    EXPECT_TRUE(createWinSurfaceResult);
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_TRUE(eglMakeCurrent(mDisplay, windowSurface, windowSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    if (mutableRenderBuffer)
    {
        EXPECT_TRUE(
            eglSurfaceAttrib(mDisplay, windowSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
        ASSERT_EGL_SUCCESS() << "eglSurfaceAttrib failed.";
        eglSwapBuffers(mDisplay, windowSurface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    // Red
    glClearColor(1.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR() << "glClear failed";
    eglSwapBuffers(mDisplay, windowSurface);
    ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    checkSwapBuffersResult("RED", isProtectedContext, isProtectedSurface);

    // Green
    glClearColor(0.0, 1.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR() << "glClear failed";
    eglSwapBuffers(mDisplay, windowSurface);
    ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    checkSwapBuffersResult("GREEN", isProtectedContext, isProtectedSurface);

    // Blue
    glClearColor(0.0, 0.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR() << "glClear failed";
    eglSwapBuffers(mDisplay, windowSurface);
    ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    checkSwapBuffersResult("BLUE", isProtectedContext, isProtectedSurface);

    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, windowSurface);
    windowSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Unprotected context with Unprotected WindowSurface
TEST_P(EGLProtectedContentWindowTest, UnprotectedContextWithUnprotectedWindowSurface)
{
    windowTest(false, false);
}

// Protected context with Protected WindowSurface
TEST_P(EGLProtectedContentWindowTest, ProtectedContextWithProtectedWindowSurface)
{
    windowTest(true, true);
}

// Protected context with Protected Mutable Render Buffer WindowSurface.
TEST_P(EGLProtectedContentWindowTest, ProtectedContextWithProtectedMutableRenderBufferWindowSurface)
{
    windowTest(true, true, true);
}

void EGLProtectedContentWindowTest::textureTest(bool isProtectedContext, bool isProtectedTexture)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));

    bool isProtectedSurface = isProtectedTexture;

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface windowSurface          = EGL_NO_SURFACE;
    EGLBoolean createWinSurfaceResult = createWindowSurface(
        isProtectedSurface, config, mOSWindow->getNativeWindow(), &windowSurface);
    EXPECT_TRUE(createWinSurfaceResult);
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";
    glViewport(0, 0, kWidth, kHeight);

    EXPECT_TRUE(eglMakeCurrent(mDisplay, windowSurface, windowSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    if (IsGLExtensionEnabled("GL_EXT_protected_textures"))
    {
        GLuint texture = 0;
        bool result    = createTexture(isProtectedTexture, &texture);
        if (isProtectedTexture && !isProtectedContext)
        {
            std::cout << "Can't create protected Texture for Unprotected Context" << std::endl;
            ASSERT_FALSE(result);
        }
        else
        {
            ASSERT_TRUE(result);

            EXPECT_TRUE(fillTexture(texture, GLColor::red));
            EXPECT_TRUE(renderTexture(texture));

            eglSwapBuffers(mDisplay, windowSurface);
            ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
            checkSwapBuffersResult("RED", isProtectedContext, isProtectedSurface);

            glBindTexture(GL_TEXTURE_2D, 0);
            glDeleteTextures(1, &texture);
        }
    }
    else
    {
        std::cout << "Skipping tests, GL_EXT_protected_textures not supported" << std::endl;
    }

    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, windowSurface);
    windowSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Unprotected context with unprotected texture
TEST_P(EGLProtectedContentWindowTest, UnprotectedContextWithUnprotectedTexture)
{
    textureTest(false, false);
}

// Protected context with protected texture
TEST_P(EGLProtectedContentWindowTest, ProtectedContextWithProtectedTexture)
{
    textureTest(true, true);
}

void EGLProtectedContentWindowTest::textureFromImageTest(bool isProtectedContext,
                                                         bool isProtectedTexture)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));

    bool isProtectedSurface = isProtectedTexture;

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface windowSurface          = EGL_NO_SURFACE;
    EGLBoolean createWinSurfaceResult = createWindowSurface(
        isProtectedSurface, config, mOSWindow->getNativeWindow(), &windowSurface);
    EXPECT_TRUE(createWinSurfaceResult);
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_TRUE(eglMakeCurrent(mDisplay, windowSurface, windowSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";
    glViewport(0, 0, kWidth, kHeight);

    if (IsGLExtensionEnabled("GL_OES_EGL_image") &&
        IsGLExtensionEnabled("GL_EXT_protected_textures"))
    {
        GLuint srcTexture = 0;
        if (isProtectedTexture && !isProtectedContext)
        {
            std::cout << "Can't create protected Texture for Unprotected Context, Skipping"
                      << std::endl;
            ASSERT_FALSE(createTexture(isProtectedTexture, &srcTexture));
        }
        else
        {
            ASSERT_TRUE(createTexture(isProtectedTexture, &srcTexture));
            EXPECT_TRUE(fillTexture(srcTexture, GLColor::red));

            EGLImage image = EGL_NO_IMAGE;
            EXPECT_TRUE(createImage(isProtectedTexture, context, EGL_GL_TEXTURE_2D,
                                    (void *)(static_cast<intptr_t>(srcTexture)), &image));

            GLuint dstTexture = 0;
            EXPECT_TRUE(createTextureFromImage(image, &dstTexture));
            EXPECT_TRUE(renderTexture(dstTexture));

            eglSwapBuffers(mDisplay, windowSurface);
            ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
            checkSwapBuffersResult("RED", isProtectedContext, isProtectedSurface);

            glBindTexture(GL_TEXTURE_2D, 0);
            glDeleteTextures(1, &dstTexture);
            glDeleteTextures(1, &srcTexture);

            eglDestroyImage(mDisplay, image);
            image = EGL_NO_IMAGE;
        }
    }
    else
    {
        std::cout << "Skipping tests, GL_OES_EGL_image or GL_EXT_protected_textures not supported"
                  << std::endl;
    }

    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, windowSurface);
    windowSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Unprotected context with unprotected texture from EGL image
TEST_P(EGLProtectedContentWindowTest, UnprotectedContextWithUnprotectedTextureFromImage)
{
    textureFromImageTest(false, false);
}

// Protected context with protected texture from EGL image
TEST_P(EGLProtectedContentWindowTest, ProtectedContextWithProtectedTextureFromImage)
{
    textureFromImageTest(true, true);
}

void EGLProtectedContentWindowTest::textureFromPbufferTest(bool isProtectedContext,
                                                           bool isProtectedTexture)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));

    bool isProtectedSurface = isProtectedTexture;

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface pBufferSurface = EGL_NO_SURFACE;
    EXPECT_TRUE(createPbufferSurface(isProtectedSurface, config, &pBufferSurface));
    ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed.";

    EXPECT_TRUE(eglMakeCurrent(mDisplay, pBufferSurface, pBufferSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    glViewport(0, 0, kWidth, kHeight);
    glClearColor(1.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glFinish();
    ASSERT_GL_NO_ERROR() << "glFinish failed";

    EGLSurface windowSurface          = EGL_NO_SURFACE;
    EGLBoolean createWinSurfaceResult = createWindowSurface(
        isProtectedSurface, config, mOSWindow->getNativeWindow(), &windowSurface);
    EXPECT_TRUE(createWinSurfaceResult);
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_TRUE(eglMakeCurrent(mDisplay, windowSurface, windowSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";
    glViewport(0, 0, kWidth, kHeight);

    if (IsGLExtensionEnabled("GL_EXT_protected_textures"))
    {
        GLuint texture = 0;
        EXPECT_TRUE(createTextureFromPbuffer(pBufferSurface, &texture));
        EXPECT_TRUE(renderTexture(texture));

        eglSwapBuffers(mDisplay, windowSurface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
        checkSwapBuffersResult("RED", isProtectedContext, isProtectedTexture);

        eglReleaseTexImage(mDisplay, pBufferSurface, EGL_BACK_BUFFER);
        glDeleteTextures(1, &texture);
    }
    else
    {
        std::cout << "Skipping tests, GL_EXT_protected_textures not supported" << std::endl;
    }

    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, windowSurface);
    windowSurface = EGL_NO_SURFACE;

    eglDestroySurface(mDisplay, pBufferSurface);
    pBufferSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Unprotected context with unprotected texture from BindTex of PBufferSurface
TEST_P(EGLProtectedContentWindowTest, UnprotectedContextWithUnprotectedTextureFromPBuffer)
{
    textureFromPbufferTest(false, false);
}

// Protected context with protected texture from BindTex of PBufferSurface
TEST_P(EGLProtectedContentWindowTest, ProtectedContextWithProtectedTextureFromPbuffer)
{
    textureFromPbufferTest(true, true);
}

void EGLProtectedContentWindowTest::textureFromAndroidNativeBufferTest(bool isProtectedContext,
                                                                       bool isProtectedTexture)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));
    ANGLE_SKIP_TEST_IF(
        !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANDROID_get_native_client_buffer"));
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANDROID_image_native_buffer"));

    bool isProtectedSurface = isProtectedTexture;

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface windowSurface          = EGL_NO_SURFACE;
    EGLBoolean createWinSurfaceResult = createWindowSurface(
        isProtectedSurface, config, mOSWindow->getNativeWindow(), &windowSurface);
    EXPECT_TRUE(createWinSurfaceResult);
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_TRUE(eglMakeCurrent(mDisplay, windowSurface, windowSurface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";
    glViewport(0, 0, kWidth, kHeight);

    if (IsGLExtensionEnabled("GL_EXT_protected_textures"))
    {
        EGLClientBuffer clientBuffer = nullptr;
        EXPECT_TRUE(createAndroidClientBuffer(isProtectedTexture, false, true, &clientBuffer));

        EGLImage image = EGL_NO_IMAGE;
        EXPECT_TRUE(createImage(isProtectedTexture, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
                                clientBuffer, &image));

        GLuint texture = 0;
        if (isProtectedTexture && !isProtectedContext)
        {
            std::cout << "Can't create protected Texture for Unprotected Context, Skipping"
                      << std::endl;
            ASSERT_FALSE(createTextureFromImage(image, &texture));
        }
        else
        {
            EXPECT_TRUE(createTextureFromImage(image, &texture));
            EXPECT_TRUE(fillTexture(texture, GLColor::red));
            EXPECT_TRUE(renderTexture(texture));

            eglSwapBuffers(mDisplay, windowSurface);
            ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
            checkSwapBuffersResult("RED", isProtectedContext, isProtectedTexture);

            glBindTexture(GL_TEXTURE_2D, 0);
            glDeleteTextures(1, &texture);

            eglDestroyImage(mDisplay, image);
            image = EGL_NO_IMAGE;
        }
    }
    else
    {
        std::cout << "Skipping tests, GL_EXT_protected_textures not supported" << std::endl;
    }

    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, windowSurface);
    windowSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Unprotected context with unprotected texture from EGL image from Android native buffer
TEST_P(EGLProtectedContentWindowTest,
       UnprotectedContextWithUnprotectedTextureFromAndroidNativeBuffer)
{
    textureFromAndroidNativeBufferTest(false, false);
}

// Protected context with protected texture from EGL image from Android native buffer
TEST_P(EGLProtectedContentWindowTest, ProtectedContextWithProtectedTextureFromAndroidNativeBuffer)
{
    textureFromAndroidNativeBufferTest(true, true);
}

// Retrieve the EGL_PROTECTED_CONTENT_EXT attribute via eglQueryContext
TEST_P(EGLProtectedContentTest, QueryContext)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_protected_content"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));
    ANGLE_SKIP_TEST_IF(config == EGL_NO_CONFIG_KHR);

    bool isProtectedContext = true;
    EGLContext context      = EGL_NO_CONTEXT;
    EXPECT_TRUE(createContext(isProtectedContext, config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLint value = 0;
    EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_PROTECTED_CONTENT_EXT, &value));
    ASSERT_EGL_SUCCESS() << "eglQueryContext failed.";

    EXPECT_EQ(value, 1);
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLProtectedContentTest);
ANGLE_INSTANTIATE_TEST(EGLProtectedContentTest,
                       WithNoFixture(ES2_OPENGLES()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLProtectedContentWindowTest);
ANGLE_INSTANTIATE_TEST(EGLProtectedContentWindowTest,
                       WithNoFixture(ES2_OPENGLES()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));
