// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/opengl/EGLFunctions.h"

#include <string>
#include <tuple>

#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_split.h"
#include "src/dawn/native/opengl/UtilsEGL.h"

namespace dawn::native::opengl {

namespace {

using VersionPromoted = std::tuple<uint32_t, uint32_t>;
constexpr VersionPromoted EGL1_5 = {1, 5};
constexpr VersionPromoted NeverPromoted = {100000, 0};

// EGL_EXT_client_extensions which was promoted to 1.5 adds the concept of client extensions that
// can be queried with eglGetString(EGL_NO_DISPLAY, EGL_EXTENSIONS). These can be loaded during
// LoadClientProcs instead of LoadDisplayProcs.
enum class ExtType {
    Display,
    Client,
};

struct ExtensionInfo {
    EGLExt index;
    const char* name;
    VersionPromoted versionPromoted;
    ExtType type;
};

static constexpr size_t kExtensionCount = static_cast<size_t>(EGLExt::EnumCount);
static constexpr std::array<ExtensionInfo, kExtensionCount> kExtensionInfos{{
    //
    {EGLExt::ClientExtensions, "EGL_EXT_client_extensions", EGL1_5, ExtType::Client},
    {EGLExt::PlatformBase, "EGL_EXT_platform_base", EGL1_5, ExtType::Client},
    // EGL_KHR_fence_sync cannot be marked as promoted to core due to different function prototypes.
    {EGLExt::FenceSync, "EGL_KHR_fence_sync", NeverPromoted, ExtType::Display},
    {EGLExt::CLEvent2, "EGL_KHR_cl_event2", EGL1_5, ExtType::Display},
    {EGLExt::WaitSync, "EGL_KHR_wait_sync", EGL1_5, ExtType::Display},
    {EGLExt::ImageBase, "EGL_KHR_image_base", EGL1_5, ExtType::Display},
    {EGLExt::GLTexture2DImage, "EGL_KHR_gl_texture_2D_image", EGL1_5, ExtType::Display},
    {EGLExt::GLTexture3DImage, "EGL_KHR_gl_texture_3D_image", EGL1_5, ExtType::Display},
    {EGLExt::GLTextureCubemapImage, "EGL_KHR_gl_texture_cubemap_image", EGL1_5, ExtType::Display},
    {EGLExt::GLRenderBufferImage, "EGL_KHR_gl_renderbuffer_image", EGL1_5, ExtType::Display},
    {EGLExt::CreateContext, "EGL_KHR_create_context", EGL1_5, ExtType::Display},
    {EGLExt::CreateContextRobustness, "EGL_EXT_create_context_robustness", EGL1_5,
     ExtType::Display},
    {EGLExt::GetAllProcAddresses, "EGL_KHR_get_all_proc_addresses", EGL1_5, ExtType::Display},
    {EGLExt::ClientGetAllProcAddresses, "EGL_KHR_client_get_all_proc_addresses", EGL1_5,
     ExtType::Client},
    {EGLExt::GLColorSpace, "EGL_KHR_gl_colorspace", EGL1_5, ExtType::Display},
    {EGLExt::SurfacelessContext, "EGL_KHR_surfaceless_context", EGL1_5, ExtType::Display},
    {EGLExt::NativeFenceSync, "EGL_ANDROID_native_fence_sync", NeverPromoted, ExtType::Display},

    {EGLExt::DisplayTextureShareGroup, "EGL_ANGLE_display_texture_share_group", NeverPromoted,
     ExtType::Display},
    {EGLExt::ReusableSync, "EGL_KHR_reusable_sync", NeverPromoted, ExtType::Display},
    {EGLExt::NoConfigContext, "EGL_KHR_no_config_context", NeverPromoted, ExtType::Display},
    {EGLExt::PixelFormatFloat, "EGL_EXT_pixel_format_float", NeverPromoted, ExtType::Display},
    {EGLExt::GLColorspace, "EGL_KHR_gl_colorspace", NeverPromoted, ExtType::Display},
    {EGLExt::ImageNativeBuffer, "EGL_ANDROID_image_native_buffer", NeverPromoted, ExtType::Display},
    {EGLExt::GetNativeClientBuffer, "EGL_ANDROID_get_native_client_buffer", NeverPromoted,
     ExtType::Display},

    {EGLExt::ANGLECreateContextBackwardsCompatible, "EGL_ANGLE_create_context_backwards_compatible",
     NeverPromoted, ExtType::Display},
    {EGLExt::ANGLECreateContextExtensionsEnabled, "EGL_ANGLE_create_context_extensions_enabled",
     NeverPromoted, ExtType::Display},
    {EGLExt::ANGLEContextVirtualization, "EGL_ANGLE_context_virtualization", NeverPromoted,
     ExtType::Display},
    //
}};

}  // anonymous namespace

#define GET_PROC_WITH_NAME(member, name)                                          \
    do {                                                                          \
        member = reinterpret_cast<decltype(member)>(GetProcAddress(name));        \
        if (member == nullptr) {                                                  \
            return DAWN_INTERNAL_ERROR(std::string("Couldn't get proc ") + name); \
        }                                                                         \
    } while (0)

#define GET_PROC(name) GET_PROC_WITH_NAME(name, "egl" #name)

MaybeError EGLFunctions::LoadClientProcs(EGLGetProcProc getProc) {
    // Load EGL 1.0
    GetProcAddress = getProc;

    GET_PROC(ChooseConfig);
    GET_PROC(CopyBuffers);
    GET_PROC(CreateContext);
    GET_PROC(CreatePbufferSurface);
    GET_PROC(CreatePixmapSurface);
    GET_PROC(CreateWindowSurface);
    GET_PROC(DestroyContext);
    GET_PROC(DestroySurface);
    GET_PROC(GetConfigAttrib);
    GET_PROC(GetConfigs);
    GET_PROC(GetCurrentDisplay);
    GET_PROC(GetCurrentSurface);
    GET_PROC(GetDisplay);
    GET_PROC(GetError);
    GET_PROC(Initialize);
    GET_PROC(MakeCurrent);
    GET_PROC(QueryContext);
    GET_PROC(QueryString);
    GET_PROC(QuerySurface);
    GET_PROC(SwapBuffers);
    GET_PROC(Terminate);
    GET_PROC(WaitGL);
    GET_PROC(WaitNative);

    // Get the EGL client extensions, if they are supported.
    const char* rawExtensions = QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
    if (rawExtensions == nullptr) {
        return {};
    }
    absl::flat_hash_set<std::string_view> extensions = absl::StrSplit(rawExtensions, " ");

    for (const ExtensionInfo& ext : kExtensionInfos) {
        if (extensions.contains(ext.name) && ext.type == ExtType::Client) {
            mExtensions.set(ext.index);
        }
    }

    return LoadClientExtensions();
}

MaybeError EGLFunctions::LoadDisplayProcs(EGLDisplay display) {
    DAWN_ASSERT(GetProcAddress != nullptr);

    // Get the EGL version and extensions.
    {
        EGLint major, minor;
        DAWN_TRY(CheckEGL(*this, Initialize(display, &major, &minor), "eglInitialize"));
        DAWN_INVALID_IF(major != 1, "EGL version (%u) is not 1.", major);
        mMajorVersion = major;
        mMinorVersion = minor;
    }
    {
        const char* rawExtensions = QueryString(display, EGL_EXTENSIONS);
        absl::flat_hash_set<std::string_view> extensions = absl::StrSplit(rawExtensions, " ");

        for (const ExtensionInfo& ext : kExtensionInfos) {
            if (std::tie(mMajorVersion, mMinorVersion) >= ext.versionPromoted ||
                extensions.contains(ext.name)) {
                mExtensions.set(ext.index);
            }
        }
    }

    // EGL 1.1
    if (mMinorVersion >= 1) {
        GET_PROC(BindTexImage);
        GET_PROC(ReleaseTexImage);
        GET_PROC(SurfaceAttrib);
        GET_PROC(SwapInterval);
    }

    // EGL 1.2
    if (mMinorVersion >= 2) {
        GET_PROC(BindAPI);
        GET_PROC(QueryAPI);
        GET_PROC(CreatePbufferFromClientBuffer);
        GET_PROC(ReleaseThread);
        GET_PROC(WaitClient);
    }

    // EGL 1.3 (no new procs)

    // EGL 1.4
    if (mMinorVersion >= 4) {
        GET_PROC(GetCurrentContext);
    }

    // EGL 1.5
    if (mMinorVersion >= 5) {
        GET_PROC(CreateSync);
        GET_PROC(DestroySync);
        GET_PROC(ClientWaitSync);
        GET_PROC(GetSyncAttrib);
        GET_PROC(CreateImage);
        GET_PROC(DestroyImage);
        GET_PROC(GetPlatformDisplay);
        GET_PROC(CreatePlatformWindowSurface);
        GET_PROC(CreatePlatformPixmapSurface);
        GET_PROC(WaitSync);
    } else {
        // Load display extensions that would otherwise be promoted to EGL 1.5.

        if (HasExt(EGLExt::ImageBase)) {
            GET_PROC_WITH_NAME(CreateImage, "eglCreateImageKHR");
            GET_PROC_WITH_NAME(DestroyImage, "eglDestroyImageKHR");
        }

        if (HasExt(EGLExt::WaitSync)) {
            GET_PROC_WITH_NAME(WaitSync, "eglWaitSyncKHR");
        }
    }

    // Load client extensions if they haven't been already.
    if (!HasExt(EGLExt::ClientExtensions)) {
        DAWN_TRY(LoadClientExtensions());
    }

    // Other extensions

    if (HasExt(EGLExt::FenceSync)) {
        GET_PROC_WITH_NAME(ClientWaitSyncKHR, "eglClientWaitSyncKHR");
        GET_PROC_WITH_NAME(CreateSyncKHR, "eglCreateSyncKHR");
        GET_PROC_WITH_NAME(DestroySyncKHR, "eglDestroySyncKHR");
        GET_PROC_WITH_NAME(GetSyncAttribKHR, "eglGetSyncAttribKHR");
    }

    if (HasExt(EGLExt::ReusableSync)) {
        GET_PROC_WITH_NAME(SignalSync, "eglSignalSyncKHR");
    }

    if (HasExt(EGLExt::GetNativeClientBuffer)) {
        GET_PROC_WITH_NAME(GetNativeClientBuffer, "eglGetNativeClientBufferANDROID");
    }

    if (HasExt(EGLExt::NativeFenceSync)) {
        GET_PROC_WITH_NAME(DupNativeFenceFD, "eglDupNativeFenceFDANDROID");
    }

    return {};
}

uint32_t EGLFunctions::GetMajorVersion() const {
    return mMajorVersion;
}

uint32_t EGLFunctions::GetMinorVersion() const {
    return mMinorVersion;
}

bool EGLFunctions::IsAtLeastVersion(uint32_t major, uint32_t minor) const {
    return std::tie(major, minor) >= std::tie(mMajorVersion, mMinorVersion);
}

bool EGLFunctions::HasExt(EGLExt extension) const {
    return mExtensions[extension];
}

MaybeError EGLFunctions::LoadClientExtensions() {
    if (HasExt(EGLExt::PlatformBase) && mMinorVersion < 5) {
        GET_PROC_WITH_NAME(GetPlatformDisplay, "eglGetPlatformDisplayEXT");
        GET_PROC_WITH_NAME(CreatePlatformWindowSurface, "eglCreatePlatformWindowSurfaceEXT");
        GET_PROC_WITH_NAME(CreatePlatformPixmapSurface, "eglCreatePlatformPixmapSurfaceEXT");
    }

    return {};
}

}  // namespace dawn::native::opengl
