/************************************************************/ /**
*
*	@file: egl_surface.cpp
*	@author: Martin Fouilleul
*	@date: 17/02/2023
*	@revision:
*
*****************************************************************/

#define EGL_EGLEXT_PROTOTYPES
#include "app/app_internal.h"
#include "gl_loader.h"
#include "graphics_surface.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>

#if OC_PLATFORM_MACOS
    //NOTE: EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE on osx defaults to CGL backend, which doesn't handle SwapInterval correctly
    #define OC_EGL_PLATFORM_ANGLE_TYPE EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE

    //NOTE: hardcode GLES versions for now
    //TODO: use version hints, once we have all api versions correctly categorized by glapi.py
    #define OC_GLES_VERSION_MAJOR 3
    #define OC_GLES_VERSION_MINOR 0
    #define oc_gl_load_gles oc_gl_load_gles31
#else
    #define OC_EGL_PLATFORM_ANGLE_TYPE EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE
    #define OC_GLES_VERSION_MAJOR 3
    #define OC_GLES_VERSION_MINOR 1
    #define oc_gl_load_gles oc_gl_load_gles32
#endif

typedef struct oc_egl_surface
{
    oc_surface_data interface;

    EGLDisplay eglDisplay;
    EGLConfig eglConfig;
    EGLContext eglContext;
    EGLSurface eglSurface;

    oc_gl_api api;

} oc_egl_surface;

void oc_egl_surface_destroy(oc_surface_data* interface)
{
    oc_egl_surface* surface = (oc_egl_surface*)interface;

    if(&surface->api == oc_gl_get_api())
    {
        oc_gl_deselect_api();
    }
    if(eglGetCurrentContext() == surface->eglContext)
    {
        eglMakeCurrent(surface->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    }
    eglDestroyContext(surface->eglDisplay, surface->eglContext);
    eglDestroySurface(surface->eglDisplay, surface->eglSurface);

    oc_surface_cleanup((oc_surface_data*)surface);
    free(surface);
}

void oc_egl_surface_prepare(oc_surface_data* interface)
{
    oc_egl_surface* surface = (oc_egl_surface*)interface;
    eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);
    oc_gl_select_api(&surface->api);
}

void oc_egl_surface_present(oc_surface_data* interface)
{
    oc_egl_surface* surface = (oc_egl_surface*)interface;
    eglSwapBuffers(surface->eglDisplay, surface->eglSurface);
}

void oc_egl_surface_deselect(oc_surface_data* interface)
{
    oc_egl_surface* surface = (oc_egl_surface*)interface;
    eglMakeCurrent(surface->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    oc_gl_deselect_api();
}

void oc_egl_surface_swap_interval(oc_surface_data* interface, int swap)
{
    oc_egl_surface* surface = (oc_egl_surface*)interface;
    eglSwapInterval(surface->eglDisplay, swap);
}

void oc_egl_surface_init(oc_egl_surface* surface)
{
    void* nativeLayer = surface->interface.nativeLayer((oc_surface_data*)surface);

    surface->interface.api = OC_GLES;

    surface->interface.destroy = oc_egl_surface_destroy;
    surface->interface.prepare = oc_egl_surface_prepare;
    surface->interface.present = oc_egl_surface_present;
    surface->interface.deselect = oc_egl_surface_deselect;
    surface->interface.swapInterval = oc_egl_surface_swap_interval;

    EGLAttrib displayAttribs[] = {
        EGL_PLATFORM_ANGLE_TYPE_ANGLE, OC_EGL_PLATFORM_ANGLE_TYPE,
        EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
        EGL_NONE
    };

    surface->eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, (void*)EGL_DEFAULT_DISPLAY, displayAttribs);
    eglInitialize(surface->eglDisplay, NULL, NULL);

    EGLint const configAttributes[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 24,
        EGL_STENCIL_SIZE, 8,
        EGL_SAMPLE_BUFFERS, 0,
        EGL_SAMPLES, EGL_DONT_CARE,
        EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FIXED_EXT,
        EGL_NONE
    };

    int numConfigs = 0;
    eglChooseConfig(surface->eglDisplay, configAttributes, &surface->eglConfig, 1, &numConfigs);

    EGLint const surfaceAttributes[] = { EGL_NONE };
    surface->eglSurface = eglCreateWindowSurface(surface->eglDisplay,
                                                 surface->eglConfig,
                                                 nativeLayer,
                                                 surfaceAttributes);

    eglBindAPI(EGL_OPENGL_ES_API);
    EGLint contextAttributes[] = {
        EGL_CONTEXT_MAJOR_VERSION_KHR, OC_GLES_VERSION_MAJOR,
        EGL_CONTEXT_MINOR_VERSION_KHR, OC_GLES_VERSION_MINOR,
        EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM, EGL_TRUE,
        EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE, EGL_TRUE,
        EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE, EGL_FALSE,
        EGL_NONE
    };

    surface->eglContext = eglCreateContext(surface->eglDisplay, surface->eglConfig, EGL_NO_CONTEXT, contextAttributes);
    eglMakeCurrent(surface->eglDisplay, surface->eglSurface, surface->eglSurface, surface->eglContext);

    oc_gl_load_gles(&surface->api, (oc_gl_load_proc)eglGetProcAddress);

    eglSwapInterval(surface->eglDisplay, 1);
}

oc_surface_data* oc_egl_surface_create_remote(u32 width, u32 height)
{
    oc_egl_surface* surface = 0;

    surface = oc_malloc_type(oc_egl_surface);
    if(surface)
    {
        memset(surface, 0, sizeof(oc_egl_surface));

        oc_surface_init_remote((oc_surface_data*)surface, width, height);
        oc_egl_surface_init(surface);
    }

    return ((oc_surface_data*)surface);
}

oc_surface_data* oc_egl_surface_create_for_window(oc_window window)
{
    oc_egl_surface* surface = 0;
    oc_window_data* windowData = oc_window_ptr_from_handle(window);
    if(windowData)
    {
        surface = oc_malloc_type(oc_egl_surface);
        if(surface)
        {
            memset(surface, 0, sizeof(oc_egl_surface));

            oc_surface_init_for_window((oc_surface_data*)surface, windowData);
            oc_egl_surface_init(surface);
        }
    }
    return ((oc_surface_data*)surface);
}
