#include "fastfetch.h"
#include "opengl.h"
#include "common/io.h"

#include <string.h>

#if __ANDROID__ && !defined(FF_HAVE_EGL)
    // On Android, installing OpenGL headers is enough (mesa-dev)
    #if __has_include(<EGL/egl.h>)
        #define FF_HAVE_EGL 1
    #endif
#endif

#if defined(FF_HAVE_EGL) || defined(FF_HAVE_GLX)
    #define FF_HAVE_GL 1

    #include "common/library.h"

    #include <GL/gl.h>

void ffOpenGLHandleResult(FFOpenGLResult* result, typeof(&glGetString) ffglGetString);

#endif // FF_HAVE_GL

#ifdef FF_HAVE_GLX
    #include <GL/glx.h>

typedef struct GLXData {
    FF_LIBRARY_SYMBOL(glGetString)
    FF_LIBRARY_SYMBOL(glXGetProcAddress)
    FF_LIBRARY_SYMBOL(glXQueryVersion)
    FF_LIBRARY_SYMBOL(XOpenDisplay)
    FF_LIBRARY_SYMBOL(glXChooseVisual)
    FF_LIBRARY_SYMBOL(XCreatePixmap)
    FF_LIBRARY_SYMBOL(glXCreateGLXPixmap)
    FF_LIBRARY_SYMBOL(glXCreateContext)
    FF_LIBRARY_SYMBOL(glXMakeCurrent)
    FF_LIBRARY_SYMBOL(glXDestroyContext)
    FF_LIBRARY_SYMBOL(glXDestroyGLXPixmap)
    FF_LIBRARY_SYMBOL(XFreePixmap)
    FF_LIBRARY_SYMBOL(XCloseDisplay)
    FF_LIBRARY_SYMBOL(XFree)

    Display* display;
    XVisualInfo* visualInfo;
    Pixmap pixmap;
    GLXPixmap glxPixmap;
    GLXContext context;
} GLXData;

static const char* glxHandleContext(FFOpenGLResult* result, GLXData* data) {
    if (data->ffglXMakeCurrent(data->display, data->glxPixmap, data->context) != True) {
        return "glXMakeCurrent returned False";
    }
    ffOpenGLHandleResult(result, data->ffglGetString);

    int major, minor;
    if (data->ffglXQueryVersion(data->display, &major, &minor)) {
        ffStrbufSetF(&result->library, "GLX %d.%d", major, minor);
    } else {
        ffStrbufSetStatic(&result->library, "GLX");
    }

    return nullptr;
}

static const char* glxHandleGLXPixmap(FFOpenGLResult* result, GLXData* data) {
    data->context = data->ffglXCreateContext(data->display, data->visualInfo, nullptr, True);
    if (data->context == nullptr) {
        return "glXCreateContext returned nullptr";
    }

    const char* error = glxHandleContext(result, data);
    data->ffglXDestroyContext(data->display, data->context);
    return error;
}

static const char* glxHandlePixmap(FFOpenGLResult* result, GLXData* data) {
    data->glxPixmap = data->ffglXCreateGLXPixmap(data->display, data->visualInfo, data->pixmap);
    if (data->glxPixmap == None) {
        return "glXCreateGLXPixmap returned None";
    }

    const char* error = glxHandleGLXPixmap(result, data);
    data->ffglXDestroyGLXPixmap(data->display, data->glxPixmap);
    return error;
}

static const char* glxHandleVisualInfo(FFOpenGLResult* result, GLXData* data) {
    data->pixmap = data->ffXCreatePixmap(data->display, DefaultRootWindow(data->display), FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT, (unsigned int) data->visualInfo->depth);
    if (data->pixmap == None) {
        return "XCreatePixmap returned None";
    }

    const char* error = glxHandlePixmap(result, data);
    data->ffXFreePixmap(data->display, data->pixmap);
    return error;
}

static const char* glxHandleDisplay(FFOpenGLResult* result, GLXData* data) {
    data->visualInfo = data->ffglXChooseVisual(data->display, DefaultScreen(data->display), (int[]) { None });
    if (data->visualInfo == nullptr) {
        return "glXChooseVisual returned nullptr";
    }

    const char* error = glxHandleVisualInfo(result, data);
    data->ffXFree(data->visualInfo);
    return error;
}

static const char* glxHandleData(FFOpenGLResult* result, GLXData* data) {
    data->ffglGetString = (typeof(data->ffglGetString)) data->ffglXGetProcAddress((const GLubyte*) "glGetString");
    if (data->ffglGetString == nullptr) {
        return "glXGetProcAddress(glGetString) returned nullptr";
    }

    data->display = data->ffXOpenDisplay(nullptr);
    if (data->display == nullptr) {
        return "XOpenDisplay returned nullptr";
    }

    const char* error = glxHandleDisplay(result, data);
    data->ffXCloseDisplay(data->display);
    return error;
}

static const char* detectByGlx(FFOpenGLResult* result) {
    GLXData data;

    FF_LIBRARY_LOAD_MESSAGE(glx,
    #if !__OpenBSD__ && !__NetBSD__
        "libGLX"
    #else
        "libGL"
    #endif
        FF_LIBRARY_EXTENSION,
        1);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXGetProcAddress);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXQueryVersion);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XOpenDisplay);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXChooseVisual);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XCreatePixmap);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXCreateGLXPixmap);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXCreateContext);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXMakeCurrent);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXDestroyContext);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXDestroyGLXPixmap);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XFreePixmap);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XCloseDisplay);
    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XFree);

    FF_SUPPRESS_IO();

    return glxHandleData(result, &data);
}

#endif // FF_HAVE_GLX

const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) {
#if FF_HAVE_GL

    if (options->library == FF_OPENGL_LIBRARY_GLX) {
    #ifdef FF_HAVE_GLX
        return detectByGlx(result);
    #else
        return "fastfetch was compiled without glx support";
    #endif
    }

    if (options->library == FF_OPENGL_LIBRARY_EGL) {
    #ifdef FF_HAVE_EGL
        const char* ffOpenGLDetectByEGL(FFOpenGLResult * result);
        return ffOpenGLDetectByEGL(result);
    #else
        return "fastfetch was compiled without egl support";
    #endif
    }

    const char* error = ""; // not nullptr dummy value

    #ifdef FF_HAVE_EGL
    const char* ffOpenGLDetectByEGL(FFOpenGLResult * result);
    error = ffOpenGLDetectByEGL(result);
    #endif

    #ifdef FF_HAVE_GLX
    if (error != nullptr) {
        error = detectByGlx(result);
    }
    #endif

    return error;

#else

    FF_UNUSED(options, result);
    return "Fastfetch was built without gl support.";

#endif // FF_HAVE_GL
}
