/*
 * Copyright (C) 2018 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.
 */
#undef NDEBUG

#include "Context.h"
#include "EglConfig.h"
#include "EglContext.h"
#include "EglImage.h"
#include "EglSurface.h"
#include "EglSync.h"

#include <cassert>
#include <cerrno>
#include <cstring>
#include <string>

#include <unistd.h>

#include <GLES/gl.h>
#include <GLES/glext.h>

#include <OpenGLESDispatch/EGLDispatch.h>
#include <OpenGLESDispatch/GLESv1Dispatch.h>
#include <OpenGLESDispatch/GLESv3Dispatch.h>

#include "virgl_hw.h"

#include "RenderControl.h"

#include <hardware/gralloc.h>
#include <hardware/gralloc1.h>
#include <nativebase/nativebase.h>
#include <system/window.h>

static void incRefANWB(android_native_base_t* base) {
    ANativeWindowBuffer* anwb = reinterpret_cast<ANativeWindowBuffer*>(base);
    anwb->layerCount++;
}

static void decRefANWB(android_native_base_t* base) {
    ANativeWindowBuffer* anwb = reinterpret_cast<ANativeWindowBuffer*>(base);
    if (anwb->layerCount > 0) {
        anwb->layerCount--;
        if (anwb->layerCount == 0)
            delete anwb;
    }
}
struct FakeANativeWindowBuffer : public ANativeWindowBuffer {
    FakeANativeWindowBuffer() {
        ANativeWindowBuffer();

        common.incRef = incRefANWB;
        common.decRef = decRefANWB;
        layerCount = 0U;
    }
};

static void incRefANW(android_native_base_t* base) {
    ANativeWindow* anw = reinterpret_cast<ANativeWindow*>(base);
    anw->oem[0]++;
}

static void decRefANW(android_native_base_t* base) {
    ANativeWindow* anw = reinterpret_cast<ANativeWindow*>(base);
    if (anw->oem[0] > 0) {
        anw->oem[0]--;
        if (anw->oem[0] == 0)
            delete anw;
    }
}

static int setSwapInterval(ANativeWindow*, int) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static int dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer) {
    if (!window->oem[1])
        return -EINVAL;
    *buffer = reinterpret_cast<ANativeWindowBuffer*>(window->oem[1]);
    window->oem[1] = 0;
    return 0;
}

static int lockBuffer_DEPRECATED(ANativeWindow*, ANativeWindowBuffer*) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static int queueBuffer_DEPRECATED(ANativeWindow*, ANativeWindowBuffer*) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static int query(const ANativeWindow* window, int what, int* value) {
    switch (what) {
        case NATIVE_WINDOW_WIDTH:
            return static_cast<int>(window->oem[2]);
        case NATIVE_WINDOW_HEIGHT:
            return static_cast<int>(window->oem[3]);
        default:
            return -EINVAL;
    }
}

static int perform(ANativeWindow*, int, ...) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static int cancelBuffer_DEPRECATED(ANativeWindow*, ANativeWindowBuffer*) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static int dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, int* fenceFd) {
    *fenceFd = -1;
    return dequeueBuffer_DEPRECATED(window, buffer);
}

static int queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd) {
    if (fenceFd >= 0)
        close(fenceFd);
    return queueBuffer_DEPRECATED(window, buffer);
}

static int cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd) {
    if (fenceFd >= 0)
        close(fenceFd);
    return cancelBuffer_DEPRECATED(window, buffer);
}

struct FakeANativeWindow : public ANativeWindow {
    FakeANativeWindow(uint32_t width, uint32_t height) {
        ANativeWindow();

        common.incRef = incRefANW;
        common.decRef = decRefANW;
        oem[0] = 0;
        oem[2] = static_cast<intptr_t>(width);
        oem[3] = static_cast<intptr_t>(height);

        this->setSwapInterval = ::setSwapInterval;
        this->dequeueBuffer_DEPRECATED = ::dequeueBuffer_DEPRECATED;
        this->lockBuffer_DEPRECATED = ::lockBuffer_DEPRECATED;
        this->queueBuffer_DEPRECATED = ::queueBuffer_DEPRECATED;
        this->query = ::query;
        this->perform = ::perform;
        this->cancelBuffer_DEPRECATED = ::cancelBuffer_DEPRECATED;
        this->dequeueBuffer = ::dequeueBuffer;
        this->queueBuffer = ::queueBuffer;
        this->cancelBuffer = ::cancelBuffer;
    }
};

// Helpers

static ANativeWindowBuffer* resourceToANWB(Resource* res) {
    ANativeWindowBuffer* buffer = new (std::nothrow) FakeANativeWindowBuffer();
    if (!buffer)
        return nullptr;

    buffer->width = res->args.width;
    buffer->height = res->args.height;
    buffer->stride = res->args.width;
    buffer->handle = reinterpret_cast<const native_handle_t*>(res->args.handle);
    buffer->usage_deprecated = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
                               GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER;
    buffer->usage =
        GRALLOC1_CONSUMER_USAGE_CPU_READ_OFTEN | GRALLOC1_CONSUMER_USAGE_CPU_WRITE_OFTEN |
        GRALLOC1_CONSUMER_USAGE_GPU_TEXTURE | GRALLOC1_PRODUCER_USAGE_CPU_READ_OFTEN |
        GRALLOC1_PRODUCER_USAGE_CPU_WRITE_OFTEN | GRALLOC1_PRODUCER_USAGE_GPU_RENDER_TARGET;

    switch (res->args.format) {
        case VIRGL_FORMAT_B8G8R8A8_UNORM:
            buffer->format = HAL_PIXEL_FORMAT_BGRA_8888;
            break;
        case VIRGL_FORMAT_B5G6R5_UNORM:
            buffer->format = HAL_PIXEL_FORMAT_RGB_565;
            break;
        case VIRGL_FORMAT_R8G8B8A8_UNORM:
            buffer->format = HAL_PIXEL_FORMAT_RGBA_8888;
            break;
        case VIRGL_FORMAT_R8G8B8X8_UNORM:
            buffer->format = HAL_PIXEL_FORMAT_RGBX_8888;
            break;
        default:
            delete buffer;
            return nullptr;
    }

    return buffer;
}

// RenderControl

static GLint rcGetRendererVersion() {
    return 1;  // seems to be hard-coded
}

static EGLint rcGetEGLVersion(void* ctx_, EGLint* major, EGLint* minor) {
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    return s_egl.eglInitialize(rc->dpy, major, minor);
}

static EGLint rcQueryEGLString(void* ctx_, EGLenum name, void* buffer, EGLint bufferSize) {
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    const char* str = s_egl.eglQueryString(rc->dpy, name);
    if (!str)
        str = "";

    if (strlen(str) > (size_t)bufferSize) {
        memset(buffer, 0, bufferSize);
        return -strlen(str);
    }

    char* strOut = static_cast<char*>(buffer);
    strncpy(strOut, str, bufferSize - 1);
    strOut[bufferSize - 1] = 0;

    return strlen(strOut) + 1U;
}

static std::string replaceESVersionString(const std::string& prev, const char* const newver) {
    // Do not touch ES 1.x contexts (they will all be 1.1 anyway)
    if (prev.find("ES-CM") != std::string::npos)
        return prev;

    size_t esStart = prev.find("ES ");
    size_t esEnd = prev.find(" ", esStart + 3);

    // Do not change out-of-spec version strings.
    if (esStart == std::string::npos || esEnd == std::string::npos)
        return prev;

    std::string res = prev.substr(0, esStart + 3);
    res += newver;
    res += prev.substr(esEnd);
    return res;
}

static EGLint rcGetGLString(void* ctx_, EGLenum name, void* buffer, EGLint bufferSize) {
    std::string glStr;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    if (rc->ctx->ctx) {
        const char* str = nullptr;
        switch (rc->ctx->ctx->api) {
            case EglContext::GLESApi::GLESApi_CM:
                str = reinterpret_cast<const char*>(s_gles1.glGetString(name));
                break;
            default:
                str = reinterpret_cast<const char*>(s_gles3.glGetString(name));
                break;
        }
        if (str)
            glStr += str;
    }

    // FIXME: Should probably filter the extensions list like the emulator
    //        does. We need to handle ES2 on ES3 compatibility for older
    //        Android versions, as well as filter out unsupported features.

    if (name == GL_EXTENSIONS) {
        glStr += ChecksumCalculator::getMaxVersionStr();
        glStr += " ";

        // FIXME: Hard-coded to 3.0 for now. We should attempt to detect 3.1.
        glStr += "ANDROID_EMU_gles_max_version_3_0";
        glStr += " ";
    }

    // FIXME: Add support for async swap and the fence_sync extensions

    // We don't support GLDMA; use VIRTGPU_RESOURCE_CREATE and a combination of
    // VIRTGPU_TRANSFER_TO_HOST and VIRTGPU_TRANSFER_FROM_HOST.

    // FIXME: Add support for 'no host error'

    if (name == GL_VERSION)
        glStr = replaceESVersionString(glStr, "3.0");

    int nextBufferSize = glStr.size() + 1;

    if (!buffer || nextBufferSize > bufferSize)
        return -nextBufferSize;

    snprintf((char*)buffer, nextBufferSize, "%s", glStr.c_str());
    return nextBufferSize;
}

static EGLint rcGetNumConfigs(uint32_t* numAttribs) {
    *numAttribs = EglConfig::kNumAttribs;
    return EglConfig::vec.size();
}

static EGLint rcGetConfigs(uint32_t bufSize, GLuint* buffer) {
    size_t configAttribBytes = sizeof(EglConfig::kAttribs);
    size_t nConfigs = EglConfig::vec.size();
    size_t sizeNeeded = configAttribBytes + nConfigs * configAttribBytes;

    if (bufSize < sizeNeeded)
        return -sizeNeeded;

    memcpy(buffer, &EglConfig::kAttribs, configAttribBytes);
    size_t offset = EglConfig::kNumAttribs;
    for (auto const& config : EglConfig::vec) {
        memcpy(&buffer[offset], config->attribs, configAttribBytes);
        offset += EglConfig::kNumAttribs;
    }

    return nConfigs;
}

static EGLint rcChooseConfig(void* ctx_, EGLint* attribs, uint32_t, uint32_t* config_ints,
                             uint32_t configs_size) {
    EGLint num_config;
    EGLConfig configs[configs_size];
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    EGLBoolean ret = s_egl.eglChooseConfig(rc->dpy, attribs, configs, configs_size, &num_config);
    if (!ret)
        num_config = 0;

    if (configs_size) {
        for (EGLint i = 0; i < num_config; i++) {
            config_ints[i] = ~0U;
            EGLint config_id;
            if (s_egl.eglGetConfigAttrib(rc->dpy, configs[i], EGL_CONFIG_ID, &config_id)) {
                for (size_t i = 0; i < EglConfig::vec.size(); i++) {
                    if (EglConfig::vec[i]->attribs[4] == config_id)
                        config_ints[i] = i;
                }
            }
            if (config_ints[i] == ~0U) {
                num_config = 0;
                break;
            }
        }
        if (!num_config)
            memset(config_ints, 0, configs_size * sizeof(uint32_t));
    }

    return num_config;
}

static EGLint rcGetFBParam(EGLint) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static uint32_t rcCreateContext(void* ctx_, uint32_t config_, uint32_t share_, uint32_t glVersion) {
    // clang-format off
    EGLint attrib_list[] = {
        EGL_CONTEXT_CLIENT_VERSION,    0,
        EGL_CONTEXT_MINOR_VERSION_KHR, 0,
        EGL_NONE
    };
    // clang-format on
    switch (glVersion) {
        case EglContext::GLESApi::GLESApi_CM:
            attrib_list[1] = 1;
            attrib_list[3] = 1;
            break;
        case EglContext::GLESApi::GLESApi_2:
            attrib_list[1] = 2;
            break;
        case EglContext::GLESApi::GLESApi_3_0:
            attrib_list[1] = 3;
            break;
        case EglContext::GLESApi::GLESApi_3_1:
            attrib_list[1] = 3;
            attrib_list[3] = 1;
            break;
    }
    if (!attrib_list[1])
        return 0U;

    if (config_ > EglConfig::vec.size())
        return 0U;
    EglConfig const* config = EglConfig::vec[config_];

    EGLContext share_context = EGL_NO_CONTEXT;
    if (share_ > 0) {
        std::map<uint32_t, EglContext*>::iterator context_it;
        context_it = EglContext::map.find(share_);
        if (context_it == EglContext::map.end())
            return 0U;

        EglContext const* share = context_it->second;
        share_context = share->context;
    }

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    EGLContext context_ =
        s_egl.eglCreateContext(rc->dpy, config->config, share_context, attrib_list);
    if (context_ == EGL_NO_CONTEXT)
        return 0U;

    EglContext* context = new (std::nothrow)
        EglContext(context_, rc->ctx->handle, (enum EglContext::GLESApi)glVersion);
    if (!context) {
        s_egl.eglDestroyContext(rc->dpy, context_);
        return 0U;
    }

    return context->id;
}

static void rcDestroyContext(void* ctx_, uint32_t ctx) {
    std::map<uint32_t, EglContext*>::iterator it;
    it = EglContext::map.find(ctx);
    if (it == EglContext::map.end())
        return;

    EglContext* context = it->second;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    s_egl.eglDestroyContext(rc->dpy, context->context);
    context->context = EGL_NO_CONTEXT;
    if (context->disposable())
        delete context;
}

static uint32_t rcCreateWindowSurface(void* ctx_, uint32_t config_, uint32_t width,
                                      uint32_t height) {
    if (config_ > EglConfig::vec.size())
        return 0U;

    EglConfig const* config = EglConfig::vec[config_];

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    EglSurface* surface =
        new (std::nothrow) EglSurface(config->config, rc->ctx->handle, width, height);
    if (!surface)
        return 0U;

    return surface->id;
}

static void rcDestroyWindowSurface(void* ctx_, uint32_t surface_) {
    std::map<uint32_t, EglSurface*>::iterator it;
    it = EglSurface::map.find(surface_);
    if (it == EglSurface::map.end())
        return;

    EglSurface* surface = it->second;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    s_egl.eglDestroySurface(rc->dpy, surface->surface);
    surface->surface = EGL_NO_SURFACE;
    if (surface->disposable()) {
        delete surface->window;
        delete surface;
    }
}

static uint32_t rcCreateColorBuffer(uint32_t, uint32_t, GLenum) {
    // NOTE: This CreateColorBuffer implementation is a no-op which returns a
    //       special surface ID to indicate that a pbuffer surface should be
    //       created. This is necessary because the emulator does not create a
    //       true pbuffer, it always creates a fake one. We don't want this.
    return ~1U;
}

static void rcOpenColorBuffer(uint32_t) {
    printf("%s: not implemented\n", __func__);
}

static void rcCloseColorBuffer(uint32_t) {
    printf("%s: not implemented\n", __func__);
}

static void rcSetWindowColorBuffer(void* ctx_, uint32_t windowSurface, uint32_t colorBuffer) {
    std::map<uint32_t, EglSurface*>::iterator surface_it;
    surface_it = EglSurface::map.find(windowSurface);
    if (surface_it == EglSurface::map.end())
        return;

    EglSurface* surface = surface_it->second;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);

    if (colorBuffer == ~1U) {
        EGLint const attrib_list[] = { EGL_WIDTH, (EGLint)surface->width, EGL_HEIGHT,
                                       (EGLint)surface->height, EGL_NONE };
        assert(surface->surface == EGL_NO_SURFACE && "Pbuffer set twice");
        surface->surface = s_egl.eglCreatePbufferSurface(rc->dpy, surface->config, attrib_list);
    } else {
        std::map<uint32_t, Resource*>::iterator resource_it;
        resource_it = Resource::map.find(colorBuffer);
        if (resource_it == Resource::map.end())
            return;

        Resource* res = resource_it->second;
        ANativeWindowBuffer* buffer = resourceToANWB(res);
        if (!buffer)
            return;

        if (surface->surface == EGL_NO_SURFACE) {
            surface->window =
                new (std::nothrow) FakeANativeWindow(res->args.width, res->args.height);
            if (!surface->window)
                return;

            NativeWindowType native_window = reinterpret_cast<NativeWindowType>(surface->window);
            surface->window->oem[1] = (intptr_t)buffer;
            surface->surface =
                s_egl.eglCreateWindowSurface(rc->dpy, surface->config, native_window, nullptr);
        } else {
            surface->window->oem[1] = (intptr_t)buffer;
            s_egl.eglSwapBuffers(rc->dpy, surface->surface);
        }
    }
}

static int rcFlushWindowColorBuffer(uint32_t windowSurface) {
    std::map<uint32_t, EglSurface*>::iterator it;
    it = EglSurface::map.find(windowSurface);
    return it == EglSurface::map.end() ? -1 : 0;
}

static EGLint rcMakeCurrent(void* ctx_, uint32_t context_, uint32_t drawSurf, uint32_t readSurf) {
    std::map<uint32_t, EglContext*>::iterator context_it;
    context_it = EglContext::map.find(context_);
    if (context_it == EglContext::map.end())
        return EGL_FALSE;

    EglContext* context = context_it->second;

    std::map<uint32_t, EglSurface*>::iterator surface_it;
    surface_it = EglSurface::map.find(drawSurf);
    if (surface_it == EglSurface::map.end())
        return EGL_FALSE;

    EglSurface* draw_surface = surface_it->second;

    surface_it = EglSurface::map.find(readSurf);
    if (surface_it == EglSurface::map.end())
        return EGL_FALSE;

    EglSurface* read_surface = surface_it->second;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);

    EglSurface* old_draw_surface = draw_surface->bind(rc->ctx->handle, false);
    if (old_draw_surface)
        old_draw_surface->unbind(false);

    EglSurface* old_read_surface = read_surface->bind(rc->ctx->handle, true);
    if (old_read_surface)
        old_read_surface->unbind(true);

    EglContext* old_context = context->bind(rc->ctx->handle);
    if (old_context)
        old_context->unbind();

    EGLBoolean ret = s_egl.eglMakeCurrent(rc->dpy, draw_surface->surface, read_surface->surface,
                                          context->context);
    if (!ret) {
        // If eglMakeCurrent fails, it's specified *not* to have unbound the
        // previous contexts or surfaces, but many implementations do. This bug
        // isn't worked around here, and we just assume the implementations obey
        // the spec.
        context->unbind();
        if (old_context)
            old_context->bind(rc->ctx->handle);
        read_surface->unbind(true);
        if (old_read_surface)
            old_read_surface->bind(rc->ctx->handle, true);
        draw_surface->unbind(false);
        if (old_draw_surface)
            old_draw_surface->bind(rc->ctx->handle, false);
    } else {
        if (old_context && old_context->disposable())
            delete old_context;
        if (old_read_surface && old_read_surface->disposable())
            delete old_read_surface;
        if (old_draw_surface && old_draw_surface->disposable())
            delete old_draw_surface;
        rc->ctx->unbind();
        rc->ctx->bind(context);
    }

    return (EGLint)ret;
}

static void rcFBPost(uint32_t) {
    printf("%s: not implemented\n", __func__);
}

static void rcFBSetSwapInterval(void* ctx_, EGLint interval) {
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    s_egl.eglSwapInterval(rc->dpy, interval);
}

static void rcBindTexture(void* ctx_, uint32_t colorBuffer) {
    std::map<uint32_t, Resource*>::iterator it;
    it = Resource::map.find(colorBuffer);
    if (it == Resource::map.end())
        return;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    Resource* res = it->second;
    if (!res->image) {
        ANativeWindowBuffer* buffer = resourceToANWB(res);
        if (!buffer)
            return;

        EGLClientBuffer client_buffer = static_cast<EGLClientBuffer>(buffer);
        EGLImageKHR image = s_egl.eglCreateImageKHR(
            rc->dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, client_buffer, nullptr);
        if (image == EGL_NO_IMAGE_KHR)
            return;

        EglImage* img = new (std::nothrow) EglImage(rc->dpy, image, s_egl.eglDestroyImageKHR);
        if (!img) {
            s_egl.eglDestroyImageKHR(rc->dpy, image);
            return;
        }

        // FIXME: House keeping, because we won't get asked to delete the image
        //        object otherwise, so we need to keep a reference to it..
        res->image = img;
    }

    if (rc->ctx->ctx->api == EglContext::GLESApi::GLESApi_CM) {
        // FIXME: Unconditional use of GL_TEXTURE_2D here is wrong
        s_gles1.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, res->image->image);
    } else {
        // FIXME: Unconditional use of GL_TEXTURE_2D here is wrong
        s_gles3.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, res->image->image);
    }
}

static void rcBindRenderbuffer(void* ctx_, uint32_t colorBuffer) {
    std::map<uint32_t, Resource*>::iterator it;
    it = Resource::map.find(colorBuffer);
    if (it == Resource::map.end())
        return;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    Resource* res = it->second;
    if (!res->image) {
        ANativeWindowBuffer* buffer = resourceToANWB(res);
        if (!buffer)
            return;

        EGLClientBuffer client_buffer = static_cast<EGLClientBuffer>(buffer);
        EGLImageKHR image = s_egl.eglCreateImageKHR(
            rc->dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, client_buffer, nullptr);
        if (image == EGL_NO_IMAGE_KHR)
            return;

        EglImage* img = new (std::nothrow) EglImage(rc->dpy, image, s_egl.eglDestroyImageKHR);
        if (!img) {
            s_egl.eglDestroyImageKHR(rc->dpy, image);
            return;
        }

        // FIXME: House keeping, because we won't get asked to delete the image
        //        object otherwise, so we need to keep a reference to it..
        res->image = img;
    }

    if (rc->ctx->ctx->api == EglContext::GLESApi::GLESApi_CM) {
        s_gles1.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER_OES, res->image->image);
    } else {
        s_gles3.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER_OES, res->image->image);
    }
}

static EGLint rcColorBufferCacheFlush(uint32_t, EGLint, int) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static void rcReadColorBuffer(uint32_t, GLint, GLint, GLint, GLint, GLenum, GLenum, void*) {
    printf("%s: not implemented\n", __func__);
}

static int rcUpdateColorBuffer(uint32_t, GLint, GLint, GLint, GLint, GLenum, GLenum, void*) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static int rcOpenColorBuffer2(uint32_t) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static uint32_t rcCreateClientImage(void* ctx_, uint32_t context_, EGLenum target, GLuint buffer_) {
    std::map<uint32_t, EglContext*>::iterator it;
    it = EglContext::map.find(context_);
    if (it == EglContext::map.end())
        return 0U;

    EglContext* context = it->second;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    EGLClientBuffer buffer = reinterpret_cast<EGLClientBuffer>(buffer_);
    EGLImageKHR image = s_egl.eglCreateImageKHR(rc->dpy, context, target, buffer, nullptr);
    EglImage* img = new (std::nothrow) EglImage(rc->dpy, image, s_egl.eglDestroyImageKHR);
    if (!img) {
        s_egl.eglDestroyImageKHR(rc->dpy, image);
        return 0U;
    }

    return img->id;
}

static int rcDestroyClientImage(uint32_t image_) {
    std::map<uint32_t, EglImage*>::iterator it;
    it = EglImage::map.find(image_);
    if (it == EglImage::map.end())
        return EGL_FALSE;

    EglImage* image = it->second;

    delete image;
    return EGL_TRUE;
}

static void rcSelectChecksumHelper(void* ctx_, uint32_t protocol, uint32_t) {
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    rc->ctx->checksum_calc.setVersion(protocol);
}

static void rcCreateSyncKHR(void* ctx_, EGLenum type, EGLint* attribs, uint32_t, int,
                            uint64_t* glsync_out, uint64_t* syncthread_out) {
    *syncthread_out = 0ULL;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    EGLSyncKHR sync = s_egl.eglCreateSyncKHR(rc->dpy, type, attribs);
    if (sync == EGL_NO_SYNC_KHR) {
        *glsync_out = 0ULL;
        return;
    }

    EglSync* syn = new (std::nothrow) EglSync(sync);
    if (!syn) {
        s_egl.eglDestroySyncKHR(rc->dpy, sync);
        *glsync_out = 0ULL;
        return;
    }

    *glsync_out = syn->id;
}

static EGLint rcClientWaitSyncKHR(void* ctx_, uint64_t sync_, EGLint flags, uint64_t timeout) {
    std::map<uint64_t, EglSync*>::iterator it;
    it = EglSync::map.find(sync_);
    if (it == EglSync::map.end())
        return EGL_CONDITION_SATISFIED_KHR;

    EglSync* sync = it->second;
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    return s_egl.eglClientWaitSyncKHR(rc->dpy, sync->sync, flags, timeout);
}

static void rcFlushWindowColorBufferAsync(uint32_t windowSurface) {
    // No-op
}

static int rcDestroySyncKHR(void* ctx_, uint64_t sync_) {
    std::map<uint64_t, EglSync*>::iterator it;
    it = EglSync::map.find(sync_);
    if (it == EglSync::map.end())
        return EGL_FALSE;

    EglSync* sync = it->second;
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    return s_egl.eglDestroySyncKHR(rc->dpy, sync->sync);
}

static void rcSetPuid(void* ctx_, uint64_t proto) {
    union {
        uint64_t proto;
        struct {
            int pid;
            int tid;
        } id;
    } puid;

    puid.proto = proto;

    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    rc->ctx->setPidTid(puid.id.pid, puid.id.tid);
}

static int rcUpdateColorBufferDMA(uint32_t, GLint, GLint, GLint, GLint, GLenum, GLenum, void*,
                                  uint32_t) {
    printf("%s: not implemented\n", __func__);
    return 0;
}

static uint32_t rcCreateColorBufferDMA(uint32_t, uint32_t, GLenum, int) {
    printf("%s: not implemented\n", __func__);
    return 0U;
}

static void rcWaitSyncKHR(void* ctx_, uint64_t sync_, EGLint flags) {
    std::map<uint64_t, EglSync*>::iterator it;
    it = EglSync::map.find(sync_);
    if (it == EglSync::map.end())
        return;

    EglSync* sync = it->second;
    RenderControl* rc = static_cast<RenderControl*>(ctx_);
    // FIXME: No eglWaitSyncKHR support in SwiftShader
    //        This call will BLOCK when it should be asynchronous!
    s_egl.eglClientWaitSyncKHR(rc->dpy, sync->sync, flags, EGL_FOREVER_KHR);
}

RenderControl::RenderControl(Context* ctx_, EGLDisplay dpy_) {
    rcGetRendererVersion = ::rcGetRendererVersion;
    rcGetEGLVersion_dec = ::rcGetEGLVersion;
    rcQueryEGLString_dec = ::rcQueryEGLString;
    rcGetGLString_dec = ::rcGetGLString;
    rcGetNumConfigs = ::rcGetNumConfigs;
    rcGetConfigs = ::rcGetConfigs;
    rcChooseConfig_dec = ::rcChooseConfig;
    rcGetFBParam = ::rcGetFBParam;
    rcCreateContext_dec = ::rcCreateContext;
    rcDestroyContext_dec = ::rcDestroyContext;
    rcCreateWindowSurface_dec = ::rcCreateWindowSurface;
    rcDestroyWindowSurface_dec = ::rcDestroyWindowSurface;
    rcCreateColorBuffer = ::rcCreateColorBuffer;
    rcOpenColorBuffer = ::rcOpenColorBuffer;
    rcCloseColorBuffer = ::rcCloseColorBuffer;
    rcSetWindowColorBuffer_dec = ::rcSetWindowColorBuffer;
    rcFlushWindowColorBuffer = ::rcFlushWindowColorBuffer;
    rcMakeCurrent_dec = ::rcMakeCurrent;
    rcFBPost = ::rcFBPost;
    rcFBSetSwapInterval_dec = ::rcFBSetSwapInterval;
    rcBindTexture_dec = ::rcBindTexture;
    rcBindRenderbuffer_dec = ::rcBindRenderbuffer;
    rcColorBufferCacheFlush = ::rcColorBufferCacheFlush;
    rcReadColorBuffer = ::rcReadColorBuffer;
    rcUpdateColorBuffer = ::rcUpdateColorBuffer;
    rcOpenColorBuffer2 = ::rcOpenColorBuffer2;
    rcCreateClientImage_dec = ::rcCreateClientImage;
    rcDestroyClientImage = ::rcDestroyClientImage;
    rcSelectChecksumHelper_dec = ::rcSelectChecksumHelper;
    rcCreateSyncKHR_dec = ::rcCreateSyncKHR;
    rcClientWaitSyncKHR_dec = ::rcClientWaitSyncKHR;
    rcFlushWindowColorBufferAsync = ::rcFlushWindowColorBufferAsync;
    rcDestroySyncKHR_dec = ::rcDestroySyncKHR;
    rcSetPuid_dec = ::rcSetPuid;
    rcUpdateColorBufferDMA = ::rcUpdateColorBufferDMA;
    rcCreateColorBufferDMA = ::rcCreateColorBufferDMA;
    rcWaitSyncKHR_dec = ::rcWaitSyncKHR;

    dpy = dpy_;
    ctx = ctx_;
}