//
// Copyright 2020 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.
//

// DisplayEAGL.cpp: EAGL implementation of egl::Display

#import "common/platform.h"

#if defined(ANGLE_ENABLE_EAGL)

#    import "libANGLE/renderer/gl/eagl/DisplayEAGL.h"

#    import "common/debug.h"
#    import "gpu_info_util/SystemInfo.h"
#    import "libANGLE/Display.h"
#    import "libANGLE/renderer/gl/eagl/ContextEAGL.h"
#    import "libANGLE/renderer/gl/eagl/DeviceEAGL.h"
#    import "libANGLE/renderer/gl/eagl/FunctionsEAGL.h"
#    import "libANGLE/renderer/gl/eagl/IOSurfaceSurfaceEAGL.h"
#    import "libANGLE/renderer/gl/eagl/PbufferSurfaceEAGL.h"
#    import "libANGLE/renderer/gl/eagl/RendererEAGL.h"
#    import "libANGLE/renderer/gl/eagl/WindowSurfaceEAGL.h"

#    import <Foundation/Foundation.h>
#    import <QuartzCore/QuartzCore.h>
#    import <dlfcn.h>

namespace
{

const char *kOpenGLESDylibName = "/System/Library/Frameworks/OpenGLES.framework/OpenGLES";

}

namespace rx
{

class FunctionsGLEAGL : public FunctionsGL
{
  public:
    FunctionsGLEAGL(void *dylibHandle) : mDylibHandle(dylibHandle) {}

    ~FunctionsGLEAGL() override { dlclose(mDylibHandle); }

  private:
    void *loadProcAddress(const std::string &function) const override
    {
        return dlsym(mDylibHandle, function.c_str());
    }

    void *mDylibHandle;
};

DisplayEAGL::DisplayEAGL(const egl::DisplayState &state)
    : DisplayGL(state), mEGLDisplay(nullptr), mContext(nullptr)
{}

DisplayEAGL::~DisplayEAGL() {}

egl::Error DisplayEAGL::initialize(egl::Display *display)
{
    mEGLDisplay = display;

    angle::SystemInfo info;
    // It's legal for GetSystemInfo to return false and thereby
    // contain incomplete information.
    (void)angle::GetSystemInfo(&info);

    mContext = [allocEAGLContextInstance() initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (mContext == nullptr)
    {
        return egl::EglNotInitialized() << "Could not create the EAGL context.";
    }

    if (![getEAGLContextClass() setCurrentContext:mContext])
    {
        return egl::EglNotInitialized() << "Could set the EAGL context current.";
    }
    mThreadsWithContextCurrent.insert(std::this_thread::get_id());

    // There is no equivalent getProcAddress in EAGL so we open the dylib directly
    void *handle = dlopen(kOpenGLESDylibName, RTLD_NOW);
    if (!handle)
    {
        return egl::EglNotInitialized() << "Could not open the OpenGLES Framework.";
    }

    std::unique_ptr<FunctionsGL> functionsGL(new FunctionsGLEAGL(handle));
    functionsGL->initialize(display->getAttributeMap());

    mRenderer.reset(new RendererEAGL(std::move(functionsGL), display->getAttributeMap(), this));

    const gl::Version &maxVersion = mRenderer->getMaxSupportedESVersion();
    if (maxVersion < gl::Version(2, 0))
    {
        return egl::EglNotInitialized() << "OpenGL ES 2.0 is not supportable.";
    }

    auto &attributes = display->getAttributeMap();
    mDeviceContextIsVolatile =
        attributes.get(EGL_PLATFORM_ANGLE_DEVICE_CONTEXT_VOLATILE_EAGL_ANGLE, GL_FALSE);

    return DisplayGL::initialize(display);
}

void DisplayEAGL::terminate()
{
    DisplayGL::terminate();

    mRenderer.reset();
    if (mContext != nullptr)
    {
        [getEAGLContextClass() setCurrentContext:nil];
        mContext = nullptr;
        mThreadsWithContextCurrent.clear();
    }
}

egl::Error DisplayEAGL::prepareForCall()
{
    auto threadId = std::this_thread::get_id();
    if (mDeviceContextIsVolatile ||
        mThreadsWithContextCurrent.find(threadId) == mThreadsWithContextCurrent.end())
    {
        if (![getEAGLContextClass() setCurrentContext:mContext])
        {
            return egl::EglBadAlloc() << "Could not make device EAGL context current.";
        }
        mThreadsWithContextCurrent.insert(threadId);
    }
    return egl::NoError();
}

egl::Error DisplayEAGL::releaseThread()
{
    auto threadId = std::this_thread::get_id();
    if (mThreadsWithContextCurrent.find(threadId) != mThreadsWithContextCurrent.end())
    {
        if (![getEAGLContextClass() setCurrentContext:nil])
        {
            return egl::EglBadAlloc() << "Could not release device EAGL context.";
        }
        mThreadsWithContextCurrent.erase(threadId);
    }
    return egl::NoError();
}

SurfaceImpl *DisplayEAGL::createWindowSurface(const egl::SurfaceState &state,
                                              EGLNativeWindowType window,
                                              const egl::AttributeMap &attribs)
{
    return new WindowSurfaceEAGL(state, mRenderer.get(), window, mContext);
}

SurfaceImpl *DisplayEAGL::createPbufferSurface(const egl::SurfaceState &state,
                                               const egl::AttributeMap &attribs)
{
    EGLint width  = static_cast<EGLint>(attribs.get(EGL_WIDTH, 0));
    EGLint height = static_cast<EGLint>(attribs.get(EGL_HEIGHT, 0));
    return new PbufferSurfaceEAGL(state, mRenderer.get(), width, height);
}

SurfaceImpl *DisplayEAGL::createPbufferFromClientBuffer(const egl::SurfaceState &state,
                                                        EGLenum buftype,
                                                        EGLClientBuffer clientBuffer,
                                                        const egl::AttributeMap &attribs)
{
    ASSERT(buftype == EGL_IOSURFACE_ANGLE);
    return new IOSurfaceSurfaceEAGL(state, mContext, clientBuffer, attribs);
}

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

ContextImpl *DisplayEAGL::createContext(const gl::State &state,
                                        gl::ErrorSet *errorSet,
                                        const egl::Config *configuration,
                                        const gl::Context *shareContext,
                                        const egl::AttributeMap &attribs)
{
    return new ContextEAGL(state, errorSet, mRenderer);
}

DeviceImpl *DisplayEAGL::createDevice()
{
    return new DeviceEAGL();
}

egl::ConfigSet DisplayEAGL::generateConfigs()
{
    // TODO(cwallez): generate more config permutations
    egl::ConfigSet configs;

    const gl::Version &maxVersion = getMaxSupportedESVersion();
    ASSERT(maxVersion >= gl::Version(2, 0));
    bool supportsES3 = maxVersion >= gl::Version(3, 0);

    egl::Config config;

    // Native stuff
    config.nativeVisualID   = 0;
    config.nativeVisualType = 0;
    config.nativeRenderable = EGL_TRUE;

    // Buffer sizes
    config.redSize     = 8;
    config.greenSize   = 8;
    config.blueSize    = 8;
    config.alphaSize   = 8;
    config.depthSize   = 24;
    config.stencilSize = 8;

    config.colorBufferType = EGL_RGB_BUFFER;
    config.luminanceSize   = 0;
    config.alphaMaskSize   = 0;

    config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize;

    config.transparentType = EGL_NONE;

    // Pbuffer
    config.maxPBufferWidth  = 4096;
    config.maxPBufferHeight = 4096;
    config.maxPBufferPixels = 4096 * 4096;

    // Caveat
    config.configCaveat = EGL_NONE;

    // Misc
    config.sampleBuffers     = 0;
    config.samples           = 0;
    config.level             = 0;
    config.bindToTextureRGB  = EGL_FALSE;
    config.bindToTextureRGBA = EGL_FALSE;

    config.bindToTextureTarget = EGL_TEXTURE_2D;

    config.surfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT;

    config.minSwapInterval = 1;
    config.maxSwapInterval = 1;

    config.renderTargetFormat = GL_RGBA8;
    config.depthStencilFormat = GL_DEPTH24_STENCIL8;

    config.conformant     = EGL_OPENGL_ES2_BIT | (supportsES3 ? EGL_OPENGL_ES3_BIT_KHR : 0);
    config.renderableType = config.conformant;

    config.matchNativePixmap = EGL_NONE;

    config.colorComponentType = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;

    configs.add(config);
    return configs;
}

bool DisplayEAGL::testDeviceLost()
{
    // TODO(cwallez) investigate implementing this
    return false;
}

egl::Error DisplayEAGL::restoreLostDevice(const egl::Display *display)
{
    UNIMPLEMENTED();
    return egl::EglBadDisplay();
}

bool DisplayEAGL::isValidNativeWindow(EGLNativeWindowType window) const
{
    NSObject *layer = (__bridge NSObject *)window;
    return [layer isKindOfClass:[CALayer class]];
}

egl::Error DisplayEAGL::validateClientBuffer(const egl::Config *configuration,
                                             EGLenum buftype,
                                             EGLClientBuffer clientBuffer,
                                             const egl::AttributeMap &attribs) const
{
    ASSERT(buftype == EGL_IOSURFACE_ANGLE);

    if (!IOSurfaceSurfaceEAGL::validateAttributes(clientBuffer, attribs))
    {
        return egl::EglBadAttribute();
    }

    return egl::NoError();
}

EAGLContextObj DisplayEAGL::getEAGLContext() const
{
    return mContext;
}

void DisplayEAGL::generateExtensions(egl::DisplayExtensions *outExtensions) const
{
    outExtensions->iosurfaceClientBuffer = true;
    outExtensions->surfacelessContext    = true;

    // Contexts are virtualized so textures ans semaphores can be shared globally
    outExtensions->displayTextureShareGroup   = true;
    outExtensions->displaySemaphoreShareGroup = true;

    outExtensions->powerPreference = false;

    DisplayGL::generateExtensions(outExtensions);
}

void DisplayEAGL::generateCaps(egl::Caps *outCaps) const
{
    outCaps->textureNPOT = true;
}

egl::Error DisplayEAGL::waitClient(const gl::Context *context)
{
    // TODO(cwallez) UNIMPLEMENTED()
    return egl::NoError();
}

egl::Error DisplayEAGL::waitNative(const gl::Context *context, EGLint engine)
{
    // TODO(cwallez) UNIMPLEMENTED()
    return egl::NoError();
}

gl::Version DisplayEAGL::getMaxSupportedESVersion() const
{
    return mRenderer->getMaxSupportedESVersion();
}

egl::Error DisplayEAGL::makeCurrentSurfaceless(gl::Context *context)
{
    // We have nothing to do as mContext is always current, and that EAGL is surfaceless by
    // default.
    return egl::NoError();
}

class WorkerContextEAGL final : public WorkerContext
{
  public:
    WorkerContextEAGL(EAGLContextObj context);
    ~WorkerContextEAGL() override;

    bool makeCurrent() override;
    void unmakeCurrent() override;

  private:
    EAGLContextObj mContext;
};

WorkerContextEAGL::WorkerContextEAGL(EAGLContextObj context) : mContext(context) {}

WorkerContextEAGL::~WorkerContextEAGL()
{
    [getEAGLContextClass() setCurrentContext:nil];
    mContext = nullptr;
}

bool WorkerContextEAGL::makeCurrent()
{
    if (![getEAGLContextClass() setCurrentContext:static_cast<EAGLContext *>(mContext)])
    {
        ERR() << "Unable to make gl context current.";
        return false;
    }
    return true;
}

void WorkerContextEAGL::unmakeCurrent()
{
    [getEAGLContextClass() setCurrentContext:nil];
}

WorkerContext *DisplayEAGL::createWorkerContext(std::string *infoLog)
{
    EAGLContextObj context = nullptr;
    context                = [allocEAGLContextInstance() initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (!context)
    {
        *infoLog += "Could not create the EAGL context.";
        return nullptr;
    }

    return new WorkerContextEAGL(context);
}

void DisplayEAGL::initializeFrontendFeatures(angle::FrontendFeatures *features) const
{
    mRenderer->initializeFrontendFeatures(features);
}

void DisplayEAGL::populateFeatureList(angle::FeatureList *features)
{
    mRenderer->getFeatures().populateFeatureList(features);
}

RendererGL *DisplayEAGL::getRenderer() const
{
    return mRenderer.get();
}

}

#endif  // defined(ANGLE_ENABLE_EAGL)
