#include "egl_context.h"
#include "logger.h"
#include <glad/gles2.h>
#include <glad/egl.h>
#include <android/native_window_jni.h>
#include <vector>
#include <format>

static void debug_handler(GLenum source, GLenum type,
    GLuint id, GLenum severity, GLsizei length, const GLchar* message,
    const void* userParam)
{
    Logger::Log(std::format("GL Error: {}", std::string_view(message, length)));
}

bool egl::Context::create(ANativeWindow* window)
{
    int version = gladLoaderLoadEGL(EGL_NO_DISPLAY);
    if (version == 0)
    {
        Logger::Log("glad failed to load EGL");
        return false;
    }
    if (m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        m_display == EGL_NO_DISPLAY)
    {
        Logger::Log("eglGetDisplay failed");
        return false;
    }
    EGLint major, minor;
    if (!eglInitialize(m_display, &major, &minor))
    {
        Logger::Log("eglInitialize failed");
        return false;
    }
    version = gladLoadEGL(m_display, eglGetProcAddress);
    std::vector<EGLint> config_attribs =
    {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
    };
    if (window)
    {
        config_attribs.append_range(std::to_array({
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_BLUE_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_RED_SIZE, 8,
        }));
    }
    config_attribs.push_back(EGL_NONE);
    EGLint num_configs;
    if (!eglChooseConfig(m_display, config_attribs.data(), &m_config, 1, &num_configs) || num_configs == 0)
    {
        Logger::Log("eglChooseConfig failed");
        return false;
    }
    const EGLint context_attribs[] =
    {
        EGL_CONTEXT_MAJOR_VERSION, 3,
        EGL_CONTEXT_MINOR_VERSION, 2,
        EGL_NONE,
    };
    if (m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, context_attribs);
        m_context == EGL_NO_CONTEXT)
    {
        Logger::Log("eglCreateContext failed");
        return false;
    }
    if (window)
    {
        m_surface = eglCreateWindowSurface(m_display, m_config, window, nullptr);
        ANativeWindow_acquire(window);
        m_native_window = window;
    }
    if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context))
    {
        Logger::Log("eglMakeCurrent failed");
        return false;
    }
    int gl_version = gladLoaderLoadGLES2();
    if (gl_version == 0)
    {
        Logger::Log("glad failed to load GLES2");
        return false;
    }
    if (glDebugMessageCallback)
    {
        glDebugMessageCallback(debug_handler, nullptr);
        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    }
    return true;
}

void egl::Context::destroy()
{
    if (m_display != EGL_NO_DISPLAY)
    {
        eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        if (m_surface != EGL_NO_SURFACE)
        {
            eglDestroySurface(m_display, m_surface);
        }
        if (m_context != EGL_NO_CONTEXT)
        {
            eglDestroyContext(m_display, m_context);
        }
        if (m_native_window)
        {
            ANativeWindow_release(m_native_window);
        }
        eglTerminate(m_display);
    }
    m_display = EGL_NO_DISPLAY;
    m_context = EGL_NO_CONTEXT;
    m_config = EGL_NO_CONFIG_KHR;
    m_surface = EGL_NO_SURFACE;
    m_native_window = nullptr;
}

egl::Context::Context() :
    m_display(EGL_NO_DISPLAY), m_config(EGL_NO_CONFIG_KHR),
    m_context(EGL_NO_CONTEXT), m_surface(EGL_NO_SURFACE),
    m_native_window(nullptr)
{
    Logger::Log("egl::Context::Context");
}

void egl::Context::swap()
{
    eglSwapBuffers(m_display, m_surface);
}

bool egl::Context::make_current(ANativeWindow* window)
{
    if (window == m_native_window)
    {
        return true;
    }
    eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    if (m_surface != EGL_NO_SURFACE)
    {
        eglDestroySurface(m_display, m_surface);
        m_surface = EGL_NO_SURFACE;
    }
    if (m_native_window)
    {
        ANativeWindow_release(m_native_window);
        m_native_window = nullptr;
    }
    if (window)
    {
        m_surface = eglCreateWindowSurface(m_display, m_config, window, nullptr);
        ANativeWindow_acquire(window);
        m_native_window = window;
        if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context))
        {
            Logger::Log("eglMakeCurrent failed");
            return false;
        }
    }
    return true;
}
