/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef GLLIBRARYEGL_H_
#define GLLIBRARYEGL_H_

#if defined(MOZ_X11)
#include "mozilla/X11Util.h"
#endif

#include "GLLibraryLoader.h"
#include "mozilla/ThreadLocal.h"
#include "nsIFile.h"

#include <bitset>

#if defined(XP_WIN)

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif

#include <windows.h>

typedef HDC EGLNativeDisplayType;
typedef HBITMAP EGLNativePixmapType;
typedef HWND EGLNativeWindowType;

#define GET_NATIVE_WINDOW(aWidget) ((EGLNativeWindowType)aWidget->GetNativeData(NS_NATIVE_WINDOW))

#else
typedef void *EGLNativeDisplayType;
typedef void *EGLNativePixmapType;
typedef void *EGLNativeWindowType;

#ifdef ANDROID
// We only need to explicitly dlopen egltrace
// on android as we can use LD_PRELOAD or other tricks
// on other platforms. We look for it in /data/local
// as that's writeable by all users
//
// This should really go in GLLibraryEGL.cpp but we currently reference
// APITRACE_LIB in GLContextProviderEGL.cpp. Further refactoring
// will come in subsequent patches on Bug 732865
#define APITRACE_LIB "/data/local/tmp/egltrace.so"

#ifdef MOZ_WIDGET_ANDROID

#endif // MOZ_WIDGET_ANDROID
#endif // ANDROID
#endif

#if defined(MOZ_X11)
#define EGL_DEFAULT_DISPLAY  ((EGLNativeDisplayType)mozilla::DefaultXDisplay())
#else
#define EGL_DEFAULT_DISPLAY  ((EGLNativeDisplayType)0)
#endif

namespace mozilla {
namespace gl {

#undef BEFORE_GL_CALL
#undef AFTER_GL_CALL

#ifdef DEBUG

#ifndef MOZ_FUNCTION_NAME
# ifdef __GNUC__
#  define MOZ_FUNCTION_NAME __PRETTY_FUNCTION__
# elif defined(_MSC_VER)
#  define MOZ_FUNCTION_NAME __FUNCTION__
# else
#  define MOZ_FUNCTION_NAME __func__  // defined in C99, supported in various C++ compilers. Just raw function name.
# endif
#endif

#define BEFORE_GL_CALL do {          \
    BeforeGLCall(MOZ_FUNCTION_NAME); \
} while (0)

#define AFTER_GL_CALL do {           \
    AfterGLCall(MOZ_FUNCTION_NAME);  \
} while (0)
// We rely on the fact that GLLibraryEGL.h #defines BEFORE_GL_CALL and
// AFTER_GL_CALL to nothing if !defined(DEBUG).
#else
#define BEFORE_GL_CALL
#define AFTER_GL_CALL
#endif

class GLLibraryEGL
{
public:
    GLLibraryEGL() 
        : mInitialized(false),
          mEGLLibrary(nullptr),
          mIsANGLE(false)
    {
    }

    void InitExtensions();

    /**
     * Known GL extensions that can be queried by
     * IsExtensionSupported.  The results of this are cached, and as
     * such it's safe to use this even in performance critical code.
     * If you add to this array, remember to add to the string names
     * in GLContext.cpp.
     */
    enum EGLExtensions {
        KHR_image_base,
        KHR_image_pixmap,
        KHR_gl_texture_2D_image,
        KHR_lock_surface,
        ANGLE_surface_d3d_texture_2d_share_handle,
        EXT_create_context_robustness,
        KHR_image,
        KHR_fence_sync,
        ANDROID_native_fence_sync,
        Extensions_Max
    };

    bool IsExtensionSupported(EGLExtensions aKnownExtension) const {
        return mAvailableExtensions[aKnownExtension];
    }

    void MarkExtensionUnsupported(EGLExtensions aKnownExtension) {
        mAvailableExtensions[aKnownExtension] = false;
    }

protected:
    std::bitset<Extensions_Max> mAvailableExtensions;

public:

    EGLDisplay fGetDisplay(void* display_id)
    {
        BEFORE_GL_CALL;
        EGLDisplay disp = mSymbols.fGetDisplay(display_id);
        AFTER_GL_CALL;
        return disp;
    }

    EGLSurface fGetCurrentSurface(EGLint id)
    {
        BEFORE_GL_CALL;
        EGLSurface surf = mSymbols.fGetCurrentSurface(id);
        AFTER_GL_CALL;
        return surf;
    }

    EGLContext fGetCurrentContext()
    {
        BEFORE_GL_CALL;
        EGLContext context = mSymbols.fGetCurrentContext();
        AFTER_GL_CALL;
        return context;
    }

    EGLBoolean fMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fMakeCurrent(dpy, draw, read, ctx);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fDestroyContext(EGLDisplay dpy, EGLContext ctx)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fDestroyContext(dpy, ctx);
        AFTER_GL_CALL;
        return b;
    }

    EGLContext fCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list)
    {
        BEFORE_GL_CALL;
        EGLContext ctx = mSymbols.fCreateContext(dpy, config, share_context, attrib_list);
        AFTER_GL_CALL;
        return ctx;
    }

    EGLBoolean fDestroySurface(EGLDisplay dpy, EGLSurface surface)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fDestroySurface(dpy, surface);
        AFTER_GL_CALL;
        return b;
    }

    EGLSurface fCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list)
    {
        BEFORE_GL_CALL;
        EGLSurface surf = mSymbols.fCreateWindowSurface(dpy, config, win, attrib_list);
        AFTER_GL_CALL;
        return surf;
    }

    EGLSurface fCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list)
    {
        BEFORE_GL_CALL;
        EGLSurface surf = mSymbols.fCreatePbufferSurface(dpy, config, attrib_list);
        AFTER_GL_CALL;
        return surf;
    }

    EGLSurface fCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list)
    {
        BEFORE_GL_CALL;
        EGLSurface surf = mSymbols.fCreatePixmapSurface(dpy, config, pixmap, attrib_list);
        AFTER_GL_CALL;
        return surf;
    }

    EGLBoolean fBindAPI(EGLenum api)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fBindAPI(api);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fInitialize(dpy, major, minor);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fChooseConfig(dpy, attrib_list, configs, config_size, num_config);
        AFTER_GL_CALL;
        return b;
    }

    EGLint fGetError()
    {
        BEFORE_GL_CALL;
        EGLint i = mSymbols.fGetError();
        AFTER_GL_CALL;
        return i;
    }

    EGLBoolean fGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fGetConfigAttrib(dpy, config, attribute, value);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fGetConfigs(dpy, configs, config_size, num_config);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fWaitNative(EGLint engine)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fWaitNative(engine);
        AFTER_GL_CALL;
        return b;
    }

    EGLCastToRelevantPtr fGetProcAddress(const char *procname)
    {
        BEFORE_GL_CALL;
        EGLCastToRelevantPtr p = mSymbols.fGetProcAddress(procname);
        AFTER_GL_CALL;
        return p;
    }

    EGLBoolean fSwapBuffers(EGLDisplay dpy, EGLSurface surface)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fSwapBuffers(dpy, surface);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fCopyBuffers(dpy, surface, target);
        AFTER_GL_CALL;
        return b;
    }

    const GLubyte* fQueryString(EGLDisplay dpy, EGLint name)
    {
        BEFORE_GL_CALL;
        const GLubyte* b;
        if (mSymbols.fQueryStringImplementationANDROID) {
          b = mSymbols.fQueryStringImplementationANDROID(dpy, name);
        } else {
          b = mSymbols.fQueryString(dpy, name);
        }
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fQueryContext(dpy, ctx, attribute, value);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fBindTexImage(dpy, surface, buffer);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fReleaseTexImage(dpy, surface, buffer);
        AFTER_GL_CALL;
        return b;
    }

    EGLImage fCreateImage(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
    {
         BEFORE_GL_CALL;
         EGLImage i = mSymbols.fCreateImage(dpy, ctx, target, buffer, attrib_list);
         AFTER_GL_CALL;
         return i;
    }

    EGLBoolean fDestroyImage(EGLDisplay dpy, EGLImage image)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fDestroyImage(dpy, image);
        AFTER_GL_CALL;
        return b;
    }

    // New extension which allow us to lock texture and get raw image pointer
    EGLBoolean fLockSurface(EGLDisplay dpy, EGLSurface surface, const EGLint *attrib_list)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fLockSurface(dpy, surface, attrib_list);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fUnlockSurface(EGLDisplay dpy, EGLSurface surface)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fUnlockSurface(dpy, surface);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fQuerySurface(dpy, surface, attribute, value);
        AFTER_GL_CALL;
        return b;
    }

    EGLBoolean fQuerySurfacePointerANGLE(EGLDisplay dpy, EGLSurface surface, EGLint attribute, void **value)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fQuerySurfacePointerANGLE(dpy, surface, attribute, value);
        AFTER_GL_CALL;
        return b;
    }

    EGLSync fCreateSync(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list)
    {
        BEFORE_GL_CALL;
        EGLSync ret = mSymbols.fCreateSync(dpy, type, attrib_list);
        AFTER_GL_CALL;
        return ret;
    }

    EGLBoolean fDestroySync(EGLDisplay dpy, EGLSync sync)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fDestroySync(dpy, sync);
        AFTER_GL_CALL;
        return b;
    }

    EGLint fClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout)
    {
        BEFORE_GL_CALL;
        EGLint ret = mSymbols.fClientWaitSync(dpy, sync, flags, timeout);
        AFTER_GL_CALL;
        return ret;
    }

    EGLBoolean fGetSyncAttrib(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLint *value)
    {
        BEFORE_GL_CALL;
        EGLBoolean b = mSymbols.fGetSyncAttrib(dpy, sync, attribute, value);
        AFTER_GL_CALL;
        return b;
    }

    EGLint fDupNativeFenceFDANDROID(EGLDisplay dpy, EGLSync sync)
    {
        MOZ_ASSERT(mSymbols.fDupNativeFenceFDANDROID);
        BEFORE_GL_CALL;
        EGLint ret = mSymbols.fDupNativeFenceFDANDROID(dpy, sync);
        AFTER_GL_CALL;
        return ret;
    }

    EGLDisplay Display() {
        return mEGLDisplay;
    }

    bool IsANGLE() const {
        return mIsANGLE;
    }

    bool HasKHRImageBase() {
        return IsExtensionSupported(KHR_image) || IsExtensionSupported(KHR_image_base);
    }

    bool HasKHRImagePixmap() {
        return IsExtensionSupported(KHR_image) || IsExtensionSupported(KHR_image_pixmap);
    }

    bool HasKHRImageTexture2D() {
        return IsExtensionSupported(KHR_gl_texture_2D_image);
    }

    bool HasANGLESurfaceD3DTexture2DShareHandle() {
        return IsExtensionSupported(ANGLE_surface_d3d_texture_2d_share_handle);
    }

    bool HasRobustness() const {
        return IsExtensionSupported(EXT_create_context_robustness);
    }

    bool EnsureInitialized();

    void DumpEGLConfig(EGLConfig cfg);
    void DumpEGLConfigs();

    struct {
        typedef EGLDisplay (GLAPIENTRY * pfnGetDisplay)(void *display_id);
        pfnGetDisplay fGetDisplay;
        typedef EGLSurface (GLAPIENTRY * pfnGetCurrentSurface)(EGLint);
        pfnGetCurrentSurface fGetCurrentSurface;
        typedef EGLContext (GLAPIENTRY * pfnGetCurrentContext)(void);
        pfnGetCurrentContext fGetCurrentContext;
        typedef EGLBoolean (GLAPIENTRY * pfnMakeCurrent)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
        pfnMakeCurrent fMakeCurrent;
        typedef EGLBoolean (GLAPIENTRY * pfnDestroyContext)(EGLDisplay dpy, EGLContext ctx);
        pfnDestroyContext fDestroyContext;
        typedef EGLContext (GLAPIENTRY * pfnCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);
        pfnCreateContext fCreateContext;
        typedef EGLBoolean (GLAPIENTRY * pfnDestroySurface)(EGLDisplay dpy, EGLSurface surface);
        pfnDestroySurface fDestroySurface;
        typedef EGLSurface (GLAPIENTRY * pfnCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list);
        pfnCreateWindowSurface fCreateWindowSurface;
        typedef EGLSurface (GLAPIENTRY * pfnCreatePbufferSurface)(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
        pfnCreatePbufferSurface fCreatePbufferSurface;
        typedef EGLSurface (GLAPIENTRY * pfnCreatePixmapSurface)(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list);
        pfnCreatePixmapSurface fCreatePixmapSurface;
        typedef EGLBoolean (GLAPIENTRY * pfnBindAPI)(EGLenum api);
        pfnBindAPI fBindAPI;
        typedef EGLBoolean (GLAPIENTRY * pfnInitialize)(EGLDisplay dpy, EGLint *major, EGLint *minor);
        pfnInitialize fInitialize;
        typedef EGLBoolean (GLAPIENTRY * pfnChooseConfig)(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
        pfnChooseConfig fChooseConfig;
        typedef EGLint (GLAPIENTRY * pfnGetError)(void);
        pfnGetError fGetError;
        typedef EGLBoolean (GLAPIENTRY * pfnGetConfigAttrib)(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
        pfnGetConfigAttrib fGetConfigAttrib;
        typedef EGLBoolean (GLAPIENTRY * pfnGetConfigs)(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config);
        pfnGetConfigs fGetConfigs;
        typedef EGLBoolean (GLAPIENTRY * pfnWaitNative)(EGLint engine);
        pfnWaitNative fWaitNative;
        typedef EGLCastToRelevantPtr (GLAPIENTRY * pfnGetProcAddress)(const char *procname);
        pfnGetProcAddress fGetProcAddress;
        typedef EGLBoolean (GLAPIENTRY * pfnSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
        pfnSwapBuffers fSwapBuffers;
        typedef EGLBoolean (GLAPIENTRY * pfnCopyBuffers)(EGLDisplay dpy, EGLSurface surface,
                                                         EGLNativePixmapType target);
        pfnCopyBuffers fCopyBuffers;
        typedef const GLubyte* (GLAPIENTRY * pfnQueryString)(EGLDisplay, EGLint name);
        pfnQueryString fQueryString;
        pfnQueryString fQueryStringImplementationANDROID;
        typedef EGLBoolean (GLAPIENTRY * pfnQueryContext)(EGLDisplay dpy, EGLContext ctx,
                                                          EGLint attribute, EGLint *value);
        pfnQueryContext fQueryContext;
        typedef EGLBoolean (GLAPIENTRY * pfnBindTexImage)(EGLDisplay, EGLSurface surface, EGLint buffer);
        pfnBindTexImage fBindTexImage;
        typedef EGLBoolean (GLAPIENTRY * pfnReleaseTexImage)(EGLDisplay, EGLSurface surface, EGLint buffer);
        pfnReleaseTexImage fReleaseTexImage;
        typedef EGLImage (GLAPIENTRY * pfnCreateImage)(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list);
        pfnCreateImage fCreateImage;
        typedef EGLBoolean (GLAPIENTRY * pfnDestroyImage)(EGLDisplay dpy, EGLImage image);
        pfnDestroyImage fDestroyImage;

        // New extension which allow us to lock texture and get raw image pointer
        typedef EGLBoolean (GLAPIENTRY * pfnLockSurface)(EGLDisplay dpy, EGLSurface surface, const EGLint *attrib_list);
        pfnLockSurface fLockSurface;
        typedef EGLBoolean (GLAPIENTRY * pfnUnlockSurface)(EGLDisplay dpy, EGLSurface surface);
        pfnUnlockSurface fUnlockSurface;
        typedef EGLBoolean (GLAPIENTRY * pfnQuerySurface)(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value);
        pfnQuerySurface fQuerySurface;

        typedef EGLBoolean (GLAPIENTRY * pfnQuerySurfacePointerANGLE)(EGLDisplay dpy, EGLSurface surface, EGLint attribute, void **value);
        pfnQuerySurfacePointerANGLE fQuerySurfacePointerANGLE;

        typedef EGLSync (GLAPIENTRY * pfnCreateSync)(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list);
        pfnCreateSync fCreateSync;
        typedef EGLBoolean (GLAPIENTRY * pfnDestroySync)(EGLDisplay dpy, EGLSync sync);
        pfnDestroySync fDestroySync;
        typedef EGLint (GLAPIENTRY * pfnClientWaitSync)(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
        pfnClientWaitSync fClientWaitSync;
        typedef EGLBoolean (GLAPIENTRY * pfnGetSyncAttrib)(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLint *value);
        pfnGetSyncAttrib fGetSyncAttrib;
        typedef EGLint (GLAPIENTRY * pfnDupNativeFenceFDANDROID)(EGLDisplay dpy, EGLSync sync);
        pfnDupNativeFenceFDANDROID fDupNativeFenceFDANDROID;
    } mSymbols;

#ifdef DEBUG
    static void BeforeGLCall(const char* glFunction);
    static void AfterGLCall(const char* glFunction);
#endif

#ifdef MOZ_B2G
    EGLContext CachedCurrentContext() {
        return sCurrentContext.get();
    }
    void UnsetCachedCurrentContext() {
        sCurrentContext.set(nullptr);
    }
    void SetCachedCurrentContext(EGLContext aCtx) {
        sCurrentContext.set(aCtx);
    }
    bool CachedCurrentContextMatches() {
        return sCurrentContext.get() == fGetCurrentContext();
    }

private:
    static ThreadLocal<EGLContext> sCurrentContext;
public:

#else
    EGLContext CachedCurrentContext() {
        return nullptr;
    }
    void UnsetCachedCurrentContext() {}
    void SetCachedCurrentContext(EGLContext aCtx) { }
    bool CachedCurrentContextMatches() { return true; }
#endif

private:
    bool mInitialized;
    PRLibrary* mEGLLibrary;
    EGLDisplay mEGLDisplay;

    bool mIsANGLE;
};

extern GLLibraryEGL sEGLLibrary;
#define EGL_DISPLAY()        sEGLLibrary.Display()

} /* namespace gl */
} /* namespace mozilla */

#endif /* GLLIBRARYEGL_H_ */

