//
// Copyright(c) 2014 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.
//

// entry_points_egl.cpp : Implements the EGL entry points.

#include "libGLESv2/entry_points_egl.h"

#include "common/debug.h"
#include "common/utilities.h"
#include "common/version.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/EGLSync.h"
#include "libANGLE/Surface.h"
#include "libANGLE/Texture.h"
#include "libANGLE/Thread.h"
#include "libANGLE/queryutils.h"
#include "libANGLE/validationEGL.h"
#include "libGLESv2/global_state.h"
#include "libGLESv2/proc_table.h"

using namespace egl;

namespace
{

bool CompareProc(const ProcEntry &a, const char *b)
{
    return strcmp(a.first, b) < 0;
}

void ClipConfigs(const std::vector<const Config *> &filteredConfigs,
                 EGLConfig *output_configs,
                 EGLint config_size,
                 EGLint *num_config)
{
    EGLint result_size = static_cast<EGLint>(filteredConfigs.size());
    if (output_configs)
    {
        result_size = std::max(std::min(result_size, config_size), 0);
        for (EGLint i = 0; i < result_size; i++)
        {
            output_configs[i] = const_cast<Config *>(filteredConfigs[i]);
        }
    }
    *num_config = result_size;
}
}  // anonymous namespace

extern "C" {
// EGL 1.0
EGLint EGLAPIENTRY EGL_GetError(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    EGLint error = thread->getError();
    thread->setSuccess();
    return error;
}

EGLDisplay EGLAPIENTRY EGL_GetDisplay(EGLNativeDisplayType display_id)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLNativeDisplayType display_id = 0x%016" PRIxPTR ")", (uintptr_t)display_id);

    return egl::Display::GetDisplayFromNativeDisplay(display_id, AttributeMap());
}

EGLBoolean EGLAPIENTRY EGL_Initialize(EGLDisplay dpy, EGLint *major, EGLint *minor)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLint *major = 0x%016" PRIxPTR
          ", EGLint *minor = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)major, (uintptr_t)minor);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    ANGLE_EGL_TRY_RETURN(thread, ValidateInitialize(display), "eglInitialize",
                         GetDisplayIfValid(display), EGL_FALSE);

    ANGLE_EGL_TRY_RETURN(thread, display->initialize(), "eglInitialize", GetDisplayIfValid(display),
                         EGL_FALSE);

    if (major)
        *major = 1;
    if (minor)
        *minor = 4;

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_Terminate(EGLDisplay dpy)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ")", (uintptr_t)dpy);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    ANGLE_EGL_TRY_RETURN(thread, ValidateTerminate(display), "eglTerminate",
                         GetDisplayIfValid(display), EGL_FALSE);

    ANGLE_EGL_TRY_RETURN(thread, display->makeCurrent(thread, nullptr, nullptr, nullptr),
                         "eglTerminate", GetDisplayIfValid(display), EGL_FALSE);
    SetContextCurrent(thread, nullptr);
    ANGLE_EGL_TRY_RETURN(thread, display->terminate(thread), "eglTerminate",
                         GetDisplayIfValid(display), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

const char *EGLAPIENTRY EGL_QueryString(EGLDisplay dpy, EGLint name)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLint name = %d)", (uintptr_t)dpy, name);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    if (!(display == EGL_NO_DISPLAY && name == EGL_EXTENSIONS))
    {
        ANGLE_EGL_TRY_RETURN(thread, ValidateDisplay(display), "eglQueryString",
                             GetDisplayIfValid(display), nullptr);
    }

    const char *result;
    switch (name)
    {
        case EGL_CLIENT_APIS:
            result = "OpenGL_ES";
            break;
        case EGL_EXTENSIONS:
            if (display == EGL_NO_DISPLAY)
            {
                result = egl::Display::GetClientExtensionString().c_str();
            }
            else
            {
                result = display->getExtensionString().c_str();
            }
            break;
        case EGL_VENDOR:
            result = display->getVendorString().c_str();
            break;
        case EGL_VERSION:
            result = "1.4 (ANGLE " ANGLE_VERSION_STRING ")";
            break;
        default:
            thread->setError(EglBadParameter(), GetDebug(), "eglQueryString",
                             GetDisplayIfValid(display));
            return nullptr;
    }

    thread->setSuccess();
    return result;
}

EGLBoolean EGLAPIENTRY EGL_GetConfigs(EGLDisplay dpy,
                                      EGLConfig *configs,
                                      EGLint config_size,
                                      EGLint *num_config)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig *configs = 0x%016" PRIxPTR
          ", "
          "EGLint config_size = %d, EGLint *num_config = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)configs, config_size, (uintptr_t)num_config);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);

    ANGLE_EGL_TRY_RETURN(thread, ValidateGetConfigs(display, config_size, num_config),
                         "eglGetConfigs", GetDisplayIfValid(display), EGL_FALSE);

    ClipConfigs(display->getConfigs(AttributeMap()), configs, config_size, num_config);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_ChooseConfig(EGLDisplay dpy,
                                        const EGLint *attrib_list,
                                        EGLConfig *configs,
                                        EGLint config_size,
                                        EGLint *num_config)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", const EGLint *attrib_list = 0x%016" PRIxPTR
          ", "
          "EGLConfig *configs = 0x%016" PRIxPTR
          ", EGLint config_size = %d, EGLint *num_config = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)attrib_list, (uintptr_t)configs, config_size,
          (uintptr_t)num_config);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display  = static_cast<egl::Display *>(dpy);
    AttributeMap attribMap = AttributeMap::CreateFromIntArray(attrib_list);

    ANGLE_EGL_TRY_RETURN(thread, ValidateChooseConfig(display, attribMap, config_size, num_config),
                         "eglChooseConfig", GetDisplayIfValid(display), EGL_FALSE);

    ClipConfigs(display->chooseConfig(attribMap), configs, config_size, num_config);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_GetConfigAttrib(EGLDisplay dpy,
                                           EGLConfig config,
                                           EGLint attribute,
                                           EGLint *value)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", EGLint attribute = %d, EGLint "
          "*value = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, attribute, (uintptr_t)value);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Config *configuration = static_cast<Config *>(config);

    ANGLE_EGL_TRY_RETURN(thread, ValidateGetConfigAttrib(display, configuration, attribute),
                         "eglGetConfigAttrib", GetDisplayIfValid(display), EGL_FALSE);

    QueryConfigAttrib(configuration, attribute, value);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLSurface EGLAPIENTRY EGL_CreateWindowSurface(EGLDisplay dpy,
                                               EGLConfig config,
                                               EGLNativeWindowType win,
                                               const EGLint *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", EGLNativeWindowType win = 0x%016" PRIxPTR
          ", "
          "const EGLint *attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, (uintptr_t)win, (uintptr_t)attrib_list);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display   = static_cast<egl::Display *>(dpy);
    Config *configuration   = static_cast<Config *>(config);
    AttributeMap attributes = AttributeMap::CreateFromIntArray(attrib_list);

    ANGLE_EGL_TRY_RETURN(thread,
                         ValidateCreateWindowSurface(display, configuration, win, attributes),
                         "eglCreateWindowSurface", GetDisplayIfValid(display), EGL_NO_SURFACE);

    egl::Surface *surface = nullptr;
    ANGLE_EGL_TRY_RETURN(thread,
                         display->createWindowSurface(configuration, win, attributes, &surface),
                         "eglCreateWindowSurface", GetDisplayIfValid(display), EGL_NO_SURFACE);

    return static_cast<EGLSurface>(surface);
}

EGLSurface EGLAPIENTRY EGL_CreatePbufferSurface(EGLDisplay dpy,
                                                EGLConfig config,
                                                const EGLint *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", const EGLint *attrib_list = "
          "0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, (uintptr_t)attrib_list);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display   = static_cast<egl::Display *>(dpy);
    Config *configuration   = static_cast<Config *>(config);
    AttributeMap attributes = AttributeMap::CreateFromIntArray(attrib_list);

    ANGLE_EGL_TRY_RETURN(thread, ValidateCreatePbufferSurface(display, configuration, attributes),
                         "eglCreatePbufferSurface", GetDisplayIfValid(display), EGL_NO_SURFACE);

    egl::Surface *surface = nullptr;
    ANGLE_EGL_TRY_RETURN(thread, display->createPbufferSurface(configuration, attributes, &surface),
                         "eglCreatePbufferSurface", GetDisplayIfValid(display), EGL_NO_SURFACE);

    return static_cast<EGLSurface>(surface);
}

EGLSurface EGLAPIENTRY EGL_CreatePixmapSurface(EGLDisplay dpy,
                                               EGLConfig config,
                                               EGLNativePixmapType pixmap,
                                               const EGLint *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", EGLNativePixmapType pixmap = "
          "0x%016" PRIxPTR
          ", "
          "const EGLint *attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, (uintptr_t)pixmap, (uintptr_t)attrib_list);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Config *configuration = static_cast<Config *>(config);

    ANGLE_EGL_TRY_RETURN(thread, ValidateConfig(display, configuration), "eglCreatePixmapSurface",
                         GetDisplayIfValid(display), EGL_NO_SURFACE);

    UNIMPLEMENTED();  // FIXME

    thread->setSuccess();
    return EGL_NO_SURFACE;
}

EGLBoolean EGLAPIENTRY EGL_DestroySurface(EGLDisplay dpy, EGLSurface surface)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)surface);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *eglSurface   = static_cast<Surface *>(surface);

    ANGLE_EGL_TRY_RETURN(thread, ValidateDestroySurface(display, eglSurface, surface),
                         "eglDestroySurface", GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    ANGLE_EGL_TRY_RETURN(thread, display->destroySurface(eglSurface), "eglDestroySurface",
                         GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_QuerySurface(EGLDisplay dpy,
                                        EGLSurface surface,
                                        EGLint attribute,
                                        EGLint *value)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR
          ", EGLint attribute = %d, EGLint "
          "*value = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)surface, attribute, (uintptr_t)value);
    Thread *thread = egl::GetCurrentThread();

    const egl::Display *display = static_cast<const egl::Display *>(dpy);
    const Surface *eglSurface   = static_cast<const Surface *>(surface);

    ANGLE_EGL_TRY_RETURN(thread, ValidateQuerySurface(display, eglSurface, attribute, value),
                         "eglQuerySurface", GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    QuerySurfaceAttrib(eglSurface, attribute, value);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLContext EGLAPIENTRY EGL_CreateContext(EGLDisplay dpy,
                                         EGLConfig config,
                                         EGLContext share_context,
                                         const EGLint *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", EGLContext share_context = "
          "0x%016" PRIxPTR
          ", "
          "const EGLint *attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, (uintptr_t)share_context, (uintptr_t)attrib_list);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display        = static_cast<egl::Display *>(dpy);
    Config *configuration        = static_cast<Config *>(config);
    gl::Context *sharedGLContext = static_cast<gl::Context *>(share_context);
    AttributeMap attributes      = AttributeMap::CreateFromIntArray(attrib_list);

    ANGLE_EGL_TRY_RETURN(thread,
                         ValidateCreateContext(display, configuration, sharedGLContext, attributes),
                         "eglCreateContext", GetDisplayIfValid(display), EGL_NO_CONTEXT);

    gl::Context *context = nullptr;
    ANGLE_EGL_TRY_RETURN(
        thread, display->createContext(configuration, sharedGLContext, attributes, &context),
        "eglCreateContext", GetDisplayIfValid(display), EGL_NO_CONTEXT);

    thread->setSuccess();
    return static_cast<EGLContext>(context);
}

EGLBoolean EGLAPIENTRY EGL_DestroyContext(EGLDisplay dpy, EGLContext ctx)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLContext ctx = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)ctx);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    gl::Context *context  = static_cast<gl::Context *>(ctx);

    ANGLE_EGL_TRY_RETURN(thread, ValidateDestroyContext(display, context, ctx), "eglDestroyContext",
                         GetContextIfValid(display, context), EGL_FALSE);

    bool contextWasCurrent = context == thread->getContext();

    ANGLE_EGL_TRY_RETURN(thread, display->destroyContext(thread, context), "eglDestroyContext",
                         GetContextIfValid(display, context), EGL_FALSE);

    if (contextWasCurrent)
    {
        SetContextCurrent(thread, nullptr);
    }

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_MakeCurrent(EGLDisplay dpy,
                                       EGLSurface draw,
                                       EGLSurface read,
                                       EGLContext ctx)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface draw = 0x%016" PRIxPTR
          ", EGLSurface read = 0x%016" PRIxPTR
          ", "
          "EGLContext ctx = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)draw, (uintptr_t)read, (uintptr_t)ctx);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *drawSurface  = static_cast<Surface *>(draw);
    Surface *readSurface  = static_cast<Surface *>(read);
    gl::Context *context  = static_cast<gl::Context *>(ctx);

    ANGLE_EGL_TRY_RETURN(thread, ValidateMakeCurrent(display, drawSurface, readSurface, context),
                         "eglMakeCurrent", GetContextIfValid(display, context), EGL_FALSE);

    Surface *previousDraw        = thread->getCurrentDrawSurface();
    Surface *previousRead        = thread->getCurrentReadSurface();
    gl::Context *previousContext = thread->getContext();

    // Only call makeCurrent if the context or surfaces have changed.
    if (previousDraw != drawSurface || previousRead != readSurface || previousContext != context)
    {
        ANGLE_EGL_TRY_RETURN(thread,
                             display->makeCurrent(thread, drawSurface, readSurface, context),
                             "eglMakeCurrent", GetContextIfValid(display, context), EGL_FALSE);

        SetContextCurrent(thread, context);
    }

    thread->setSuccess();
    return EGL_TRUE;
}

EGLSurface EGLAPIENTRY EGL_GetCurrentSurface(EGLint readdraw)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLint readdraw = %d)", readdraw);
    Thread *thread = egl::GetCurrentThread();

    if (readdraw == EGL_READ)
    {
        thread->setSuccess();
        return thread->getCurrentReadSurface();
    }
    else if (readdraw == EGL_DRAW)
    {
        thread->setSuccess();
        return thread->getCurrentDrawSurface();
    }
    else
    {
        thread->setError(EglBadParameter(), GetDebug(), "eglGetCurrentSurface", nullptr);
        return EGL_NO_SURFACE;
    }
}

EGLDisplay EGLAPIENTRY EGL_GetCurrentDisplay(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    thread->setSuccess();
    if (thread->getContext() != nullptr)
    {
        return thread->getContext()->getDisplay();
    }
    return EGL_NO_DISPLAY;
}

EGLBoolean EGLAPIENTRY EGL_QueryContext(EGLDisplay dpy,
                                        EGLContext ctx,
                                        EGLint attribute,
                                        EGLint *value)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLContext ctx = 0x%016" PRIxPTR
          ", EGLint attribute = %d, EGLint *value "
          "= 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)ctx, attribute, (uintptr_t)value);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    gl::Context *context  = static_cast<gl::Context *>(ctx);

    ANGLE_EGL_TRY_RETURN(thread, ValidateQueryContext(display, context, attribute, value),
                         "eglQueryContext", GetContextIfValid(display, context), EGL_FALSE);

    QueryContextAttrib(context, attribute, value);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_WaitGL(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = thread->getDisplay();

    ANGLE_EGL_TRY_RETURN(thread, ValidateDisplay(display), "eglWaitGL", GetDisplayIfValid(display),
                         EGL_FALSE);

    // eglWaitGL like calling eglWaitClient with the OpenGL ES API bound. Since we only implement
    // OpenGL ES we can do the call directly.
    ANGLE_EGL_TRY_RETURN(thread, display->waitClient(thread->getContext()), "eglWaitGL",
                         GetDisplayIfValid(display), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_WaitNative(EGLint engine)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLint engine = %d)", engine);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = thread->getDisplay();

    ANGLE_EGL_TRY_RETURN(thread, ValidateWaitNative(display, engine), "eglWaitNative",
                         GetThreadIfValid(thread), EGL_FALSE);

    ANGLE_EGL_TRY_RETURN(thread, display->waitNative(thread->getContext(), engine), "eglWaitNative",
                         GetThreadIfValid(thread), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_SwapBuffers(EGLDisplay dpy, EGLSurface surface)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)surface);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *eglSurface   = (Surface *)surface;

    ANGLE_EGL_TRY_RETURN(thread, ValidateSwapBuffers(thread, display, eglSurface), "eglSwapBuffers",
                         GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    ANGLE_EGL_TRY_RETURN(thread, eglSurface->swap(thread->getContext()), "eglSwapBuffers",
                         GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_CopyBuffers(EGLDisplay dpy,
                                       EGLSurface surface,
                                       EGLNativePixmapType target)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR
          ", EGLNativePixmapType target = "
          "0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)surface, (uintptr_t)target);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *eglSurface   = static_cast<Surface *>(surface);

    ANGLE_EGL_TRY_RETURN(thread, ValidateCopyBuffers(display, eglSurface), "eglCopyBuffers",
                         GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    UNIMPLEMENTED();  // FIXME

    thread->setSuccess();
    return 0;
}

// EGL 1.1
EGLBoolean EGLAPIENTRY EGL_BindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR
          ", EGLint buffer = %d)",
          (uintptr_t)dpy, (uintptr_t)surface, buffer);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display      = static_cast<egl::Display *>(dpy);
    Surface *eglSurface        = static_cast<Surface *>(surface);
    gl::Context *context       = thread->getContext();
    gl::Texture *textureObject = nullptr;

    ANGLE_EGL_TRY_RETURN(
        thread, ValidateBindTexImage(display, eglSurface, surface, buffer, context, &textureObject),
        "eglBindTexImage", GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    if (context)
    {
        ANGLE_EGL_TRY_RETURN(thread, eglSurface->bindTexImage(context, textureObject, buffer),
                             "eglBindTexImage", GetSurfaceIfValid(display, eglSurface), EGL_FALSE);
    }

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_SurfaceAttrib(EGLDisplay dpy,
                                         EGLSurface surface,
                                         EGLint attribute,
                                         EGLint value)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR
          ", EGLint attribute = %d, EGLint "
          "value = %d)",
          (uintptr_t)dpy, (uintptr_t)surface, attribute, value);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *eglSurface   = static_cast<Surface *>(surface);

    ANGLE_EGL_TRY_RETURN(thread, ValidateSurfaceAttrib(display, eglSurface, attribute, value),
                         "eglSurfaceAttrib", GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    SetSurfaceAttrib(eglSurface, attribute, value);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_ReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSurface surface = 0x%016" PRIxPTR
          ", EGLint buffer = %d)",
          (uintptr_t)dpy, (uintptr_t)surface, buffer);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *eglSurface   = static_cast<Surface *>(surface);

    ANGLE_EGL_TRY_RETURN(thread, ValidateReleaseTexImage(display, eglSurface, surface, buffer),
                         "eglReleaseTexImage", GetSurfaceIfValid(display, eglSurface), EGL_FALSE);

    gl::Texture *texture = eglSurface->getBoundTexture();

    if (texture)
    {
        ANGLE_EGL_TRY_RETURN(thread, eglSurface->releaseTexImage(thread->getContext(), buffer),
                             "eglReleaseTexImage", GetSurfaceIfValid(display, eglSurface),
                             EGL_FALSE);
    }

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_SwapInterval(EGLDisplay dpy, EGLint interval)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLint interval = %d)", (uintptr_t)dpy, interval);
    Thread *thread       = egl::GetCurrentThread();
    gl::Context *context = thread->getContext();

    egl::Display *display = static_cast<egl::Display *>(dpy);
    Surface *draw_surface = static_cast<Surface *>(thread->getCurrentDrawSurface());

    ANGLE_EGL_TRY_RETURN(thread, ValidateSwapInterval(display, draw_surface, context),
                         "eglSwapInterval", GetDisplayIfValid(display), EGL_FALSE);

    const egl::Config *surfaceConfig = draw_surface->getConfig();
    EGLint clampedInterval           = std::min(std::max(interval, surfaceConfig->minSwapInterval),
                                      surfaceConfig->maxSwapInterval);

    draw_surface->setSwapInterval(clampedInterval);

    thread->setSuccess();
    return EGL_TRUE;
}

// EGL 1.2
EGLBoolean EGLAPIENTRY EGL_BindAPI(EGLenum api)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLenum api = 0x%X)", api);
    Thread *thread = egl::GetCurrentThread();

    ANGLE_EGL_TRY_RETURN(thread, ValidateBindAPI(api), "eglBindAPI", GetThreadIfValid(thread),
                         EGL_FALSE);

    thread->setAPI(api);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLenum EGLAPIENTRY EGL_QueryAPI(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    EGLenum API = thread->getAPI();

    thread->setSuccess();
    return API;
}

EGLSurface EGLAPIENTRY EGL_CreatePbufferFromClientBuffer(EGLDisplay dpy,
                                                         EGLenum buftype,
                                                         EGLClientBuffer buffer,
                                                         EGLConfig config,
                                                         const EGLint *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR
          ", EGLenum buftype = 0x%X, EGLClientBuffer buffer = 0x%016" PRIxPTR
          ", "
          "EGLConfig config = 0x%016" PRIxPTR ", const EGLint *attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, buftype, (uintptr_t)buffer, (uintptr_t)config, (uintptr_t)attrib_list);
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display   = static_cast<egl::Display *>(dpy);
    Config *configuration   = static_cast<Config *>(config);
    AttributeMap attributes = AttributeMap::CreateFromIntArray(attrib_list);

    ANGLE_EGL_TRY_RETURN(
        thread,
        ValidateCreatePbufferFromClientBuffer(display, buftype, buffer, configuration, attributes),
        "eglCreatePbufferFromClientBuffer", GetDisplayIfValid(display), EGL_NO_SURFACE);

    egl::Surface *surface = nullptr;
    ANGLE_EGL_TRY_RETURN(thread,
                         display->createPbufferFromClientBuffer(configuration, buftype, buffer,
                                                                attributes, &surface),
                         "eglCreatePbufferFromClientBuffer", GetDisplayIfValid(display),
                         EGL_NO_SURFACE);

    return static_cast<EGLSurface>(surface);
}

EGLBoolean EGLAPIENTRY EGL_ReleaseThread(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    Surface *previousDraw         = thread->getCurrentDrawSurface();
    Surface *previousRead         = thread->getCurrentReadSurface();
    gl::Context *previousContext  = thread->getContext();
    egl::Display *previousDisplay = thread->getDisplay();

    // Only call makeCurrent if the context or surfaces have changed.
    if (previousDraw != EGL_NO_SURFACE || previousRead != EGL_NO_SURFACE ||
        previousContext != EGL_NO_CONTEXT)
    {
        if (previousDisplay != EGL_NO_DISPLAY)
        {
            ANGLE_EGL_TRY_RETURN(thread,
                                 previousDisplay->makeCurrent(thread, nullptr, nullptr, nullptr),
                                 "eglReleaseThread", nullptr, EGL_FALSE);
        }

        SetContextCurrent(thread, nullptr);
    }

    thread->setSuccess();
    return EGL_TRUE;
}

EGLBoolean EGLAPIENTRY EGL_WaitClient(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    egl::Display *display = thread->getDisplay();
    gl::Context *context  = thread->getContext();

    ANGLE_EGL_TRY_RETURN(thread, ValidateDisplay(display), "eglWaitClient",
                         GetContextIfValid(display, context), EGL_FALSE);

    ANGLE_EGL_TRY_RETURN(thread, display->waitClient(context), "eglWaitClient",
                         GetContextIfValid(display, context), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

// EGL 1.4
EGLContext EGLAPIENTRY EGL_GetCurrentContext(void)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("()");
    Thread *thread = egl::GetCurrentThread();

    gl::Context *context = thread->getContext();

    thread->setSuccess();
    return static_cast<EGLContext>(context);
}

// EGL 1.5
EGLSync EGLAPIENTRY EGL_CreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR
          ", EGLenum type = 0x%X, const EGLint* attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, type, (uintptr_t)attrib_list);

    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);
    AttributeMap attributes = AttributeMap::CreateFromAttribArray(attrib_list);

    gl::Context *currentContext  = thread->getContext();
    egl::Display *currentDisplay = currentContext ? currentContext->getDisplay() : nullptr;

    ANGLE_EGL_TRY_RETURN(
        thread, ValidateCreateSyncKHR(display, type, attributes, currentDisplay, currentContext),
        "eglCreateSync", GetDisplayIfValid(display), EGL_NO_SYNC);

    egl::Sync *syncObject = nullptr;
    ANGLE_EGL_TRY_RETURN(thread, display->createSync(currentContext, type, attributes, &syncObject),
                         "eglCreateSync", GetDisplayIfValid(display), EGL_NO_SYNC);

    thread->setSuccess();
    return static_cast<EGLSync>(syncObject);
}

EGLBoolean EGLAPIENTRY EGL_DestroySync(EGLDisplay dpy, EGLSync sync)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSync sync = 0x%016" PRIxPTR ")", (uintptr_t)dpy,
          (uintptr_t)sync);

    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);
    egl::Sync *syncObject = static_cast<Sync *>(sync);

    ANGLE_EGL_TRY_RETURN(thread, ValidateDestroySync(display, syncObject), "eglDestroySync",
                         GetDisplayIfValid(display), EGL_FALSE);

    display->destroySync(syncObject);

    thread->setSuccess();
    return EGL_TRUE;
}

EGLint EGLAPIENTRY EGL_ClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSync sync = 0x%016" PRIxPTR
          ", EGLint flags = 0x%X, EGLTime timeout = "
          "%llu)",
          (uintptr_t)dpy, (uintptr_t)sync, flags, static_cast<unsigned long long>(timeout));

    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);
    egl::Sync *syncObject = static_cast<Sync *>(sync);

    ANGLE_EGL_TRY_RETURN(thread, ValidateClientWaitSync(display, syncObject, flags, timeout),
                         "eglClientWaitSync", GetDisplayIfValid(display), EGL_FALSE);

    gl::Context *currentContext = thread->getContext();
    EGLint syncStatus = EGL_FALSE;
    ANGLE_EGL_TRY_RETURN(
        thread, syncObject->clientWait(display, currentContext, flags, timeout, &syncStatus),
        "eglClientWaitSync", GetDisplayIfValid(display), EGL_FALSE);

    thread->setSuccess();
    return syncStatus;
}

EGLBoolean EGLAPIENTRY EGL_GetSyncAttrib(EGLDisplay dpy,
                                         EGLSync sync,
                                         EGLint attribute,
                                         EGLAttrib *value)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLSync sync = 0x%016" PRIxPTR
          ", EGLint attribute = 0x%X, EGLAttrib "
          "*value = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)sync, attribute, (uintptr_t)value);

    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);
    egl::Sync *syncObject = static_cast<Sync *>(sync);

    ANGLE_EGL_TRY_RETURN(thread, ValidateGetSyncAttrib(display, syncObject, attribute, value),
                         "eglGetSyncAttrib", GetDisplayIfValid(display), EGL_FALSE);

    EGLint valueExt;
    ANGLE_EGL_TRY_RETURN(thread, GetSyncAttrib(display, syncObject, attribute, &valueExt),
                         "eglGetSyncAttrib", GetDisplayIfValid(display), EGL_FALSE);
    *value = valueExt;

    thread->setSuccess();
    return EGL_TRUE;
}

EGLImage EGLAPIENTRY EGL_CreateImage(EGLDisplay dpy,
                                     EGLContext ctx,
                                     EGLenum target,
                                     EGLClientBuffer buffer,
                                     const EGLAttrib *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLContext ctx = 0x%016" PRIxPTR
          ", EGLenum target = 0x%X, "
          "EGLClientBuffer buffer = 0x%016" PRIxPTR
          ", const EGLAttrib *attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)ctx, target, (uintptr_t)buffer, (uintptr_t)attrib_list);
    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreateImage unimplemented.", GetDebug(),
                     "eglCreateImage", GetDisplayIfValid(display));
    return EGL_NO_IMAGE;
}

EGLBoolean EGLAPIENTRY EGL_DestroyImage(EGLDisplay dpy, EGLImage image)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLImage image = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)image);
    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);
    Image *eglImage       = static_cast<Image *>(image);

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglDestroyImage unimplemented.", GetDebug(),
                     "eglDestroyImage", GetImageIfValid(display, eglImage));
    return EGL_FALSE;
}

EGLDisplay EGLAPIENTRY EGL_GetPlatformDisplay(EGLenum platform,
                                              void *native_display,
                                              const EGLAttrib *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLenum platform = %d, void* native_display = 0x%016" PRIxPTR
          ", const EGLint* attrib_list = "
          "0x%016" PRIxPTR ")",
          platform, (uintptr_t)native_display, (uintptr_t)attrib_list);
    Thread *thread = egl::GetCurrentThread();

    ANGLE_EGL_TRY_RETURN(thread, ValidateGetPlatformDisplay(platform, native_display, attrib_list),
                         "eglGetPlatformDisplay", GetThreadIfValid(thread), EGL_NO_DISPLAY);

    const auto &attribMap = AttributeMap::CreateFromAttribArray(attrib_list);
    if (platform == EGL_PLATFORM_ANGLE_ANGLE)
    {
        return egl::Display::GetDisplayFromNativeDisplay(
            gl::bitCast<EGLNativeDisplayType>(native_display), attribMap);
    }
    else if (platform == EGL_PLATFORM_DEVICE_EXT)
    {
        Device *eglDevice = static_cast<Device *>(native_display);
        return egl::Display::GetDisplayFromDevice(eglDevice, attribMap);
    }
    else
    {
        UNREACHABLE();
        return EGL_NO_DISPLAY;
    }
}

EGLSurface EGLAPIENTRY EGL_CreatePlatformWindowSurface(EGLDisplay dpy,
                                                       EGLConfig config,
                                                       void *native_window,
                                                       const EGLAttrib *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", void* native_window = 0x%016" PRIxPTR
          ", "
          "const EGLint* attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, (uintptr_t)native_window, (uintptr_t)attrib_list);
    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreatePlatformWindowSurface unimplemented.", GetDebug(),
                     "eglCreatePlatformWindowSurface", GetDisplayIfValid(display));
    return EGL_NO_SURFACE;
}

EGLSurface EGLAPIENTRY EGL_CreatePlatformPixmapSurface(EGLDisplay dpy,
                                                       EGLConfig config,
                                                       void *native_pixmap,
                                                       const EGLAttrib *attrib_list)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy = 0x%016" PRIxPTR ", EGLConfig config = 0x%016" PRIxPTR
          ", void* native_pixmap = 0x%016" PRIxPTR
          ", "
          "const EGLint* attrib_list = 0x%016" PRIxPTR ")",
          (uintptr_t)dpy, (uintptr_t)config, (uintptr_t)native_pixmap, (uintptr_t)attrib_list);
    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);

    UNIMPLEMENTED();
    thread->setError(EglBadDisplay() << "eglCreatePlatformPixmapSurface unimplemented.", GetDebug(),
                     "eglCreatePlatformPixmapSurface", GetDisplayIfValid(display));
    return EGL_NO_SURFACE;
}

EGLBoolean EGLAPIENTRY EGL_WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(EGLDisplay dpy =0x%016" PRIxPTR "p, EGLSync sync = 0x%016" PRIxPTR
          ", EGLint flags = 0x%X)",
          (uintptr_t)dpy, (uintptr_t)sync, flags);

    Thread *thread        = egl::GetCurrentThread();
    egl::Display *display = static_cast<egl::Display *>(dpy);
    gl::Context *context  = thread->getContext();
    egl::Sync *syncObject = static_cast<Sync *>(sync);

    ANGLE_EGL_TRY_RETURN(thread, ValidateWaitSync(display, context, syncObject, flags),
                         "eglWaitSync", GetDisplayIfValid(display), EGL_FALSE);

    gl::Context *currentContext = thread->getContext();
    ANGLE_EGL_TRY_RETURN(thread, syncObject->serverWait(display, currentContext, flags),
                         "eglWaitSync", GetDisplayIfValid(display), EGL_FALSE);

    thread->setSuccess();
    return EGL_TRUE;
}

__eglMustCastToProperFunctionPointerType EGLAPIENTRY EGL_GetProcAddress(const char *procname)
{
    ANGLE_SCOPED_GLOBAL_LOCK();
    EVENT("(const char *procname = \"%s\")", procname);
    Thread *thread = egl::GetCurrentThread();

    ProcEntry *entry =
        std::lower_bound(&g_procTable[0], &g_procTable[g_numProcs], procname, CompareProc);

    thread->setSuccess();

    if (entry == &g_procTable[g_numProcs] || strcmp(entry->first, procname) != 0)
    {
        return nullptr;
    }

    return entry->second;
}
}  // extern "C"
