/*
* Copyright (C) 2011 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.
*/
#ifdef _WIN32
#undef EGLAPI
#define EGLAPI
#define EGLAPIENTRY
#endif

#include <GLcommon/GLESmacros.h>
#include "GLcommon/GLEScontext.h"
#include "GLcommon/GLutils.h"
#include "GLcommon/TextureData.h"
#include "GLcommon/TextureUtils.h"
#include "GLcommon/TranslatorIfaces.h"
#include "ThreadInfo.h"
#include "aemu/base/synchronization/Lock.h"
#include "aemu/base/files/Stream.h"
#include "aemu/base/system/System.h"
#include "aemu/base/SharedLibrary.h"
#include "host-common/GfxstreamFatalError.h"
#include "host-common/emugl_vm_operations.h"
#include "host-common/logging.h"

#include "EglWindowSurface.h"
#include "EglPbufferSurface.h"
#include "EglGlobalInfo.h"
#include "EglThreadInfo.h"
#include "EglValidate.h"
#include "EglDisplay.h"
#include "EglContext.h"
#include "EglConfig.h"
#include "EglOsApi.h"
#include "ClientAPIExts.h"

#include <EGL/egl.h>
#include <EGL/eglext.h>

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#define MAJOR          1
#define MINOR          4

using emugl::ABORT_REASON_OTHER;
using emugl::FatalError;

//declarations

namespace translator {
namespace egl {

ImagePtr getEGLImage(unsigned int imageId);
GLEScontext* getGLESContext();
GlLibrary* getGlLibrary();
void* getProcAddressFromEGL(const char*);
static bool createAndBindAuxiliaryContext(
    EGLContext* context_out, EGLSurface* surface_out);
static bool unbindAndDestroyAuxiliaryContext(
    EGLContext context, EGLSurface surface);
static bool bindAuxiliaryContext(
    EGLContext context, EGLSurface surface);
static bool unbindAuxiliaryContext();

} // namespace translator
} // namespace egl

#define tls_thread  EglThreadInfo::get()

EglGlobalInfo* g_eglInfo = NULL;
android::base::Lock  s_eglLock;
android::base::Lock  s_surfaceDestroyLock;

void initGlobalInfo()
{
    android::base::AutoLock mutex(s_eglLock);
    if (!g_eglInfo) {
        g_eglInfo = EglGlobalInfo::getInstance();
    }
}

static const EGLiface s_eglIface = {
    .getGLESContext = translator::egl::getGLESContext,
    .getEGLImage = translator::egl::getEGLImage,
    .eglGetGlLibrary = translator::egl::getGlLibrary,
    .createAndBindAuxiliaryContext = translator::egl::createAndBindAuxiliaryContext,
    .unbindAndDestroyAuxiliaryContext = translator::egl::unbindAndDestroyAuxiliaryContext,
    .bindAuxiliaryContext = translator::egl::bindAuxiliaryContext,
    .unbindAuxiliaryContext = translator::egl::unbindAuxiliaryContext,
    .getProcAddress = translator::egl::getProcAddressFromEGL,
};

static void initGLESx(GLESVersion version) {
    const GLESiface* iface = g_eglInfo->getIface(version);
    if (!iface) {
        ERR("EGL failed to initialize GLESv%d; incompatible interface", version);
        return;
    }
    iface->initGLESx(EglGlobalInfo::isEgl2Egl());
}

/*****************************************  supported extensions  ***********************************************************************/

namespace translator {
namespace egl {

EGLAPI EGLImageKHR EGLAPIENTRY eglCreateImageKHR(EGLDisplay display, EGLContext context, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImageKHR(EGLDisplay display, EGLImageKHR image);
EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateSyncKHR(EGLDisplay display, EGLenum type, const EGLint* attribs);
EGLAPI EGLint EGLAPIENTRY eglClientWaitSyncKHR(EGLDisplay display, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout);
EGLAPI EGLBoolean EGLAPIENTRY eglDestroySyncKHR(EGLDisplay display, EGLSyncKHR sync);
EGLAPI EGLint EGLAPIENTRY eglGetMaxGLESVersion(EGLDisplay display);
EGLAPI EGLint EGLAPIENTRY eglWaitSyncKHR(EGLDisplay display, EGLSyncKHR sync, EGLint flags);
EGLAPI void EGLAPIENTRY eglBlitFromCurrentReadBufferANDROID(EGLDisplay display, EGLImageKHR image);
EGLAPI void* EGLAPIENTRY eglSetImageFenceANDROID(EGLDisplay display, EGLImageKHR image);
EGLAPI void EGLAPIENTRY eglWaitImageFenceANDROID(EGLDisplay display, void* fence);
EGLAPI void EGLAPIENTRY eglAddLibrarySearchPathANDROID(const char* path);
EGLAPI EGLBoolean EGLAPIENTRY eglQueryVulkanInteropSupportANDROID(void);
EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttribKHR(EGLDisplay display, EGLSyncKHR sync, EGLint attribute, EGLint *value);
EGLAPI EGLBoolean EGLAPIENTRY eglSetNativeTextureDecompressionEnabledANDROID(EGLDisplay display, EGLBoolean enabled);
EGLAPI EGLBoolean EGLAPIENTRY eglSetProgramBinaryLinkStatusEnabledANDROID(EGLDisplay display, EGLBoolean enabled);

EGLAPI EGLBoolean EGLAPIENTRY eglSaveConfig(EGLDisplay display, EGLConfig config, EGLStreamKHR stream);
EGLAPI EGLConfig EGLAPIENTRY eglLoadConfig(EGLDisplay display, EGLStreamKHR stream);

EGLAPI EGLBoolean EGLAPIENTRY eglPreSaveContext(EGLDisplay display, EGLContext contex, EGLStreamKHR stream);
EGLAPI EGLBoolean EGLAPIENTRY eglSaveContext(EGLDisplay display, EGLContext contex, EGLStreamKHR stream);
EGLAPI EGLBoolean EGLAPIENTRY eglPostSaveContext(EGLDisplay display, EGLContext context, EGLStreamKHR stream);
EGLAPI EGLContext EGLAPIENTRY eglLoadContext(EGLDisplay display, const EGLint *attrib_list, EGLStreamKHR stream);

EGLAPI EGLBoolean EGLAPIENTRY eglSaveAllImages(EGLDisplay display,
                                               EGLStreamKHR stream,
                                               const void* textureSaver);
EGLAPI EGLBoolean EGLAPIENTRY eglLoadAllImages(EGLDisplay display,
                                               EGLStreamKHR stream,
                                               const void* textureLoader);
EGLAPI EGLBoolean EGLAPIENTRY eglPostLoadAllImages(EGLDisplay display, EGLStreamKHR stream);
EGLAPI void EGLAPIENTRY eglUseOsEglApi(EGLBoolean enable, EGLBoolean nullEgl);
EGLAPI void EGLAPIENTRY eglSetMaxGLESVersion(EGLint version);
EGLAPI void EGLAPIENTRY eglFillUsages(void* usages);

EGLAPI EGLDisplay EGLAPIENTRY eglGetNativeDisplayANDROID(EGLDisplay);
EGLAPI EGLContext EGLAPIENTRY eglGetNativeContextANDROID(EGLDisplay, EGLContext);
EGLAPI EGLImage EGLAPIENTRY eglGetNativeImageANDROID(EGLDisplay, EGLImage);
EGLAPI EGLBoolean EGLAPIENTRY eglSetImageInfoANDROID(EGLDisplay, EGLImage, EGLint, EGLint, EGLint);
EGLAPI EGLImage EGLAPIENTRY eglImportImageANDROID(EGLDisplay, EGLImage);

EGLint eglDebugMessageControlKHR(EGLDEBUGPROCKHR callback, const EGLAttrib * attrib_list);

} // namespace translator
} // namespace egl

namespace translator {
namespace egl {

static const ExtensionDescriptor s_eglExtensions[] = {
        {"eglCreateImageKHR" ,
                (__eglMustCastToProperFunctionPointerType)eglCreateImageKHR },
        {"eglDestroyImageKHR",
                (__eglMustCastToProperFunctionPointerType)eglDestroyImageKHR },
        {"eglCreateSyncKHR" ,
                (__eglMustCastToProperFunctionPointerType)eglCreateSyncKHR },
        {"eglClientWaitSyncKHR",
                (__eglMustCastToProperFunctionPointerType)eglClientWaitSyncKHR },
        {"eglDestroySyncKHR",
                (__eglMustCastToProperFunctionPointerType)eglDestroySyncKHR },
        {"eglGetMaxGLESVersion",
                (__eglMustCastToProperFunctionPointerType)eglGetMaxGLESVersion },
        {"eglWaitSyncKHR",
                (__eglMustCastToProperFunctionPointerType)eglWaitSyncKHR },
        {"eglBlitFromCurrentReadBufferANDROID",
                (__eglMustCastToProperFunctionPointerType)eglBlitFromCurrentReadBufferANDROID },
        {"eglSetImageFenceANDROID",
                (__eglMustCastToProperFunctionPointerType)eglSetImageFenceANDROID },
        {"eglWaitImageFenceANDROID",
                (__eglMustCastToProperFunctionPointerType)eglWaitImageFenceANDROID },
        {"eglAddLibrarySearchPathANDROID",
                (__eglMustCastToProperFunctionPointerType)eglAddLibrarySearchPathANDROID },
        {"eglQueryVulkanInteropSupportANDROID",
                (__eglMustCastToProperFunctionPointerType)eglQueryVulkanInteropSupportANDROID },
        {"eglGetSyncAttribKHR",
                (__eglMustCastToProperFunctionPointerType)eglGetSyncAttribKHR },
        {"eglSetNativeTextureDecompressionEnabledANDROID",
                (__eglMustCastToProperFunctionPointerType)eglSetNativeTextureDecompressionEnabledANDROID },
        {"eglSetProgramBinaryLinkStatusEnabledANDROID",
                (__eglMustCastToProperFunctionPointerType)eglSetProgramBinaryLinkStatusEnabledANDROID },
};

static const int s_eglExtensionsSize =
        sizeof(s_eglExtensions) / sizeof(ExtensionDescriptor);

} // namespace translator
} // namespace egl

/****************************************************************************************************************************************/
//macros for accessing global egl info & tls objects

extern "C" {
}

namespace translator {
namespace egl {

#define CURRENT_THREAD() do {} while (0);

#define RETURN_ERROR(ret,err)                                \
        CURRENT_THREAD()                                     \
        if(tls_thread->getError() == EGL_SUCCESS) {          \
          tls_thread->setError(err);                         \
        }                                                    \
        return ret;

#define VALIDATE_DISPLAY_RETURN(EGLDisplay, ret)                \
    MEM_TRACE_IF(strncmp(__FUNCTION__, "egl", 3) == 0, "EMUGL") \
    EglDisplay* dpy = g_eglInfo->getDisplay(EGLDisplay);        \
    if (!dpy) {                                                 \
        RETURN_ERROR(ret, EGL_BAD_DISPLAY);                     \
    }                                                           \
    if (!dpy->isInitialize()) {                                 \
        RETURN_ERROR(ret, EGL_NOT_INITIALIZED);                 \
    }

#define VALIDATE_CONFIG_RETURN(EGLConfig,ret)                \
        EglConfig* cfg = dpy->getConfig(EGLConfig);          \
        if(!cfg) {                                           \
            RETURN_ERROR(ret,EGL_BAD_CONFIG);                \
        }

#define VALIDATE_SURFACE_RETURN(EGLSurface,ret,varName)      \
        SurfacePtr varName = dpy->getSurface(EGLSurface);    \
        if(!varName.get()) {                                 \
            RETURN_ERROR(ret,EGL_BAD_SURFACE);               \
        }

#define VALIDATE_CONTEXT_RETURN(EGLContext,ret)              \
        ContextPtr ctx = dpy->getContext(EGLContext);        \
        if(!ctx.get()) {                                     \
            RETURN_ERROR(ret,EGL_BAD_CONTEXT);               \
        }


#define VALIDATE_DISPLAY(EGLDisplay) \
        VALIDATE_DISPLAY_RETURN(EGLDisplay,EGL_FALSE)

#define VALIDATE_CONFIG(EGLConfig)   \
        VALIDATE_CONFIG_RETURN(EGLConfig,EGL_FALSE)

#define VALIDATE_SURFACE(EGLSurface,varName) \
        VALIDATE_SURFACE_RETURN(EGLSurface,EGL_FALSE,varName)

#define VALIDATE_CONTEXT(EGLContext) \
        VALIDATE_CONTEXT_RETURN(EGLContext,EGL_FALSE)


GLEScontext* getGLESContext()
{
    ThreadInfo* thread  = getThreadInfo();
    return thread->glesContext;
}

GlLibrary* getGlLibrary() {
    return EglGlobalInfo::getInstance()->getOsEngine()->getGlLibrary();
}

void* getProcAddressFromEGL(const char* func) {
    return EglGlobalInfo::getInstance()->getOsEngine()->eglGetProcAddress(func);
}

EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display_id) {
    MEM_TRACE("EMUGL");
    EglDisplay* dpy = NULL;
    EglOS::Display* internalDisplay = NULL;

    initGlobalInfo();

    if ((dpy = g_eglInfo->getDisplayFromDisplayType(display_id))) {
        return dpy;
    }
    if (display_id != EGL_DEFAULT_DISPLAY) {
        return EGL_NO_DISPLAY;
    }
    internalDisplay = g_eglInfo->getDefaultNativeDisplay();
    dpy = g_eglInfo->addDisplay(display_id,internalDisplay);
    if(!dpy) {
        return EGL_NO_DISPLAY;
    }
    return dpy;
}

} // namespace translator
} // namespace egl

#define TRANSLATOR_GETIFACE_NAME "__translator_getIfaces"

extern "C" {
GLESiface* static_translator_glescm_getIfaces(const EGLiface*);
GLESiface* static_translator_glesv2_getIfaces(const EGLiface*);
}; // extern "C"

#define STATIC_TRANSLATOR_GETIFACE_NAME_GLES_CM static_translator_glescm_getIfaces
#define STATIC_TRANSLATOR_GETIFACE_NAME_GLES_V2 static_translator_glesv2_getIfaces

#define LIB_GLES_CM_NAME EMUGL_LIBNAME("GLES_CM_translator")
#define LIB_GLES_V2_NAME EMUGL_LIBNAME("GLES_V2_translator")

static __translator_getGLESIfaceFunc loadIfaces(const char* libName,
                                                char* error,
                                                size_t errorSize) {

    if (!strcmp(libName, LIB_GLES_CM_NAME)) {
        return STATIC_TRANSLATOR_GETIFACE_NAME_GLES_CM;
    }

    if (!strcmp(libName, LIB_GLES_V2_NAME)) {
        return STATIC_TRANSLATOR_GETIFACE_NAME_GLES_V2;
    }

    return 0;
}

namespace translator {
namespace egl {

EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay display, EGLint *major, EGLint *minor) {
    MEM_TRACE("EMUGL");

    initGlobalInfo();

    EglDisplay* dpy = g_eglInfo->getDisplay(display);
    if(!dpy) {
         RETURN_ERROR(EGL_FALSE,EGL_BAD_DISPLAY);
    }

    if(major) *major = MAJOR;
    if(minor) *minor = MINOR;

    __translator_getGLESIfaceFunc func  = NULL;
    int renderableType = EGL_OPENGL_ES_BIT;

    g_eglInfo->setEglIface(&s_eglIface);

    char error[256];
    // When running on top of another GLES library, our GLES1
    // translator uses the GLES library's underlying GLES3.
    if(!g_eglInfo->getIface(GLES_1_1)) {
        func  = loadIfaces(LIB_GLES_CM_NAME, error, sizeof(error));
        if (func) {
            g_eglInfo->setIface(func(&s_eglIface),GLES_1_1);
        } else {
           fprintf(stderr, "%s: Could not find ifaces for GLES CM 1.1 [%s]\n",
                   __FUNCTION__, error);
           return EGL_FALSE;
        }
        initGLESx(GLES_1_1);
    }
    if(!g_eglInfo->getIface(GLES_2_0)) {
        func  = loadIfaces(LIB_GLES_V2_NAME, error, sizeof(error));
        if (func) {
            renderableType |= EGL_OPENGL_ES2_BIT;
            g_eglInfo->setIface(func(&s_eglIface),GLES_2_0);
        } else {
           fprintf(stderr, "%s: Could not find ifaces for GLES 2.0 [%s]\n",
                   __FUNCTION__, error);
        }
        initGLESx(GLES_2_0);
    }
    if(!g_eglInfo->getIface(GLES_3_0)) {
        func  = loadIfaces(LIB_GLES_V2_NAME, error, sizeof(error));
        if (func) {
            renderableType |= EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT;
            g_eglInfo->setIface(func(&s_eglIface),GLES_3_0);
        } else {
           fprintf(stderr, "%s: Could not find ifaces for GLES 3.x [%s]\n",
                   __FUNCTION__, error);
        }
        initGLESx(GLES_3_0);
    }
    if(!g_eglInfo->getIface(GLES_3_1)) {
        func  = loadIfaces(LIB_GLES_V2_NAME, error, sizeof(error));
        if (func) {
            renderableType |= EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT;
            g_eglInfo->setIface(func(&s_eglIface),GLES_3_1);
        } else {
           fprintf(stderr, "%s: Could not find ifaces for GLES 3.x [%s]\n",
                   __FUNCTION__, error);
        }
        initGLESx(GLES_3_1);
    }
    dpy->initialize(renderableType);
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay display) {
    VALIDATE_DISPLAY(display);
    dpy->terminate();
    return EGL_TRUE;
}

EGLAPI const char * EGLAPIENTRY eglQueryString(EGLDisplay display, EGLint name) {
    VALIDATE_DISPLAY(display);
    static const char* version    = "1.4";
    static const char* extensions = "EGL_KHR_image EGL_KHR_image_base "
                                    "EGL_KHR_gl_texture_2D_image "
                                    "EGL_ANDROID_recordable ";
    if(!EglValidate::stringName(name)) {
        RETURN_ERROR(NULL,EGL_BAD_PARAMETER);
    }
    switch(name) {
    case EGL_VENDOR:
        return dpy->getVendorString();
    case EGL_VERSION:
        return version;
    case EGL_EXTENSIONS:
        return extensions;
    }
    return NULL;
}

EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs(EGLDisplay display, EGLConfig *configs,
             EGLint config_size, EGLint *num_config) {
    VALIDATE_DISPLAY(display);
    if(!num_config) {
        RETURN_ERROR(EGL_FALSE,EGL_BAD_PARAMETER);
    }

    if(configs == NULL) {
        *num_config = dpy->nConfigs();
    } else {
        *num_config = dpy->getConfigs(configs,config_size);
    }

    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig(EGLDisplay display, const EGLint *attrib_list,
               EGLConfig *configs, EGLint config_size,
               EGLint *num_config) {
    CHOOSE_CONFIG_DLOG("eglChooseConfig: begin. validating arguments...");

    VALIDATE_DISPLAY(display);
    if(!num_config) {
         CHOOSE_CONFIG_DLOG("num_config is NULL. issue EGL_BAD_PARAMETER");
         RETURN_ERROR(EGL_FALSE,EGL_BAD_PARAMETER);
    }

        //selection defaults
        // NOTE: Some variables below are commented out to reduce compiler warnings.
        // TODO(digit): Look if these variables are really needed or not, and if so
        // fix the code to do it properly.
        EGLint      surface_type       = EGL_WINDOW_BIT;
        EGLint      renderable_type    = EGL_OPENGL_ES_BIT;
        //EGLBoolean  bind_to_tex_rgb    = EGL_DONT_CARE;
        //EGLBoolean  bind_to_tex_rgba   = EGL_DONT_CARE;
        EGLenum     caveat             = EGL_DONT_CARE;
        EGLint      config_id          = EGL_DONT_CARE;
        EGLBoolean  native_renderable  = EGL_DONT_CARE;
        EGLint      native_visual_type = EGL_DONT_CARE;
        //EGLint      max_swap_interval  = EGL_DONT_CARE;
        //EGLint      min_swap_interval  = EGL_DONT_CARE;
        EGLint      trans_red_val      = EGL_DONT_CARE;
        EGLint      trans_green_val    = EGL_DONT_CARE;
        EGLint      trans_blue_val     = EGL_DONT_CARE;
        EGLenum     transparent_type   = EGL_NONE;
        // EGLint      buffer_size        = 0;
        EGLint      red_size           = 0;
        EGLint      green_size         = 0;
        EGLint      blue_size          = 0;
        EGLint      alpha_size         = 0;
        EGLint      alpha_mask_size    = 0;
        EGLint      depth_size         = 0;
        EGLint      frame_buffer_level = 0;
        EGLint      sample_buffers_num = 0;
        EGLint      samples_per_pixel  = 0;
        EGLint      stencil_size       = 0;
        EGLint      conformant         = 0;

        EGLBoolean  recordable_android = EGL_FALSE;
        EGLBoolean  framebuffer_target_android = EGL_DONT_CARE;

        EGLint luminance_size = 0;
        EGLint wanted_buffer_size = EGL_DONT_CARE;

        std::vector<EGLint> wanted_attribs;

    if(!EglValidate::noAttribs(attrib_list)) { //there are attribs
        int i = 0 ;
        bool hasConfigId = false;
        while(attrib_list[i] != EGL_NONE && !hasConfigId) {
#define CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(attrname) \
            CHOOSE_CONFIG_DLOG("EGL_BAD_ATTRIBUTE: " #attrname "defined as 0x%x", attrib_list[i+1]);

            if (attrib_list[i] != EGL_LEVEL &&
                attrib_list[i] != EGL_MATCH_NATIVE_PIXMAP &&
                attrib_list[i + 1] == EGL_DONT_CARE) {
                i+=2;
                continue;
            }

            switch(attrib_list[i]) {
            case EGL_MAX_PBUFFER_WIDTH:
            case EGL_MAX_PBUFFER_HEIGHT:
            case EGL_MAX_PBUFFER_PIXELS:
            case EGL_NATIVE_VISUAL_ID:
                break; //we dont care from those selection crateria
            case EGL_LEVEL:
                if(attrib_list[i+1] == EGL_DONT_CARE) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_LEVEL);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                frame_buffer_level = attrib_list[i+1];
                wanted_attribs.push_back(EGL_LEVEL);
                break;
            case EGL_BUFFER_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_BUFFER_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                wanted_attribs.push_back(EGL_BUFFER_SIZE);
                wanted_buffer_size = attrib_list[i + 1];
                break;
            case EGL_RED_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_RED_SIZE);
                     RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                red_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_RED_SIZE);
                break;
            case EGL_GREEN_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_GREEN_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                green_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_GREEN_SIZE);
                break;
            case EGL_BLUE_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_BLUE_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                blue_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_BLUE_SIZE);
                break;
            case EGL_LUMINANCE_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_LUMINANCE_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                wanted_attribs.push_back(EGL_LUMINANCE_SIZE);
                luminance_size = attrib_list[i + 1];
                break;
            case EGL_ALPHA_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_ALPHA_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                alpha_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_ALPHA_SIZE);
                break;
            case EGL_ALPHA_MASK_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_ALPHA_MASK_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                alpha_mask_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_ALPHA_MASK_SIZE);
                break;
            case EGL_BIND_TO_TEXTURE_RGB:
                if (attrib_list[i+1] != EGL_TRUE &&
                    attrib_list[i+1] != EGL_FALSE) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_BIND_TO_TEXTURE_RGB);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                wanted_attribs.push_back(EGL_BIND_TO_TEXTURE_RGB);
                //bind_to_tex_rgb = attrib_list[i+1];
                break;
            case EGL_BIND_TO_TEXTURE_RGBA:
                if (attrib_list[i+1] != EGL_TRUE &&
                    attrib_list[i+1] != EGL_FALSE) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_BIND_TO_TEXTURE_RGBA);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                wanted_attribs.push_back(EGL_BIND_TO_TEXTURE_RGBA);
                //bind_to_tex_rgba = attrib_list[i+1];
                break;
            case EGL_CONFIG_CAVEAT:
                if(attrib_list[i+1] != EGL_NONE &&
                   attrib_list[i+1] != EGL_SLOW_CONFIG &&
                   attrib_list[i+1] != EGL_NON_CONFORMANT_CONFIG) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_CONFIG_CAVEAT);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                caveat = attrib_list[i+1];
                wanted_attribs.push_back(EGL_CONFIG_CAVEAT);
                break;
            case EGL_CONFORMANT:
                conformant = attrib_list[i+1];
                wanted_attribs.push_back(EGL_CONFORMANT);
                break;
            case EGL_CONFIG_ID:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_CONFIG_ID);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                config_id = attrib_list[i+1];
                hasConfigId = true;
                wanted_attribs.push_back(EGL_CONFIG_ID);
                break;
            case EGL_DEPTH_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_DEPTH_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                depth_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_DEPTH_SIZE);
                break;
            case EGL_MAX_SWAP_INTERVAL:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_MAX_SWAP_INTERVAL);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                //max_swap_interval = attrib_list[i+1];
                wanted_attribs.push_back(EGL_MAX_SWAP_INTERVAL);
                break;
            case EGL_MIN_SWAP_INTERVAL:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_MIN_SWAP_INTERVAL);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                //min_swap_interval = attrib_list[i+1];
                wanted_attribs.push_back(EGL_MIN_SWAP_INTERVAL);
                break;
            case EGL_NATIVE_RENDERABLE:
                if (attrib_list[i+1] != EGL_TRUE &&
                    attrib_list[i+1] != EGL_FALSE) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_NATIVE_RENDERABLE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                native_renderable = attrib_list[i+1];
                wanted_attribs.push_back(EGL_NATIVE_RENDERABLE);
                break;
            case EGL_RENDERABLE_TYPE:
                renderable_type = attrib_list[i+1];
                wanted_attribs.push_back(EGL_RENDERABLE_TYPE);
                break;
            case EGL_NATIVE_VISUAL_TYPE:
                native_visual_type = attrib_list[i+1];
                break;
                if(attrib_list[i+1] < 0 || attrib_list[i+1] > 1 ) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_NATIVE_VISUAL_TYPE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                wanted_attribs.push_back(EGL_NATIVE_VISUAL_TYPE);
            case EGL_SAMPLE_BUFFERS:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_SAMPLE_BUFFERS);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                sample_buffers_num = attrib_list[i+1];
                wanted_attribs.push_back(EGL_SAMPLE_BUFFERS);
                break;
            case EGL_SAMPLES:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_SAMPLES);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                samples_per_pixel = attrib_list[i+1];
                wanted_attribs.push_back(EGL_SAMPLES);
                break;
            case EGL_STENCIL_SIZE:
                if(attrib_list[i+1] < 0) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_STENCIL_SIZE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                stencil_size = attrib_list[i+1];
                wanted_attribs.push_back(EGL_STENCIL_SIZE);
                break;
            case EGL_SURFACE_TYPE:
                surface_type = attrib_list[i+1];
                wanted_attribs.push_back(EGL_SURFACE_TYPE);
                break;
            case EGL_TRANSPARENT_TYPE:
                if(attrib_list[i+1] != EGL_NONE &&
                   attrib_list[i+1] != EGL_TRANSPARENT_RGB ) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_TRANSPARENT_TYPE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                transparent_type = attrib_list[i+1];
                wanted_attribs.push_back(EGL_TRANSPARENT_TYPE);
                break;
            case EGL_TRANSPARENT_RED_VALUE:
                trans_red_val = attrib_list[i+1];
                wanted_attribs.push_back(EGL_TRANSPARENT_RED_VALUE);
                break;
            case EGL_TRANSPARENT_GREEN_VALUE:
                trans_green_val = attrib_list[i+1];
                wanted_attribs.push_back(EGL_TRANSPARENT_GREEN_VALUE);
                break;
            case EGL_TRANSPARENT_BLUE_VALUE:
                trans_blue_val = attrib_list[i+1];
                wanted_attribs.push_back(EGL_TRANSPARENT_BLUE_VALUE);
                break;
            case EGL_COLOR_BUFFER_TYPE:
                if(attrib_list[i+1] != EGL_RGB_BUFFER &&
                   attrib_list[i+1] != EGL_LUMINANCE_BUFFER) {
                    CHOOSE_CONFIG_DLOG_BAD_ATTRIBUTE(EGL_COLOR_BUFFER_TYPE);
                    RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
                }
                wanted_attribs.push_back(EGL_COLOR_BUFFER_TYPE);
                break;
            case EGL_RECORDABLE_ANDROID:
                recordable_android = attrib_list[i+1];
                wanted_attribs.push_back(EGL_RECORDABLE_ANDROID);
                break;
            case EGL_FRAMEBUFFER_TARGET_ANDROID:
                framebuffer_target_android = attrib_list[i+1];
                wanted_attribs.push_back(EGL_FRAMEBUFFER_TARGET_ANDROID);
                break;
            default:
                CHOOSE_CONFIG_DLOG("EGL_BAD_ATTRIBUTE: Unknown attribute key 0x%x", attrib_list[i]);
                RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
            }
            i+=2;
        }
        if(hasConfigId) {
            EglConfig* pConfig = dpy->getConfig(config_id);
            if(pConfig) {
                if(configs) {
                    configs[0]  = static_cast<EGLConfig>(pConfig);
                }
                *num_config = 1;
                CHOOSE_CONFIG_DLOG("Using config id 0x%x. Return EGL_TRUE", config_id);
                return EGL_TRUE;
            } else {
                CHOOSE_CONFIG_DLOG("EGL_BAD_ATTRIBUTE: Using missing config id 0x%x", config_id);
                RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
            }
        }
    }
    EglConfig dummy(red_size,green_size,blue_size,alpha_size,alpha_mask_size,caveat,conformant,depth_size,
                    frame_buffer_level,0,0,0,native_renderable,renderable_type,0,native_visual_type,
                    sample_buffers_num, samples_per_pixel,stencil_size,luminance_size,wanted_buffer_size,
                    surface_type,transparent_type,trans_red_val,trans_green_val,trans_blue_val,recordable_android, framebuffer_target_android,
                    NULL);
    for (size_t i = 0; i < wanted_attribs.size(); i++) {
        dummy.addWantedAttrib(wanted_attribs[i]);
    }
    *num_config = dpy->chooseConfigs(dummy,configs,config_size);
    CHOOSE_CONFIG_DLOG("eglChooseConfig: Success(EGL_TRUE). Num configs returned:%d", *num_config);

    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib(EGLDisplay display, EGLConfig config,
                  EGLint attribute, EGLint *value) {
    VALIDATE_DISPLAY(display);
    VALIDATE_CONFIG(config);
    if(!EglValidate::confAttrib(attribute)){
        RETURN_ERROR(EGL_FALSE, EGL_BAD_ATTRIBUTE);
    }
    return cfg->getConfAttrib(attribute,value)? EGL_TRUE:EGL_FALSE;
}

EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay display, EGLConfig config,
                  EGLNativeWindowType win,
                  const EGLint *attrib_list) {
    VALIDATE_DISPLAY_RETURN(display,EGL_NO_SURFACE);
    VALIDATE_CONFIG_RETURN(config,EGL_NO_SURFACE);

    if(!(cfg->surfaceType() & EGL_WINDOW_BIT)) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_MATCH);
    }
    if(!dpy->nativeType()->isValidNativeWin(win)) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_NATIVE_WINDOW);
    }
    if(!EglValidate::noAttribs(attrib_list)) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ATTRIBUTE);
    }
    if(EglWindowSurface::alreadyAssociatedWithConfig(win)) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ALLOC);
    }

    android::base::AutoLock mutex(s_eglLock);
    unsigned int width,height;
    if(!dpy->nativeType()->checkWindowPixelFormatMatch(
            win, cfg->nativeFormat(), &width, &height)) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ALLOC);
    }
    SurfacePtr wSurface(new EglWindowSurface(dpy, win,cfg,width,height));
    if(!wSurface.get()) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ALLOC);
    }
    return dpy->addSurface(wSurface);
}

EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferSurface(
        EGLDisplay display,
        EGLConfig config,
        const EGLint *attrib_list) {
    VALIDATE_DISPLAY_RETURN(display,EGL_NO_SURFACE);
    VALIDATE_CONFIG_RETURN(config,EGL_NO_SURFACE);
    if(!(cfg->surfaceType() & EGL_PBUFFER_BIT)) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_MATCH);
    }

    SurfacePtr pbSurface(new EglPbufferSurface(dpy,cfg));
    if(!pbSurface.get()) {
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ALLOC);
    }

    if(!EglValidate::noAttribs(attrib_list)) { // There are attribs.
        int i = 0 ;
        while(attrib_list[i] != EGL_NONE) {
            if(!pbSurface->setAttrib(attrib_list[i],attrib_list[i+1])) {
                RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ATTRIBUTE);
            }
            i+=2;
        }
    }

    EGLint width, height, largest, texTarget, texFormat;
    EglPbufferSurface* tmpPbSurfacePtr =
            static_cast<EglPbufferSurface*>(pbSurface.get());

    tmpPbSurfacePtr->getDim(&width, &height, &largest);
    tmpPbSurfacePtr->getTexInfo(&texTarget, &texFormat);

    if(!EglValidate::pbufferAttribs(width,
                                    height,
                                    texFormat == EGL_NO_TEXTURE,
                                    texTarget == EGL_NO_TEXTURE)) {
        //TODO: RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_VALUE); dont have bad_value
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ATTRIBUTE);
    }

    EglOS::PbufferInfo pbinfo;

    pbinfo.width = width;
    pbinfo.height = height;
    pbinfo.largest = largest;
    pbinfo.target = texTarget;
    pbinfo.format = texFormat;

    tmpPbSurfacePtr->getAttrib(EGL_MIPMAP_TEXTURE, &pbinfo.hasMipmap);

    android::base::AutoLock mutex(s_eglLock);
    EglOS::Surface* pb = dpy->nativeType()->createPbufferSurface(
            cfg->nativeFormat(), &pbinfo);
    if(!pb) {
        //TODO: RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_VALUE); dont have bad value
        RETURN_ERROR(EGL_NO_SURFACE,EGL_BAD_ATTRIBUTE);
    }

    tmpPbSurfacePtr->setNativePbuffer(pb);
    return dpy->addSurface(pbSurface);
}

EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface(EGLDisplay display, EGLSurface surface) {
    VALIDATE_DISPLAY(display);
    android::base::AutoLock mutex(s_eglLock);
    SurfacePtr srfc = dpy->getSurface(surface);
    if(!srfc.get()) {
        RETURN_ERROR(EGL_FALSE,EGL_BAD_SURFACE);
    }
    g_eglInfo->markSurfaceForDestroy(dpy, surface);
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface(EGLDisplay display, EGLSurface surface,
               EGLint attribute, EGLint *value) {
   VALIDATE_DISPLAY(display);
   VALIDATE_SURFACE(surface,srfc);

   if(!srfc->getAttrib(attribute,value)) {
       RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
   }
   return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSurfaceAttrib(EGLDisplay display, EGLSurface surface,
                EGLint attribute, EGLint value) {
   VALIDATE_DISPLAY(display);
   VALIDATE_SURFACE(surface,srfc);
   if(!srfc->setAttrib(attribute,value)) {
       RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
   }
   return EGL_TRUE;
}

// eglCreateOrLoadContext is the implementation of eglCreateContext and
// eglLoadContext.
// |stream| is the snapshot file to load from when calling from eglLoadContext
// when |stream| is available, config and share group ID will be loaded from stream

static EGLContext eglCreateOrLoadContext(EGLDisplay display, EGLConfig config,
                EGLContext share_context,
                const EGLint *attrib_list,
                android::base::Stream *stream) {
    assert(share_context == EGL_NO_CONTEXT || stream == nullptr);
    VALIDATE_DISPLAY_RETURN(display,EGL_NO_CONTEXT);

    uint64_t shareGroupId = 0;
    EglConfig* cfg = nullptr;
    if (!stream) {
        cfg = dpy->getConfig(config);
        if (!cfg) return EGL_NO_CONTEXT;
    }

    EGLint major_version = 0;
    EGLint minor_version = 0;
    EGLint context_flags = 0;
    EGLint profile_mask = 0;
    EGLint reset_notification_strategy = 0;
    if(!EglValidate::noAttribs(attrib_list)) {
        int i = 0;
        while(attrib_list[i] != EGL_NONE) {
            EGLint attrib_val = attrib_list[i + 1];
            switch(attrib_list[i]) {
            case EGL_CONTEXT_MAJOR_VERSION_KHR:
                major_version = attrib_val;
                break;
            case EGL_CONTEXT_MINOR_VERSION_KHR:
                minor_version = attrib_val;
                break;
            case EGL_CONTEXT_FLAGS_KHR:
                if ((attrib_val | EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR) ||
                    (attrib_val | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR)  ||
                    (attrib_val | EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR)) {
                    context_flags = attrib_val;
                } else {
                    fprintf(stderr, "%s: wrong context flags, return\n", __func__);
                    RETURN_ERROR(EGL_NO_CONTEXT,EGL_BAD_ATTRIBUTE);
                }
                break;
            case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
                if ((attrib_val | EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR) ||
                    (attrib_val | EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR)) {
                    profile_mask = attrib_val;
                } else {
                    fprintf(stderr, "%s: wrong profile mask, return\n", __func__);
                    RETURN_ERROR(EGL_NO_CONTEXT,EGL_BAD_ATTRIBUTE);
                }
                break;
            case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
                switch (attrib_val) {
                case EGL_NO_RESET_NOTIFICATION_KHR:
                case EGL_LOSE_CONTEXT_ON_RESET_KHR:
                    break;
                default:
                    fprintf(stderr, "%s: wrong reset notif strat, return\n", __func__);
                    RETURN_ERROR(EGL_NO_CONTEXT,EGL_BAD_ATTRIBUTE);
                }
                reset_notification_strategy = attrib_val;
                break;
            default:
                fprintf(stderr, "%s: unknown attrib 0x%x\n", __func__, attrib_list[i]);
                RETURN_ERROR(EGL_NO_CONTEXT,EGL_BAD_ATTRIBUTE);
            }
            i+=2;
        }
    }

    // TODO: Investigate these ignored flags and see which are needed
    (void)context_flags;
    (void)reset_notification_strategy;

    GLESVersion glesVersion;
    switch (major_version) {
     case 1:
         glesVersion = GLES_1_1;
         break;
     case 2:
         glesVersion = GLES_2_0;
         break;
     case 3:
         switch (minor_version) {
         case 0:
             glesVersion = GLES_3_0;
             break;
         case 1:
             glesVersion = GLES_3_1;
             break;
         default:
             RETURN_ERROR(EGL_NO_CONTEXT, EGL_BAD_ATTRIBUTE);
             break;
         }
         break;
     default:
         RETURN_ERROR(EGL_NO_CONTEXT, EGL_BAD_ATTRIBUTE);
         break;
    }

    const GLESiface* iface = g_eglInfo->getIface(glesVersion);
    GLEScontext* glesCtx = NULL;
    if(iface) {
        glesCtx = iface->createGLESContext(major_version, minor_version,
                dpy->getGlobalNameSpace(), stream);
    } else { // there is no interface for this gles version
                RETURN_ERROR(EGL_NO_CONTEXT,EGL_BAD_ATTRIBUTE);
    }

    if(share_context != EGL_NO_CONTEXT) {
        ContextPtr sharedCtxPtr = dpy->getContext(share_context);
        if(!sharedCtxPtr.get()) {
            RETURN_ERROR(EGL_NO_CONTEXT,EGL_BAD_CONTEXT);
        }
        shareGroupId = sharedCtxPtr->getShareGroup()->getId();
        assert(shareGroupId);
    }

    android::base::AutoLock mutex(s_eglLock);

    ContextPtr ctx(new EglContext(dpy, shareGroupId, cfg,
                                  glesCtx, glesVersion,
                                  profile_mask,
                                  dpy->getManager(glesVersion),
                                  stream));
    if(ctx->nativeType()) {
        return dpy->addContext(ctx);
    } else {
        iface->deleteGLESContext(glesCtx);
    }

    return EGL_NO_CONTEXT;
}

EGLAPI EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay display, EGLConfig config,
                EGLContext share_context,
                const EGLint *attrib_list) {
    return eglCreateOrLoadContext(display, config, share_context, attrib_list, nullptr);
}

EGLAPI EGLContext EGLAPIENTRY eglLoadContext(EGLDisplay display, const EGLint *attrib_list,
                                             android::base::Stream *stream) {
    return eglCreateOrLoadContext(display, (EGLConfig)0, (EGLContext)0, attrib_list, stream);
}

EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay display, EGLContext context) {
    VALIDATE_DISPLAY(display);
    VALIDATE_CONTEXT(context);

    android::base::AutoLock mutex(s_eglLock);
    dpy->removeContext(context);
    return EGL_TRUE;
}

static void sGetPbufferSurfaceGLProperties(
        EglPbufferSurface* surface,
        EGLint* width, EGLint* height, GLint* multisamples,
        GLint* colorFormat, GLint* depthStencilFormat) {

    assert(width);
    assert(height);
    assert(multisamples);
    assert(colorFormat);
    assert(depthStencilFormat);

    EGLint r, g, b, a, d, s;
    surface->getAttrib(EGL_WIDTH, width);
    surface->getAttrib(EGL_HEIGHT, height);
    surface->getAttrib(EGL_RED_SIZE, &r);
    surface->getAttrib(EGL_GREEN_SIZE, &g);
    surface->getAttrib(EGL_BLUE_SIZE, &b);
    surface->getAttrib(EGL_ALPHA_SIZE, &a);
    surface->getAttrib(EGL_DEPTH_SIZE, &d);
    surface->getAttrib(EGL_STENCIL_SIZE, &s);
    surface->getAttrib(EGL_SAMPLES, multisamples);

    // Currently supported: RGBA8888/RGB888/RGB565/RGBA4/RGB5A1
    if (r == 8 && g == 8 && b == 8 && a == 8) {
        *colorFormat = GL_RGBA8;
    } else if (r == 8 && g == 8 && b == 8 && a == 0) {
        *colorFormat = GL_RGB8;
    } else if (r == 5 && g == 6 && b == 5 && a == 0) {
        *colorFormat = GL_RGB565;
    } else if (r == 4 && g == 4 && b == 4 && a == 4) {
        *colorFormat = GL_RGBA4;
    } else if (r == 5 && g == 5 && b == 5 && a == 1) {
        *colorFormat = GL_RGB5_A1;
    } else {
        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
            << "invalid color format R" << r << "G" << g << "B" << b << "A" << a;
    }

    // Blanket provide 24/8 depth/stencil format for now.
    *depthStencilFormat = GL_DEPTH24_STENCIL8;

    // TODO: Support more if necessary, or even restrict
    // EGL configs from host display to only these ones.
}

EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay display,
                                             EGLSurface draw,
                                             EGLSurface read,
                                             EGLContext context) {
    VALIDATE_DISPLAY(display);

    bool releaseContext = EglValidate::releaseContext(context, read, draw);
    if(!releaseContext && EglValidate::badContextMatch(context, read, draw)) {
        RETURN_ERROR(EGL_FALSE, EGL_BAD_MATCH);
    }

    ThreadInfo* thread = getThreadInfo();
    ContextPtr prevCtx = thread->eglContext;

    if(releaseContext) { //releasing current context
       if(prevCtx.get()) {
           g_eglInfo->getIface(prevCtx->version())->flush();
           if(!dpy->nativeType()->makeCurrent(NULL,NULL,NULL)) {
               RETURN_ERROR(EGL_FALSE,EGL_BAD_ACCESS);
           }
           thread->updateInfo(ContextPtr(),dpy,NULL,ShareGroupPtr(),dpy->getManager(prevCtx->version()));
       }
    } else { //assining new context
        VALIDATE_CONTEXT(context);
        VALIDATE_SURFACE(draw,newDrawSrfc);
        VALIDATE_SURFACE(read,newReadSrfc);

        EglSurface* newDrawPtr = newDrawSrfc.get();
        EglSurface* newReadPtr = newReadSrfc.get();
        ContextPtr  newCtx     = ctx;

        if (newCtx.get() && prevCtx.get()) {
            if (newCtx.get() == prevCtx.get()) {
                if (newDrawPtr == prevCtx->draw().get() &&
                    newReadPtr == prevCtx->read().get()) {
                    // nothing to do
                    return EGL_TRUE;
                }
            }
            else {
                // Make sure previous context is detached from surfaces
                releaseContext = true;
            }
        }

        //surfaces compatibility check
        if(!((*ctx->getConfig()).compatibleWith((*newDrawPtr->getConfig()))) ||
           !((*ctx->getConfig()).compatibleWith((*newReadPtr->getConfig())))) {
            RETURN_ERROR(EGL_FALSE,EGL_BAD_MATCH);
        }

         EglOS::Display* nativeDisplay = dpy->nativeType();
         EglOS::Surface* nativeRead = newReadPtr->native();
         EglOS::Surface* nativeDraw = newDrawPtr->native();
        //checking native window validity
        if(newReadPtr->type() == EglSurface::WINDOW &&
                !nativeDisplay->isValidNativeWin(nativeRead)) {
            RETURN_ERROR(EGL_FALSE,EGL_BAD_NATIVE_WINDOW);
        }
        if(newDrawPtr->type() == EglSurface::WINDOW &&
                !nativeDisplay->isValidNativeWin(nativeDraw)) {
            RETURN_ERROR(EGL_FALSE,EGL_BAD_NATIVE_WINDOW);
        }

        if(prevCtx.get()) {
            g_eglInfo->getIface(prevCtx->version())->flush();
        }

        {
            android::base::AutoLock mutex(s_eglLock);
            if (!dpy->nativeType()->makeCurrent(
                        newReadPtr->native(),
                        newDrawPtr->native(),
                        newCtx->nativeType())) {
                RETURN_ERROR(EGL_FALSE,EGL_BAD_ACCESS);
            }
            //TODO: handle the following errors
            // EGL_BAD_CURRENT_SURFACE , EGL_CONTEXT_LOST  , EGL_BAD_ACCESS

            thread->updateInfo(newCtx,dpy,newCtx->getGlesContext(),newCtx->getShareGroup(),dpy->getManager(newCtx->version()));
            newCtx->setSurfaces(newReadSrfc,newDrawSrfc);
            g_eglInfo->getIface(newCtx->version())->initContext(newCtx->getGlesContext(), newCtx->getShareGroup(),
                                                                dpy->nativeTextureDecompressionEnabled(),
                                                                dpy->programBinaryLinkStatusEnabled());
            g_eglInfo->sweepDestroySurfaces();
        }

        if (newDrawPtr->type() == EglSurface::PBUFFER &&
            newReadPtr->type() == EglSurface::PBUFFER) {

            EglPbufferSurface* tmpPbSurfacePtr =
                static_cast<EglPbufferSurface*>(newDrawPtr);
            EglPbufferSurface* tmpReadPbSurfacePtr =
                static_cast<EglPbufferSurface*>(newReadPtr);

            EGLint width, height, readWidth, readHeight;
            GLint colorFormat, depthStencilFormat, multisamples;
            GLint readColorFormat, readDepthStencilFormat, readMultisamples;

            sGetPbufferSurfaceGLProperties(
                    tmpPbSurfacePtr,
                    &width, &height,
                    &multisamples,
                    &colorFormat, &depthStencilFormat);

            sGetPbufferSurfaceGLProperties(
                    tmpReadPbSurfacePtr,
                    &readWidth, &readHeight,
                    &readMultisamples,
                    &readColorFormat, &readDepthStencilFormat);

            newCtx->getGlesContext()->initDefaultFBO(
                    width, height,
                    colorFormat, depthStencilFormat, multisamples,
                    &tmpPbSurfacePtr->glRboColor,
                    &tmpPbSurfacePtr->glRboDepth,
                    readWidth, readHeight,
                    readColorFormat, readDepthStencilFormat, readMultisamples,
                    &tmpReadPbSurfacePtr->glRboColor,
                    &tmpReadPbSurfacePtr->glRboDepth);
        }

        // Initialize the GLES extension function table used in
        // eglGetProcAddress for the context's GLES version if not
        // yet initialized. We initialize it here to make sure we call the
        // GLES getProcAddress after when a context is bound.
        g_eglInfo->initClientExtFuncTable(newCtx->version());
    }

    // release previous context surface binding
    if(prevCtx.get() && releaseContext) {
        prevCtx->setSurfaces(SurfacePtr(),SurfacePtr());
    }

    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay display, EGLContext context,
               EGLint attribute, EGLint *value) {
    VALIDATE_DISPLAY(display);
    VALIDATE_CONTEXT(context);

    if(!ctx->getAttrib(attribute,value)){
        RETURN_ERROR(EGL_FALSE,EGL_BAD_ATTRIBUTE);
    }
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers(EGLDisplay display, EGLSurface surface) {
    VALIDATE_DISPLAY(display);
    VALIDATE_SURFACE(surface,Srfc);
    ThreadInfo* thread        = getThreadInfo();
    ContextPtr currentCtx    = thread->eglContext;


    //if surface not window return
    if(Srfc->type() != EglSurface::WINDOW){
        RETURN_ERROR(EGL_TRUE,EGL_SUCCESS);
    }

    if(!currentCtx.get() || !currentCtx->usingSurface(Srfc) ||
            !dpy->nativeType()->isValidNativeWin(Srfc.get()->native())) {
        RETURN_ERROR(EGL_FALSE,EGL_BAD_SURFACE);
    }

    dpy->nativeType()->swapBuffers(Srfc->native());
    return EGL_TRUE;
}

EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext(void) {
    MEM_TRACE("EMUGL");
    android::base::AutoLock mutex(s_eglLock);
    ThreadInfo* thread = getThreadInfo();
    EglDisplay* dpy    = static_cast<EglDisplay*>(thread->eglDisplay);
    ContextPtr  ctx    = thread->eglContext;
    if(dpy && ctx.get()){
        // This double check is required because a context might still be current after it is destroyed - in which case
        // its handle should be invalid, that is EGL_NO_CONTEXT should be returned even though the context is current
        EGLContext c = (EGLContext)SafePointerFromUInt(ctx->getHndl());
        if(dpy->getContext(c).get())
        {
            return c;
        }
    }
    return EGL_NO_CONTEXT;
}

EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface(EGLint readdraw) {
    MEM_TRACE("EMUGL");
    android::base::AutoLock mutex(s_eglLock);
    if (!EglValidate::surfaceTarget(readdraw)) {
        return EGL_NO_SURFACE;
    }

    ThreadInfo* thread = getThreadInfo();
    EglDisplay* dpy    = static_cast<EglDisplay*>(thread->eglDisplay);
    ContextPtr  ctx    = thread->eglContext;

    if(dpy && ctx.get()) {
        SurfacePtr surface = readdraw == EGL_READ ? ctx->read() : ctx->draw();
        if(surface.get())
        {
            // This double check is required because a surface might still be
            // current after it is destroyed - in which case its handle should
            // be invalid, that is EGL_NO_SURFACE should be returned even
            // though the surface is current.
            EGLSurface s = (EGLSurface)SafePointerFromUInt(surface->getHndl());
            surface = dpy->getSurface(s);
            if(surface.get())
            {
                return s;
            }
        }
    }
    return EGL_NO_SURFACE;
}

EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void) {
    MEM_TRACE("EMUGL");
    ThreadInfo* thread = getThreadInfo();
    return (thread->eglContext.get()) ? thread->eglDisplay : EGL_NO_DISPLAY;
}

EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI(EGLenum api) {
    MEM_TRACE("EMUGL");
    if(!EglValidate::supportedApi(api)) {
        RETURN_ERROR(EGL_FALSE,EGL_BAD_PARAMETER);
    }
    CURRENT_THREAD();
    tls_thread->setApi(api);
    return EGL_TRUE;
}

EGLAPI EGLenum EGLAPIENTRY eglQueryAPI(void) {
    MEM_TRACE("EMUGL");
    CURRENT_THREAD();
    return tls_thread->getApi();
}

EGLAPI EGLBoolean EGLAPIENTRY eglReleaseThread(void) {
    MEM_TRACE("EMUGL");
    ThreadInfo* thread  = getThreadInfo();
    EglDisplay* dpy     = static_cast<EglDisplay*>(thread->eglDisplay);
    if (!dpy) {
        return EGL_TRUE;
    }
    if (!translator::egl::eglMakeCurrent(dpy,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)) {
        return EGL_FALSE;
    }
    return dpy->releaseThread();
}

EGLAPI void* EGLAPIENTRY
eglGetProcAddress(const char *procname){
    __eglMustCastToProperFunctionPointerType retVal = NULL;

    if(!strncmp(procname,"egl",3)) { //EGL proc
        for(int i=0;i < s_eglExtensionsSize;i++){
            if(strcmp(procname,s_eglExtensions[i].name) == 0){
                retVal = s_eglExtensions[i].address;
                break;
            }
        }
    }
    else {
        // Look at the clientAPI (GLES) supported extension
        // function table.
        retVal = ClientAPIExts::getProcAddress(procname);
    }
    return (void*)retVal;
}

/************************** KHR IMAGE *************************************************************/
ImagePtr getEGLImage(unsigned int imageId)
{
    ThreadInfo* thread  = getThreadInfo();
    EglDisplay* dpy     = static_cast<EglDisplay*>(thread->eglDisplay);
    ContextPtr  ctx     = thread->eglContext;
    if (ctx.get()) {
        const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
        return dpy->getImage(reinterpret_cast<EGLImageKHR>(imageId),
                iface->restoreTexture);
    } else {
        // Maybe this is a native image, so we don't need a current gl context.
        const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
        return dpy->getImage(reinterpret_cast<EGLImageKHR>(imageId),
                iface->restoreTexture);
    }
    return nullptr;
}

EGLAPI EGLImageKHR EGLAPIENTRY eglCreateImageKHR(EGLDisplay display, EGLContext context, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
{
    VALIDATE_DISPLAY(display);

    if (target != EGL_GL_TEXTURE_2D_KHR) {
        // Create image from underlying and add to registry
        EGLImage image = dpy->createNativeImage(dpy->getHostDriverDisplay(), 0, target, buffer, attrib_list);

        if (image == EGL_NO_IMAGE_KHR) {
            return EGL_NO_IMAGE_KHR;
        }

        ImagePtr img( new EglImage() );
        img->isNative = true;
        img->nativeImage = image;
        img->width = 0;
        img->height = 0;
        if (attrib_list) {
            const EGLint* current = attrib_list;
            while (EGL_NONE != *current) {
                switch (*current) {
                    case EGL_WIDTH:
                        img->width = current[1];
                        break;
                    case EGL_HEIGHT:
                        img->height = current[1];
                        break;
                    case EGL_LINUX_DRM_FOURCC_EXT:
                        // TODO: Translate drm fourcc to internal format
                        // img->fourcc = current[1];
                        break;
                }
                current += 2;
            }
        }
        get_emugl_vm_operations().setSkipSnapshotSave(true);
        return dpy->addImageKHR(img);
    }

    ThreadInfo* thread  = getThreadInfo();
    ShareGroupPtr sg = thread->shareGroup;
    if (sg.get() != NULL) {
        NamedObjectPtr globalTexObject = sg->getNamedObject(NamedObjectType::TEXTURE,
                                                            SafeUIntFromPointer(buffer));
        if (!globalTexObject) return EGL_NO_IMAGE_KHR;

        ImagePtr img( new EglImage() );
        if (img.get() != NULL) {
            auto objData = sg->getObjectData(
                    NamedObjectType::TEXTURE, SafeUIntFromPointer(buffer));
            if (!objData) return EGL_NO_IMAGE_KHR;

            TextureData *texData = (TextureData *)objData;
            if(!texData->width || !texData->height) return EGL_NO_IMAGE_KHR;
            img->width = texData->width;
            img->height = texData->height;
            img->border = texData->border;
            img->internalFormat = texData->internalFormat;
            img->globalTexObj = globalTexObject;
            img->format = texData->format;
            img->type = texData->type;
            img->texStorageLevels = texData->texStorageLevels;
            img->saveableTexture = texData->getSaveableTexture();
            img->needRestore = false;
            img->sync = nullptr;
            return dpy->addImageKHR(img);
        }
    }

    return EGL_NO_IMAGE_KHR;
}


EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImageKHR(EGLDisplay display, EGLImageKHR image)
{
    VALIDATE_DISPLAY(display);
    unsigned int imagehndl = SafeUIntFromPointer(image);
    ImagePtr img = getEGLImage(imagehndl);

    if (!img) return EGL_FALSE;

    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);

    if (img->sync) {
        iface->deleteSync((GLsync)img->sync);
        img->sync = nullptr;
    }

    if (img->isNative && !img->isImported) {
        dpy->destroyNativeImage(dpy->getHostDriverDisplay(), img->nativeImage);
    }

    return dpy->destroyImageKHR(image) ? EGL_TRUE:EGL_FALSE;
}


EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint* attrib_list) {
    MEM_TRACE("EMUGL");
    // swiftshader_indirect used to have a bug with eglCreateSyncKHR
    // but it seems to have been fixed now.
    // BUG: 65587659
    // BUG: 246740239
    if (!g_eglInfo->isEgl2EglSyncSafeToUse()) {
        return (EGLSyncKHR)0x42;
    }

    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    GLsync res = iface->fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    return (EGLSyncKHR)res;
}

EGLAPI EGLint EGLAPIENTRY eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout) {
    MEM_TRACE("EMUGL");
    android::base::AutoLock mutex(s_eglLock);
    if (!g_eglInfo->isEgl2EglSyncSafeToUse()) {
        return EGL_CONDITION_SATISFIED_KHR;
    }
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    GLenum gl_wait_result =
        iface->clientWaitSync((GLsync)sync, GL_SYNC_FLUSH_COMMANDS_BIT, timeout);
    EGLint egl_wait_result;

    switch (gl_wait_result) {
    case GL_ALREADY_SIGNALED:
    case GL_CONDITION_SATISFIED:
        egl_wait_result = EGL_CONDITION_SATISFIED_KHR;
        break;
    case GL_TIMEOUT_EXPIRED:
        egl_wait_result = EGL_TIMEOUT_EXPIRED_KHR;
        break;
    case GL_WAIT_FAILED:
        egl_wait_result = EGL_FALSE;
        break;
    default:
        egl_wait_result = EGL_CONDITION_SATISFIED_KHR;
    }
    return egl_wait_result;
}

EGLAPI EGLBoolean EGLAPIENTRY eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync) {
    MEM_TRACE("EMUGL");
    if (!g_eglInfo->isEgl2EglSyncSafeToUse()) {
        return EGL_TRUE;
    }
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    iface->deleteSync((GLsync)sync);
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttribKHR(
    EGLDisplay dpy, EGLSyncKHR sync,
    EGLint attribute, EGLint *value) {
    MEM_TRACE("EMUGL");

    if (!g_eglInfo->isEgl2EglSyncSafeToUse()) {
        switch (attribute) {
            case EGL_SYNC_TYPE_KHR:
                *value = EGL_SYNC_FENCE_KHR;
                break;
            case EGL_SYNC_CONDITION_KHR:
                *value = EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR;
                break;
            case EGL_SYNC_STATUS_KHR: {
                *value = EGL_SIGNALED_KHR;
                break;
            default:
                return EGL_FALSE;
            }
        }
        return EGL_TRUE;
    }

    switch (attribute) {
        // Guest doesn't care about sync type (handled in guest),
        // but host side might care
        case EGL_SYNC_TYPE_KHR:
            *value = EGL_SYNC_FENCE_KHR;
            break;
        case EGL_SYNC_CONDITION_KHR:
            *value = EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR;
            break;
        case EGL_SYNC_STATUS_KHR: {
            const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
            GLint status = -1;
            iface->getSynciv((GLsync)sync, GL_SYNC_STATUS, sizeof(GLint), nullptr, &status);
            switch (status) {
                case GL_UNSIGNALED:
                    *value = EGL_UNSIGNALED_KHR;
                    break;
                case GL_SIGNALED:
                    *value = EGL_SIGNALED_KHR;
                    break;
                default:
                    // error, return EGL_FALSE
                    return EGL_FALSE;
            }
            break;
        }
        default:
            return EGL_FALSE;
    }

    return EGL_TRUE;
}

EGLAPI EGLint EGLAPIENTRY eglGetMaxGLESVersion(EGLDisplay display) {
    // 0: es2 1: es3.0 2: es3.1 3: es3.2
    VALIDATE_DISPLAY_RETURN(display, 0 /* gles2 */);
    return (EGLint)dpy->getMaxGlesVersion();
}

EGLAPI EGLint EGLAPIENTRY eglWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags) {
    MEM_TRACE("EMUGL");
    if (!g_eglInfo->isEgl2EglSyncSafeToUse()) {
        return EGL_TRUE;
    }
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    iface->waitSync((GLsync)sync, 0, -1);
    return EGL_TRUE;
}

EGLAPI void EGLAPIENTRY eglBlitFromCurrentReadBufferANDROID(EGLDisplay dpy, EGLImageKHR image) {
    MEM_TRACE("EMUGL");
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    iface->blitFromCurrentReadBufferANDROID((GLeglImageOES)image);
}

// Creates a fence checkpoint for operations that have happened to |image|.
// Other users of |image| can choose to wait on the resulting return fence so
// that operations on |image| occur in the correct order on the GPU.  For
// example, we might render some objects or upload image data to |image| in
// Thread A, and then in Thread B, read the results. It is not guaranteed that
// the write operations on |image| have finished on the GPU when we start
// reading, so we call eglSetImageFenceANDROID at the end of writing operations
// in Thread A, and then wait on the fence in Thread B.
EGLAPI void* EGLAPIENTRY eglSetImageFenceANDROID(EGLDisplay dpy, EGLImageKHR image) {
    MEM_TRACE("EMUGL");
    unsigned int imagehndl = SafeUIntFromPointer(image);
    ImagePtr img = getEGLImage(imagehndl);
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    if (!img) return iface->fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

    if (img->sync) {
        iface->deleteSync((GLsync)img->sync);
        img->sync = nullptr;
    }

    GLsync res = iface->fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    iface->flush();
    img->sync = res;
    return (void*)res;
}

EGLAPI void EGLAPIENTRY eglWaitImageFenceANDROID(EGLDisplay dpy, void* fence) {
    MEM_TRACE("EMUGL");
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    iface->waitSync((GLsync)fence, 0, -1);
}

EGLAPI void EGLAPIENTRY eglAddLibrarySearchPathANDROID(const char* path) {
    MEM_TRACE("EMUGL");
    android::base::SharedLibrary::addLibrarySearchPath(path);
}

EGLAPI EGLBoolean EGLAPIENTRY eglQueryVulkanInteropSupportANDROID(void) {
    MEM_TRACE("EMUGL");
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    return iface->vulkanInteropSupported() ? EGL_TRUE : EGL_FALSE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSetNativeTextureDecompressionEnabledANDROID(EGLDisplay display, EGLBoolean enabled) {
    MEM_TRACE("EMUGL");
    VALIDATE_DISPLAY_RETURN(display, EGL_FALSE);
    dpy->setNativeTextureDecompressionEnabled(enabled == EGL_TRUE);
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSetProgramBinaryLinkStatusEnabledANDROID(EGLDisplay display, EGLBoolean enabled) {
    MEM_TRACE("EMUGL");
    VALIDATE_DISPLAY_RETURN(display, EGL_FALSE);
    dpy->setProgramBinaryLinkStatusEnabled(enabled == EGL_TRUE);
    return EGL_TRUE;
}

/*********************************************************************************/

EGLAPI EGLBoolean EGLAPIENTRY eglPreSaveContext(EGLDisplay display, EGLContext contex, EGLStreamKHR stream) {
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    assert(iface->saveTexture);
    if (!iface || !iface->saveTexture) return EGL_TRUE;
    VALIDATE_DISPLAY(display);
    VALIDATE_CONTEXT(contex);
    ctx->getShareGroup()->preSave(dpy->getGlobalNameSpace());
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSaveContext(EGLDisplay display, EGLContext contex, EGLStreamKHR stream) {
    VALIDATE_DISPLAY(display);
    VALIDATE_CONTEXT(contex);
    ctx->onSave((android::base::Stream*)stream);
    return EGL_TRUE;
}

EGLAPI EGLContext EGLAPIENTRY eglLoadContext(EGLDisplay display, const EGLint *attrib_list, EGLStreamKHR stream) {
    return eglCreateOrLoadContext(display, (EGLConfig)0, EGL_NO_CONTEXT, attrib_list, (android::base::Stream*)stream);
}

EGLAPI EGLBoolean EGLAPIENTRY eglPostSaveContext(EGLDisplay display, EGLContext context, EGLStreamKHR stream) {
    VALIDATE_DISPLAY(display);
    VALIDATE_CONTEXT(context);
    ctx->postSave((android::base::Stream*)stream);
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSaveConfig(EGLDisplay display,
        EGLConfig config, EGLStreamKHR stream) {
    VALIDATE_DISPLAY(display);
    VALIDATE_CONFIG(config);
    android::base::Stream* stm = static_cast<android::base::Stream*>(stream);
    stm->putBe32(cfg->id());
    return EGL_TRUE;
}

EGLAPI EGLConfig EGLAPIENTRY eglLoadConfig(EGLDisplay display, EGLStreamKHR stream) {
    VALIDATE_DISPLAY(display);
    android::base::Stream* stm = static_cast<android::base::Stream*>(stream);
    EGLint cfgId = stm->getBe32();
    EglConfig* cfg = dpy->getConfig(cfgId);
    if (!cfg) {
        fprintf(stderr,
                "WARNING: EGL config mismatch, fallback to default configs\n");
        cfg = dpy->getDefaultConfig();
    }
    return static_cast<EGLConfig>(cfg);
}

EGLAPI EGLBoolean EGLAPIENTRY eglSaveAllImages(EGLDisplay display,
                                               EGLStreamKHR stream,
                                               const void* textureSaver) {
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    assert(iface->saveTexture);
    if (!iface || !iface->saveTexture)
        return true;
    VALIDATE_DISPLAY(display);
    android::base::Stream* stm = static_cast<android::base::Stream*>(stream);
    iface->preSaveTexture();
    dpy->onSaveAllImages(
            stm,
            *static_cast<const android::snapshot::ITextureSaverPtr*>(textureSaver),
            iface->saveTexture,
            iface->restoreTexture);
    iface->postSaveTexture();
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglLoadAllImages(EGLDisplay display,
                                               EGLStreamKHR stream,
                                               const void* textureLoader) {
    const GLESiface* iface = g_eglInfo->getIface(GLES_2_0);
    assert(iface->createTexture);
    if (!iface || !iface->createTexture)
        return true;
    VALIDATE_DISPLAY(display);
    android::base::Stream* stm = static_cast<android::base::Stream*>(stream);
    dpy->onLoadAllImages(
            stm,
            *static_cast<const android::snapshot::ITextureLoaderPtr*>(textureLoader),
            iface->createTexture);
    return EGL_TRUE;
}

EGLAPI EGLBoolean EGLAPIENTRY eglPostLoadAllImages(EGLDisplay display, EGLStreamKHR stream) {
    VALIDATE_DISPLAY(display);
    android::base::Stream* stm = static_cast<android::base::Stream*>(stream);
    dpy->postLoadAllImages(stm);
    return true;
}

EGLAPI void EGLAPIENTRY eglUseOsEglApi(EGLBoolean enable, EGLBoolean nullEgl) {
    MEM_TRACE("EMUGL");
    EglGlobalInfo::setEgl2Egl(enable, nullEgl == EGL_TRUE);
    EglGlobalInfo::setEgl2EglSyncSafeToUse(EGL_TRUE);
}

EGLAPI void EGLAPIENTRY eglSetMaxGLESVersion(EGLint version) {
    MEM_TRACE("EMUGL");
    // The "version" here follows the convention of eglGetMaxGLESVesion
    // 0: es2 1: es3.0 2: es3.1 3: es3.2
    GLESVersion glesVersion = GLES_2_0;
    switch (version) {
    case 0:
        glesVersion = GLES_2_0;
        break;
    case 1:
        glesVersion = GLES_3_0;
        break;
    case 2:
    case 3: // TODO: GLES 3.2 support?
        glesVersion = GLES_3_1;
        break;
    }

    if (g_eglInfo->getIface(GLES_1_1)) {
        g_eglInfo->getIface(GLES_1_1)->setMaxGlesVersion(glesVersion);
    }
    g_eglInfo->getIface(GLES_2_0)->setMaxGlesVersion(glesVersion);
}

EGLAPI void EGLAPIENTRY eglFillUsages(void* usages) {
    MEM_TRACE("EMUGL");
    // TODO: Figure out better usage metrics interface
    // that doesn't require linking protobuf into Translator
    // if (g_eglInfo->getIface(GLES_1_1) &&
    //         g_eglInfo->getIface(GLES_1_1)->fillGLESUsages) {
    //     g_eglInfo->getIface(GLES_1_1)->fillGLESUsages(
    //         (android_studio::EmulatorGLESUsages*)usages);
    // }
    // if (g_eglInfo->getIface(GLES_2_0) &&
    //         g_eglInfo->getIface(GLES_2_0)->fillGLESUsages) {
    //     g_eglInfo->getIface(GLES_2_0)->fillGLESUsages(
    //         (android_studio::EmulatorGLESUsages*)usages);
    // }
}

EGLAPI EGLDisplay EGLAPIENTRY eglGetNativeDisplayANDROID(EGLDisplay display) {
    VALIDATE_DISPLAY_RETURN(display, (EGLDisplay)0);
    return dpy->getHostDriverDisplay();
}

EGLAPI EGLContext EGLAPIENTRY eglGetNativeContextANDROID(EGLDisplay display, EGLContext context) {
    VALIDATE_DISPLAY_RETURN(display, (EGLContext)0);
    VALIDATE_CONTEXT_RETURN(context, (EGLContext)0);
    return dpy->getNativeContext(context);
}

EGLAPI EGLImage EGLAPIENTRY eglGetNativeImageANDROID(EGLDisplay display, EGLImage image) {
    VALIDATE_DISPLAY_RETURN(display, (EGLImage)0);
    unsigned int imagehndl = SafeUIntFromPointer(image);
    ImagePtr img = getEGLImage(imagehndl);
    if (!img || !img->isNative) return (EGLImage)0;
    return img->nativeImage;
}

EGLAPI EGLBoolean EGLAPIENTRY eglSetImageInfoANDROID(EGLDisplay display, EGLImage image, EGLint width, EGLint height, EGLint internalFormat) {
    VALIDATE_DISPLAY_RETURN(display, EGL_FALSE);
    unsigned int imagehndl = SafeUIntFromPointer(image);
    ImagePtr img = getEGLImage(imagehndl);
    if (!img) {
        fprintf(stderr, "%s: error: Could not find image %p\n", __func__, image);
        return EGL_FALSE;
    }

    img->width = width;
    img->height = height;
    img->internalFormat = internalFormat;
    img->format = getFormatFromInternalFormat(internalFormat);
    img->type = getTypeFromInternalFormat(internalFormat);

    return EGL_TRUE;
}

EGLImage eglImportImageANDROID(EGLDisplay display, EGLImage nativeImage) {
    VALIDATE_DISPLAY_RETURN(display, (EGLImage)0);
	ImagePtr img( new EglImage() );
	img->isNative = true;
	img->isImported = true;
	img->nativeImage = nativeImage;
    return dpy->addImageKHR(img);
}

EGLint eglDebugMessageControlKHR(EGLDEBUGPROCKHR callback, const EGLAttrib* attribs) {
    return EglGlobalInfo::getInstance()->getOsEngine()->eglDebugMessageControlKHR(callback, attribs);
}

static const GLint kAuxiliaryContextAttribsCompat[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};

static const GLint kAuxiliaryContextAttribsCore[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR,
    EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
    EGL_NONE
};

#define NAMESPACED_EGL(f) translator::egl::f

static bool createAndBindAuxiliaryContext(EGLContext* context_out, EGLSurface* surface_out) {
    // create the context
    EGLDisplay dpy = NAMESPACED_EGL(eglGetDisplay)(EGL_DEFAULT_DISPLAY);

    NAMESPACED_EGL(eglBindAPI)(EGL_OPENGL_ES_API);

    static const GLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE,
        EGL_OPENGL_ES2_BIT, EGL_NONE };

    EGLConfig config;
    int numConfigs;
    if (!NAMESPACED_EGL(eglChooseConfig)(dpy, configAttribs, &config, 1, &numConfigs) ||
        numConfigs == 0) {
        fprintf(stderr, "%s: could not find gles 2 config!\n", __func__);
        return false;
    }

    static const EGLint pbufAttribs[] =
        { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
    EGLSurface surface = eglCreatePbufferSurface(dpy, config, pbufAttribs);
    if (!surface) {
        fprintf(stderr, "%s: could not create surface\n", __func__);
        return false;
    }

    EGLContext context =
        NAMESPACED_EGL(eglCreateContext)(dpy, config, EGL_NO_CONTEXT,
            isCoreProfile() ? kAuxiliaryContextAttribsCore :
                              kAuxiliaryContextAttribsCompat);

    if (!NAMESPACED_EGL(eglMakeCurrent)(dpy, surface, surface, context)) {
        fprintf(stderr, "%s: eglMakeCurrent failed\n", __func__);
        return false;
    }

    if (context_out) *context_out = context;
    if (surface_out) *surface_out = surface;

    return true;
}

static bool unbindAndDestroyAuxiliaryContext(EGLContext context, EGLSurface surface) {

    // create the context
    EGLDisplay dpy = NAMESPACED_EGL(eglGetDisplay)(EGL_DEFAULT_DISPLAY);

    if (!NAMESPACED_EGL(eglMakeCurrent)(
            dpy, EGL_NO_SURFACE, EGL_NO_SURFACE,
            EGL_NO_CONTEXT)) {
        fprintf(stderr, "%s: failure to unbind current context!\n",
                __func__);
        return false;
    }


    if (!eglDestroySurface(dpy, surface)) {
        fprintf(stderr, "%s: failure to destroy surface!\n",
                __func__);
        return false;
    }

    if (!eglDestroyContext(dpy, context)) {
        fprintf(stderr, "%s: failure to destroy context!\n",
                __func__);
        return false;
    }

    return true;
}

static bool bindAuxiliaryContext(EGLContext context, EGLSurface surface) {
    // create the context
    EGLDisplay dpy = NAMESPACED_EGL(eglGetDisplay)(EGL_DEFAULT_DISPLAY);

    if (!eglMakeCurrent(dpy, surface, surface, context)) {
        fprintf(stderr, "%s: eglMakeCurrent failed\n", __func__);
        return false;
    }

    return true;
}

static bool unbindAuxiliaryContext() {

    // create the context
    EGLDisplay dpy = NAMESPACED_EGL(eglGetDisplay)(EGL_DEFAULT_DISPLAY);

    if (!eglMakeCurrent(
            dpy, EGL_NO_SURFACE, EGL_NO_SURFACE,
            EGL_NO_CONTEXT)) {
        fprintf(stderr, "%s: failure to unbind current context!\n",
                __func__);
        return false;
    }

    return true;
}

EGLAPI EGLint EGLAPIENTRY eglGetError(void) {
    MEM_TRACE("EMUGL");
    CURRENT_THREAD();
    EGLint err = tls_thread->getError();
    tls_thread->setError(EGL_SUCCESS);
    return err;
}


} // namespace translator
} // namespace egl
