/*
 * Copyright (C) 2015 Igalia S.L
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS 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 APPLE OR ITS 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 "config.h"
#include "PlatformDisplay.h"

#include <cstdlib>
#include <mutex>
#include <wtf/NeverDestroyed.h>

#if PLATFORM(X11)
#include "PlatformDisplayX11.h"
#endif

#if PLATFORM(WAYLAND)
#include "PlatformDisplayWayland.h"
#endif

#if PLATFORM(WIN)
#include "PlatformDisplayWin.h"
#endif

#if PLATFORM(GTK)
#include <gdk/gdk.h>
#endif

#if PLATFORM(GTK) && PLATFORM(X11)
#include <gdk/gdkx.h>
#endif

#if PLATFORM(GTK) && PLATFORM(WAYLAND) && !defined(GTK_API_VERSION_2)
#include <gdk/gdkwayland.h>
#endif

#if PLATFORM(EFL) && defined(HAVE_ECORE_X)
#include <Ecore_X.h>
#endif

#if USE(EGL)
#include <EGL/egl.h>
#endif

namespace WebCore {

std::unique_ptr<PlatformDisplay> PlatformDisplay::createPlatformDisplay()
{
#if PLATFORM(GTK)
#if defined(GTK_API_VERSION_2)
    return std::make_unique<PlatformDisplayX11>(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()));
#else
    GdkDisplay* display = gdk_display_manager_get_default_display(gdk_display_manager_get());
#if PLATFORM(X11)
    if (GDK_IS_X11_DISPLAY(display))
        return std::make_unique<PlatformDisplayX11>(GDK_DISPLAY_XDISPLAY(display));
#endif
#if PLATFORM(WAYLAND)
    if (GDK_IS_WAYLAND_DISPLAY(display))
        return PlatformDisplayWayland::create();
#endif
#endif
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
    return std::make_unique<PlatformDisplayX11>(static_cast<Display*>(ecore_x_display_get()));
#elif PLATFORM(WIN)
    return std::make_unique<PlatformDisplayWin>();
#endif

#if PLATFORM(X11)
    return std::make_unique<PlatformDisplayX11>();
#endif

    ASSERT_NOT_REACHED();
    return nullptr;
}

PlatformDisplay& PlatformDisplay::sharedDisplay()
{
    static std::once_flag onceFlag;
    static std::unique_ptr<PlatformDisplay> display;
    std::call_once(onceFlag, []{
        display = createPlatformDisplay();
    });
    return *display;
}

PlatformDisplay::PlatformDisplay()
#if USE(EGL)
    : m_eglDisplay(EGL_NO_DISPLAY)
#endif
{
}

PlatformDisplay::~PlatformDisplay()
{
#if USE(EGL)
    ASSERT(m_eglDisplay == EGL_NO_DISPLAY);
#endif
}

#if USE(EGL)
EGLDisplay PlatformDisplay::eglDisplay() const
{
    if (!m_eglDisplayInitialized)
        const_cast<PlatformDisplay*>(this)->initializeEGLDisplay();
    return m_eglDisplay;
}

void PlatformDisplay::initializeEGLDisplay()
{
    m_eglDisplayInitialized = true;

    if (m_eglDisplay == EGL_NO_DISPLAY) {
// EGL is optionally soft linked on Windows.
#if PLATFORM(WIN)
        auto eglGetDisplay = eglGetDisplayPtr();
        if (!eglGetDisplay)
            return;
#endif
        m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        if (m_eglDisplay == EGL_NO_DISPLAY)
            return;
    }

    if (eglInitialize(m_eglDisplay, 0, 0) == EGL_FALSE) {
        LOG_ERROR("EGLDisplay Initialization failed.");
        terminateEGLDisplay();
        return;
    }

#if USE(OPENGL_ES_2)
    static const EGLenum eglAPIVersion = EGL_OPENGL_ES_API;
#else
    static const EGLenum eglAPIVersion = EGL_OPENGL_API;
#endif
    if (eglBindAPI(eglAPIVersion) == EGL_FALSE) {
        LOG_ERROR("Failed to set EGL API(%d).", eglGetError());
        terminateEGLDisplay();
        return;
    }

    // EGL registers atexit handlers to cleanup its global display list.
    // Since the global PlatformDisplay instance is created before,
    // when the PlatformDisplay destructor is called, EGL has already removed the
    // display from the list, causing eglTerminate() to crash. So, here we register
    // our own atexit handler, after EGL has been initialized and after the global
    // instance has been created to ensure we call eglTerminate() before the other
    // EGL atexit handlers and the PlatformDisplay destructor.
    // See https://bugs.webkit.org/show_bug.cgi?id=157973.
    std::atexit([] { PlatformDisplay::sharedDisplay().terminateEGLDisplay(); });
}

void PlatformDisplay::terminateEGLDisplay()
{
    ASSERT(m_eglDisplayInitialized);
    if (m_eglDisplay == EGL_NO_DISPLAY)
        return;
    eglTerminate(m_eglDisplay);
    m_eglDisplay = EGL_NO_DISPLAY;
}
#endif // USE(EGL)

} // namespace WebCore
