//
// Copyright 2024 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// DisplayWgpu.cpp:
//    Implements the class methods for DisplayWgpu.
//

#include "libANGLE/renderer/wgpu/DisplayWgpu.h"

#include <dawn/dawn_proc.h>

#include "common/debug.h"
#include "common/platform.h"

#include "libANGLE/Display.h"
#include "libANGLE/renderer/wgpu/ContextWgpu.h"
#include "libANGLE/renderer/wgpu/DeviceWgpu.h"
#include "libANGLE/renderer/wgpu/DisplayWgpu_api.h"
#include "libANGLE/renderer/wgpu/ImageWgpu.h"
#include "libANGLE/renderer/wgpu/SurfaceWgpu.h"
#include "libANGLE/renderer/wgpu/wgpu_proc_utils.h"

#include "EGL/eglext.h"

// The wgpu backend only provides a window surface when a window system is available. On Linux
// that requires X11 or Wayland; every other supported platform always has one. When it is
// unavailable (e.g. the ChromeOS wgpu build) CreateWgpuWindowSurface is stubbed out below and no
// EGL_WINDOW_BIT config is advertised. Unlike the rest of the backend, X11 vs Wayland is chosen at
// runtime (see DisplayWgpu::initialize) because a single Linux build commonly enables both.
#if defined(ANGLE_PLATFORM_LINUX) && !defined(ANGLE_USE_X11) && !defined(ANGLE_USE_WAYLAND)
#    define ANGLE_WEBGPU_HAS_WINDOW_SURFACE_TYPE 0
#else
#    define ANGLE_WEBGPU_HAS_WINDOW_SURFACE_TYPE 1
#endif

namespace rx
{

#if !ANGLE_WEBGPU_HAS_WINDOW_SURFACE_TYPE
WindowSurfaceWgpu *CreateWgpuWindowSurface(const egl::SurfaceState &surfaceState,
                                           EGLNativeWindowType window)
{
    UNIMPLEMENTED();
    return nullptr;
}
#endif

DisplayWgpu::DisplayWgpu(const egl::DisplayState &state) : DisplayImpl(state) {}

DisplayWgpu::~DisplayWgpu() {}

egl::Error DisplayWgpu::initialize(egl::Display *display)
{
    const egl::AttributeMap &attribs = display->getAttributeMap();
    mProcTable                       = *reinterpret_cast<DawnProcTable *>(
        attribs.get(EGL_PLATFORM_ANGLE_DAWN_PROC_TABLE_ANGLE,
                                          reinterpret_cast<EGLAttrib>(&webgpu::GetDefaultProcTable())));

    WGPUInstance providedInstance =
        reinterpret_cast<WGPUInstance>(attribs.get(EGL_PLATFORM_ANGLE_WEBGPU_INSTANCE_ANGLE, 0));
    if (providedInstance)
    {
        mProcTable.instanceAddRef(providedInstance);
        mInstance = webgpu::InstanceHandle::Acquire(&mProcTable, providedInstance);
    }

    WGPUDevice providedDevice =
        reinterpret_cast<WGPUDevice>(attribs.get(EGL_PLATFORM_ANGLE_WEBGPU_DEVICE_ANGLE, 0));
    if (providedDevice)
    {
        mProcTable.deviceAddRef(providedDevice);
        mDevice = webgpu::DeviceHandle::Acquire(&mProcTable, providedDevice);

        mAdapter =
            webgpu::AdapterHandle::Acquire(&mProcTable, mProcTable.deviceGetAdapter(mDevice.get()));

        if (!mInstance)
        {
            mInstance = webgpu::InstanceHandle::Acquire(
                &mProcTable, mProcTable.adapterGetInstance(mAdapter.get()));
        }
    }
    else
    {
        ANGLE_TRY(createWgpuDevice());
    }

    mQueue = webgpu::QueueHandle::Acquire(&mProcTable, mProcTable.deviceGetQueue(mDevice.get()));

    mFormatTable.initialize();

    mLimitsWgpu = WGPU_LIMITS_INIT;
    mProcTable.deviceGetLimits(mDevice.get(), &mLimitsWgpu);

    mWindowSystem = angle::NativeWindowSystem::Other;
#if defined(ANGLE_USE_X11) || defined(ANGLE_USE_WAYLAND)
    {
        // The front-end fills in EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE from the
        // environment when the app leaves it unset (Display::UpdateAttribsFromEnvironment), so it
        // already reflects the same default the front-end picks. Anything other than X11/Wayland
        // (no window system) leaves mWindowSystem as Other.
        EGLAttrib platformType = attribs.get(EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE, 0);
#    if defined(ANGLE_USE_X11)
        if (platformType == EGL_PLATFORM_X11_EXT)
        {
            mWindowSystem = angle::NativeWindowSystem::X11;
        }
#    endif
#    if defined(ANGLE_USE_WAYLAND)
        if (platformType == EGL_PLATFORM_WAYLAND_EXT)
        {
            mWindowSystem = angle::NativeWindowSystem::Wayland;
        }
#    endif
    }
#endif

    initializeFeatures();

    webgpu::GenerateCaps(mLimitsWgpu, &mGLCaps, &mGLTextureCaps, &mGLExtensions, &mGLLimitations,
                         &mEGLCaps, &mEGLExtensions, &mMaxSupportedClientVersion);

    return egl::NoError();
}

void DisplayWgpu::terminate()
{
    mAdapter  = nullptr;
    mInstance = nullptr;
    mDevice   = nullptr;
    mQueue    = nullptr;
}

egl::Error DisplayWgpu::makeCurrent(egl::Display *display,
                                    egl::Surface *drawSurface,
                                    egl::Surface *readSurface,
                                    gl::Context *context)
{
    // Ensure that the correct global DebugAnnotator is installed when the end2end tests change
    // the ANGLE back-end (done frequently).
    display->setGlobalDebugAnnotator();

    return egl::NoError();
}

egl::ConfigSet DisplayWgpu::generateConfigs()
{
    egl::Config config;
    config.renderTargetFormat    = GL_BGRA8_EXT;
    config.depthStencilFormat    = GL_DEPTH24_STENCIL8;
    config.bufferSize            = 32;
    config.redSize               = 8;
    config.greenSize             = 8;
    config.blueSize              = 8;
    config.alphaSize             = 8;
    config.alphaMaskSize         = 0;
    config.bindToTextureRGB      = EGL_FALSE;
    config.bindToTextureRGBA     = EGL_FALSE;
    config.colorBufferType       = EGL_RGB_BUFFER;
    config.configCaveat          = EGL_NONE;
    config.conformant            = EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT;
    config.depthSize             = 24;
    config.level                 = 0;
    config.matchNativePixmap     = EGL_NONE;
    config.maxPBufferWidth       = 0;
    config.maxPBufferHeight      = 0;
    config.maxPBufferPixels      = 0;
    config.maxSwapInterval       = 1;
    config.minSwapInterval       = 1;
    config.nativeRenderable      = EGL_TRUE;
    config.nativeVisualID        = 0;
    config.nativeVisualType      = EGL_NONE;
    config.renderableType        = EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT;
    config.sampleBuffers         = 0;
    config.samples               = 0;
    config.stencilSize           = 8;
    config.surfaceType           = EGL_PBUFFER_BIT;
#if ANGLE_WEBGPU_HAS_WINDOW_SURFACE_TYPE
    config.surfaceType |= EGL_WINDOW_BIT;
#endif
    config.optimalOrientation    = 0;
    config.transparentType       = EGL_NONE;
    config.transparentRedValue   = 0;
    config.transparentGreenValue = 0;
    config.transparentBlueValue  = 0;

    egl::ConfigSet configSet;
    configSet.add(config);
    return configSet;
}

bool DisplayWgpu::testDeviceLost()
{
    return false;
}

egl::Error DisplayWgpu::restoreLostDevice(const egl::Display *display)
{
    return egl::NoError();
}

egl::Error DisplayWgpu::validateClientBuffer(const egl::Config *configuration,
                                             EGLenum buftype,
                                             EGLClientBuffer clientBuffer,
                                             const egl::AttributeMap &attribs) const
{
    switch (buftype)
    {
        case EGL_WEBGPU_TEXTURE_ANGLE:
            return validateExternalWebGPUTexture(clientBuffer, attribs);
        default:
            return DisplayImpl::validateClientBuffer(configuration, buftype, clientBuffer, attribs);
    }
}

egl::Error DisplayWgpu::validateImageClientBuffer(const gl::Context *context,
                                                  EGLenum target,
                                                  EGLClientBuffer clientBuffer,
                                                  const egl::AttributeMap &attribs) const
{
    switch (target)
    {
        case EGL_WEBGPU_TEXTURE_ANGLE:
            return validateExternalWebGPUTexture(clientBuffer, attribs);
        default:
            return DisplayImpl::validateImageClientBuffer(context, target, clientBuffer, attribs);
    }
}

egl::Error DisplayWgpu::validateExternalWebGPUTexture(EGLClientBuffer buffer,
                                                      const egl::AttributeMap &attribs) const
{
    WGPUTexture externalTexture = reinterpret_cast<WGPUTexture>(buffer);
    if (externalTexture == nullptr)
    {
        return egl::Error(EGL_BAD_PARAMETER, "NULL Buffer");
    }

    WGPUTextureFormat externalTextureFormat = mProcTable.textureGetFormat(externalTexture);
    const webgpu::Format *webgpuFormat =
        getFormatForImportedTexture(attribs, externalTextureFormat);
    if (webgpuFormat == nullptr)
    {
        return egl::Error(EGL_BAD_PARAMETER, "Invalid format.");
    }

    return egl::NoError();
}

bool DisplayWgpu::isValidNativeWindow(EGLNativeWindowType window) const
{
    return true;
}

std::string DisplayWgpu::getRendererDescription()
{
    return "WebGPU";
}

std::string DisplayWgpu::getVendorString()
{
    return "WebGPU";
}

std::string DisplayWgpu::getVersionString(bool includeFullVersion)
{
    return std::string();
}

DeviceImpl *DisplayWgpu::createDevice()
{
    return new DeviceWgpu();
}

egl::Error DisplayWgpu::waitClient(const gl::Context *context)
{
    return egl::NoError();
}

egl::Error DisplayWgpu::waitNative(const gl::Context *context, EGLint engine)
{
    return egl::NoError();
}

gl::Version DisplayWgpu::getMaxSupportedESVersion() const
{
    return mMaxSupportedClientVersion;
}

gl::Version DisplayWgpu::getMaxConformantESVersion() const
{
    return mMaxSupportedClientVersion;
}

SurfaceImpl *DisplayWgpu::createWindowSurface(const egl::SurfaceState &state,
                                              EGLNativeWindowType window,
                                              const egl::AttributeMap &attribs)
{
#if defined(ANGLE_USE_X11)
    if (mWindowSystem == angle::NativeWindowSystem::X11)
    {
        return CreateWgpuX11WindowSurface(state, window);
    }
#endif
#if defined(ANGLE_USE_WAYLAND)
    if (mWindowSystem == angle::NativeWindowSystem::Wayland)
    {
        return CreateWgpuWaylandWindowSurface(state, window);
    }
#endif
#if !defined(ANGLE_USE_X11) && !defined(ANGLE_USE_WAYLAND)
    return CreateWgpuWindowSurface(state, window);
#else
    UNIMPLEMENTED();
    return nullptr;
#endif
}

SurfaceImpl *DisplayWgpu::createPbufferSurface(const egl::SurfaceState &state,
                                               const egl::AttributeMap &attribs)
{
    return new OffscreenSurfaceWgpu(state, EGL_NONE, nullptr);
}

SurfaceImpl *DisplayWgpu::createPbufferFromClientBuffer(const egl::SurfaceState &state,
                                                        EGLenum buftype,
                                                        EGLClientBuffer buffer,
                                                        const egl::AttributeMap &attribs)
{
    return new OffscreenSurfaceWgpu(state, buftype, buffer);
}

SurfaceImpl *DisplayWgpu::createPixmapSurface(const egl::SurfaceState &state,
                                              NativePixmapType nativePixmap,
                                              const egl::AttributeMap &attribs)
{
    UNIMPLEMENTED();
    return nullptr;
}

ImageImpl *DisplayWgpu::createImage(const egl::ImageState &state,
                                    const gl::Context *context,
                                    EGLenum target,
                                    const egl::AttributeMap &attribs)
{
    return new ImageWgpu(state, context);
}

ExternalImageSiblingImpl *DisplayWgpu::createExternalImageSibling(const gl::Context *context,
                                                                  EGLenum target,
                                                                  EGLClientBuffer buffer,
                                                                  const egl::AttributeMap &attribs)
{
    switch (target)
    {
        case EGL_WEBGPU_TEXTURE_ANGLE:
            return new WebGPUTextureImageSiblingWgpu(buffer, attribs);
        default:
            return DisplayImpl::createExternalImageSibling(context, target, buffer, attribs);
    }
}

rx::ContextImpl *DisplayWgpu::createContext(const gl::State &state,
                                            gl::ErrorSet *errorSet,
                                            const egl::Config *configuration,
                                            const gl::Context *shareContext,
                                            const egl::AttributeMap &attribs)
{
    return new ContextWgpu(state, errorSet, this);
}

StreamProducerImpl *DisplayWgpu::createStreamProducerD3DTexture(
    egl::Stream::ConsumerType consumerType,
    const egl::AttributeMap &attribs)
{
    UNIMPLEMENTED();
    return nullptr;
}

ShareGroupImpl *DisplayWgpu::createShareGroup(const egl::ShareGroupState &state)
{
    return new ShareGroupWgpu(state);
}

void DisplayWgpu::populateFeatureList(angle::FeatureList *features)
{
    mFeatures.populateFeatureList(features);
}

angle::NativeWindowSystem DisplayWgpu::getWindowSystem() const
{
    return mWindowSystem;
}

const webgpu::Format *DisplayWgpu::getFormatForImportedTexture(const egl::AttributeMap &attribs,
                                                               WGPUTextureFormat wgpuFormat) const
{
    GLenum requestedGLFormat = attribs.getAsInt(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_NONE);
    GLenum requestedGLType   = attribs.getAsInt(EGL_TEXTURE_TYPE_ANGLE, GL_NONE);

    if (requestedGLFormat != GL_NONE)
    {
        const gl::InternalFormat &internalFormat =
            gl::GetInternalFormatInfo(requestedGLFormat, requestedGLType);
        if (internalFormat.internalFormat == GL_NONE)
        {
            return nullptr;
        }

        const webgpu::Format &format = mFormatTable[internalFormat.sizedInternalFormat];
        if (format.getActualWgpuTextureFormat() != wgpuFormat)
        {
            return nullptr;
        }

        return &format;
    }
    else
    {
        return mFormatTable.findClosestTextureFormat(wgpuFormat);
    }
}

void DisplayWgpu::generateExtensions(egl::DisplayExtensions *outExtensions) const
{
    *outExtensions = mEGLExtensions;
}

void DisplayWgpu::generateCaps(egl::Caps *outCaps) const
{
    *outCaps = mEGLCaps;
}

void DisplayWgpu::initializeFeatures()
{
    ApplyFeatureOverrides(&mFeatures, getState().featureOverrides);
    if (mState.featureOverrides.allDisabled)
    {
        return;
    }

    // Disabled by default. Gets explicitly enabled by ANGLE embedders.
    ANGLE_FEATURE_CONDITION((&mFeatures), avoidWaitAny, false);
}

egl::Error DisplayWgpu::createWgpuDevice()
{
    if (!mInstance)
    {
        WGPUInstanceDescriptor instanceDescriptor = WGPU_INSTANCE_DESCRIPTOR_INIT;
        static constexpr auto kTimedWaitAny       = WGPUInstanceFeatureName_TimedWaitAny;
        instanceDescriptor.requiredFeatureCount   = 1;
        instanceDescriptor.requiredFeatures       = &kTimedWaitAny;
        mInstance                                 = webgpu::InstanceHandle::Acquire(&mProcTable,
                                                                                    mProcTable.createInstance(&instanceDescriptor));
    }

    struct RequestAdapterResult
    {
        WGPURequestAdapterStatus status;
        webgpu::AdapterHandle adapter;
        std::string message;
    };
    RequestAdapterResult adapterResult;

    WGPURequestAdapterOptions requestAdapterOptions = WGPU_REQUEST_ADAPTER_OPTIONS_INIT;

    WGPURequestAdapterCallbackInfo requestAdapterCallback = WGPU_REQUEST_ADAPTER_CALLBACK_INFO_INIT;
    requestAdapterCallback.mode                           = WGPUCallbackMode_WaitAnyOnly;
    requestAdapterCallback.callback = [](WGPURequestAdapterStatus status, WGPUAdapter adapter,
                                         struct WGPUStringView message, void *userdata1,
                                         void *userdata2) {
        RequestAdapterResult *result = reinterpret_cast<RequestAdapterResult *>(userdata1);
        const DawnProcTable *wgpu    = reinterpret_cast<const DawnProcTable *>(userdata2);

        result->status  = status;
        result->adapter = webgpu::AdapterHandle::Acquire(wgpu, adapter);
        result->message = std::string(message.data, message.length);
    };
    requestAdapterCallback.userdata1 = &adapterResult;
    requestAdapterCallback.userdata2 = &mProcTable;

    WGPUFutureWaitInfo futureWaitInfo;
    futureWaitInfo.future = mProcTable.instanceRequestAdapter(
        mInstance.get(), &requestAdapterOptions, requestAdapterCallback);

    WGPUWaitStatus status = mProcTable.instanceWaitAny(mInstance.get(), 1, &futureWaitInfo, -1);
    if (webgpu::IsWgpuError(status))
    {
        std::ostringstream err;
        err << "Failed to get WebGPU adapter: " << adapterResult.message;
        return egl::Error(EGL_BAD_ALLOC, err.str());
    }

    mAdapter = adapterResult.adapter;

    std::vector<WGPUFeatureName> requiredFeatures;  // empty for now

    WGPUDeviceDescriptor deviceDesc = WGPU_DEVICE_DESCRIPTOR_INIT;
    deviceDesc.requiredFeatureCount = requiredFeatures.size();
    deviceDesc.requiredFeatures     = requiredFeatures.data();
    deviceDesc.uncapturedErrorCallbackInfo.callback =
        [](WGPUDevice const *device, WGPUErrorType type, struct WGPUStringView message,
           void *userdata1, void *userdata2) {
            ASSERT(userdata1 == nullptr);
            ASSERT(userdata2 == nullptr);
            ERR() << "Error: " << static_cast<std::underlying_type<WGPUErrorType>::type>(type)
                  << " - message: " << std::string(message.data, message.length);
        };

    struct RequestDeviceResult
    {
        WGPURequestDeviceStatus status;
        webgpu::DeviceHandle device;
        std::string message;
    };
    RequestDeviceResult deviceResult;

    WGPURequestDeviceCallbackInfo requestDeviceCallback = WGPU_REQUEST_DEVICE_CALLBACK_INFO_INIT;
    requestDeviceCallback.mode                          = WGPUCallbackMode_WaitAnyOnly;
    requestDeviceCallback.callback = [](WGPURequestDeviceStatus status, WGPUDevice device,
                                        struct WGPUStringView message, void *userdata1,
                                        void *userdata2) {
        RequestDeviceResult *result = reinterpret_cast<RequestDeviceResult *>(userdata1);
        const DawnProcTable *wgpu   = reinterpret_cast<const DawnProcTable *>(userdata2);

        result->status  = status;
        result->device  = webgpu::DeviceHandle::Acquire(wgpu, device);
        result->message = std::string(message.data, message.length);
    };
    requestDeviceCallback.userdata1 = &deviceResult;
    requestDeviceCallback.userdata2 = &mProcTable;

    futureWaitInfo.future =
        mProcTable.adapterRequestDevice(mAdapter.get(), &deviceDesc, requestDeviceCallback);

    status = mProcTable.instanceWaitAny(mInstance.get(), 1, &futureWaitInfo, -1);
    if (webgpu::IsWgpuError(status))
    {
        std::ostringstream err;
        err << "Failed to get WebGPU device: " << deviceResult.message;
        return egl::Error(EGL_BAD_ALLOC, err.str());
    }

    mDevice = deviceResult.device;

    return egl::NoError();
}

DisplayImpl *CreateWgpuDisplay(const egl::DisplayState &state)
{
    return new DisplayWgpu(state);
}

}  // namespace rx
