// 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/UtilsEGL.h"

#include <string>
#include <vector>

#include "src/dawn/native/opengl/DeviceGL.h"
#include "src/dawn/native/opengl/EGLFunctions.h"
#include "src/dawn/native/opengl/PhysicalDeviceGL.h"
#include "src/utils/compiler.h"

namespace dawn::native::opengl {

namespace {
std::vector<EGLAttrib> ConvertEGLIntParameterListToEGLAttrib(const EGLint* intAttribs) {
    std::vector<EGLAttrib> attribs;
    if (intAttribs) {
        for (const EGLint* curAttrib = intAttribs; *curAttrib != EGL_NONE;
             DAWN_UNSAFE_TODO(curAttrib++)) {
            attribs.push_back(static_cast<EGLAttrib>(*curAttrib));
        }
    }
    attribs.push_back(EGL_NONE);
    return attribs;
}
}  // namespace

const char* EGLErrorAsString(EGLint error) {
    switch (error) {
        case EGL_SUCCESS:
            return "EGL_SUCCESS";
        case EGL_NOT_INITIALIZED:
            return "EGL_NOT_INITIALIZED";
        case EGL_BAD_ACCESS:
            return "EGL_BAD_ACCESS";
        case EGL_BAD_ALLOC:
            return "EGL_BAD_ALLOC";
        case EGL_BAD_ATTRIBUTE:
            return "EGL_BAD_ATTRIBUTE";
        case EGL_BAD_CONTEXT:
            return "EGL_BAD_CONTEXT";
        case EGL_BAD_CONFIG:
            return "EGL_BAD_CONFIG";
        case EGL_BAD_CURRENT_SURFACE:
            return "EGL_BAD_CURRENT_SURFACE";
        case EGL_BAD_DISPLAY:
            return "EGL_BAD_DISPLAY";
        case EGL_BAD_SURFACE:
            return "EGL_BAD_SURFACE";
        case EGL_BAD_MATCH:
            return "EGL_BAD_MATCH";
        case EGL_BAD_PARAMETER:
            return "EGL_BAD_PARAMETER";
        case EGL_BAD_NATIVE_PIXMAP:
            return "EGL_BAD_NATIVE_PIXMAP";
        case EGL_BAD_NATIVE_WINDOW:
            return "EGL_BAD_NATIVE_WINDOW";
        case EGL_CONTEXT_LOST:
            return "EGL_CONTEXT_LOST";
        default:
            return "<Unknown EGL error>";
    }
}

MaybeError CheckEGL(const EGLFunctions& egl, EGLBoolean result, const char* context) {
    if (result != EGL_FALSE) [[likely]] {
        return {};
    }
    EGLint error = egl.GetError();
    std::string message = std::string(context) + " failed with " + EGLErrorAsString(error);
    if (error == EGL_BAD_ALLOC) {
        return DAWN_OUT_OF_MEMORY_ERROR(message);
    } else if (error == EGL_CONTEXT_LOST) {
        return DAWN_DEVICE_LOST_ERROR(message);
    } else {
        return DAWN_INTERNAL_ERROR(message);
    }
}

ResultOrError<Ref<WrappedEGLSync>> WrappedEGLSync::Create(DisplayEGL* display,
                                                          const OpenGLFunctions&,
                                                          EGLenum type,
                                                          const EGLint* attribs) {
    const EGLFunctions& egl = display->egl.get();

    EGLSyncKHR sync = EGL_NO_SYNC;
    // We don't use OpenGLFunctions struct. However eglCreateSync requires a current context,
    // so a OpenGLFunctions parameter restricts the caller to call this inside Device's
    // EnqueueGL/ExecuteGL.
    if (egl.HasExt(EGLExt::FenceSync)) {
        sync = egl.CreateSyncKHR(display->GetDisplay(), type, attribs);
    } else {
        DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
        std::vector<EGLAttrib> convertedAttribs = ConvertEGLIntParameterListToEGLAttrib(attribs);
        sync = egl.CreateSync(display->GetDisplay(), type, convertedAttribs.data());
    }

    DAWN_TRY(CheckEGL(egl, sync != EGL_NO_SYNC, "eglCreateSync"));

    return AcquireRef(new WrappedEGLSync(display, sync, true));
}

ResultOrError<Ref<WrappedEGLSync>> WrappedEGLSync::AcquireExternal(DisplayEGL* display,
                                                                   EGLSync sync) {
    const EGLFunctions& egl = display->egl.get();

    // Query a property of the sync object to verify that it's valid and associated with this
    // EGLDisplay.
    EGLBoolean queryResult = 0;
    if (egl.HasExt(EGLExt::FenceSync)) {
        EGLint syncType = 0;
        queryResult = egl.GetSyncAttribKHR(display->GetDisplay(), sync, EGL_SYNC_TYPE, &syncType);
    } else {
        DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
        EGLAttrib syncType = 0;
        queryResult = egl.GetSyncAttrib(display->GetDisplay(), sync, EGL_SYNC_TYPE, &syncType);
    }

    DAWN_TRY(CheckEGL(egl, queryResult, "eglGetSyncAttrib"));

    return AcquireRef(new WrappedEGLSync(display, sync, false));
}

WrappedEGLSync::WrappedEGLSync(DisplayEGL* display, EGLSync sync, bool ownsSync)
    : mDisplay(display), mSync(sync), mOwnsSync(ownsSync) {
    DAWN_ASSERT(mDisplay != nullptr);
    DAWN_ASSERT(mSync != EGL_NO_SYNC);
}

WrappedEGLSync::~WrappedEGLSync() {
    if (mOwnsSync) {
        const EGLFunctions& egl = mDisplay->egl.get();
        if (egl.HasExt(EGLExt::FenceSync)) {
            egl.DestroySyncKHR(mDisplay->GetDisplay(), mSync);
        } else {
            DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
            egl.DestroySync(mDisplay->GetDisplay(), mSync);
        }
    }
}

EGLSync WrappedEGLSync::Get() const {
    return mSync;
}

MaybeError WrappedEGLSync::Signal(const OpenGLFunctions&, EGLenum mode) {
    const EGLFunctions& egl = mDisplay->egl.get();
    DAWN_ASSERT(egl.HasExt(EGLExt::ReusableSync));

    DAWN_TRY(CheckEGL(egl, egl.SignalSync(mDisplay->GetDisplay(), mSync, mode), "eglSignalSync"));
    return {};
}

ResultOrError<EGLenum> WrappedEGLSync::ClientWait(const OpenGLFunctions&,
                                                  EGLint flags,
                                                  Nanoseconds timeout) {
    const EGLFunctions& egl = mDisplay->egl.get();

    EGLenum result = EGL_FALSE;
    if (egl.HasExt(EGLExt::FenceSync)) {
        result = egl.ClientWaitSyncKHR(mDisplay->GetDisplay(), mSync, flags, uint64_t(timeout));
    } else {
        DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
        result = egl.ClientWaitSync(mDisplay->GetDisplay(), mSync, flags, uint64_t(timeout));
    }

    DAWN_TRY(CheckEGL(egl, result != EGL_FALSE, "eglClientWaitSync"));
    return result;
}

MaybeError WrappedEGLSync::Wait(const OpenGLFunctions&) {
    const EGLFunctions& egl = mDisplay->egl.get();
    DAWN_ASSERT(egl.HasExt(EGLExt::WaitSync));

    constexpr EGLint flags = 0;
    DAWN_TRY(CheckEGL(egl, egl.WaitSync(mDisplay->GetDisplay(), mSync, flags), "eglWaitSync"));
    return {};
}

ResultOrError<EGLint> WrappedEGLSync::DupFD(const OpenGLFunctions&) {
    const EGLFunctions& egl = mDisplay->egl.get();
    DAWN_ASSERT(egl.HasExt(EGLExt::NativeFenceSync));

    EGLint fd = egl.DupNativeFenceFD(mDisplay->GetDisplay(), mSync);
    DAWN_TRY(CheckEGL(egl, fd != EGL_NO_NATIVE_FENCE_FD_ANDROID, "eglDupNativeFenceFDANDROID"));

    return fd;
}

}  // namespace dawn::native::opengl
