/*
 *
gcc m_own_trial.c -o my_own_trial -lwayland-client -lwayland-egl -lEGL -lGLESv2 -lglut -lGL -lGLU 
 */
 
#define EGL_NO_X11

#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <assert.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct wl_display *display = NULL;
struct wl_compositor *compositor = NULL;
struct wl_surface *surface = NULL;
struct wl_shell *shell = NULL;
struct wl_shell_surface *shell_surface = NULL;
struct wl_egl_window *egl_window = NULL;
EGLDisplay egl_display;
EGLContext egl_context;
EGLSurface egl_surface;
GLuint texture;



struct WaylandGlobals {
    struct wl_compositor* compositor;
    struct wl_shell* shell;
};
    
/*
 * Registry callbacks
 */
static void registry_global(void* data, struct wl_registry* registry, uint32_t id, const char* interface, uint32_t version)
{
    struct WaylandGlobals* globals = (struct WaylandGlobals *)data;
    if (strcmp(interface, "wl_compositor") == 0) {
        globals->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
    } else if (strcmp(interface, "wl_shell") == 0) {
        globals->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1);
    }
}

static const struct wl_registry_listener registry_listener = { registry_global, NULL };

/*
 * Connect to the Wayland display and return the display and the surface
 * output wlDisplay
 * output wlSurface
 */
static void initWaylandDisplay(struct wl_display** wlDisplay, struct wl_surface** wlSurface)
{
    struct WaylandGlobals globals = {0};

    *wlDisplay = wl_display_connect(NULL);
    assert(*wlDisplay != NULL);

    struct wl_registry* registry = wl_display_get_registry(*wlDisplay);
    wl_registry_add_listener(registry, &registry_listener, (void *) &globals);

    wl_display_dispatch(*wlDisplay);
    wl_display_roundtrip(*wlDisplay);
    assert(globals.compositor);
    assert(globals.shell);

    *wlSurface = wl_compositor_create_surface(globals.compositor);
    assert(*wlSurface != NULL);

    struct wl_shell_surface* shellSurface = wl_shell_get_shell_surface(globals.shell, *wlSurface);
    wl_shell_surface_set_toplevel(shellSurface);
}

/*
 * Configure EGL and return necessary resources
 * input nativeDisplay
 * input nativeWindow
 * output eglDisplay
 * output eglSurface
 */
static void initEGLDisplay(EGLNativeDisplayType nativeDisplay, EGLNativeWindowType nativeWindow, EGLDisplay* eglDisplay, EGLSurface* eglSurface)
{
    EGLint number_of_config;
    EGLint config_attribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };

    static const EGLint context_attribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };

    *eglDisplay = eglGetDisplay(nativeDisplay);
    assert(*eglDisplay != EGL_NO_DISPLAY);

    EGLBoolean initialized = eglInitialize(*eglDisplay, NULL, NULL);
    assert(initialized == EGL_TRUE);

    EGLConfig configs[1];

    eglChooseConfig(*eglDisplay, config_attribs, configs, 1, &number_of_config);
    assert(number_of_config);

    EGLContext eglContext = eglCreateContext(*eglDisplay, configs[0], EGL_NO_CONTEXT, context_attribs);

    *eglSurface = eglCreateWindowSurface(*eglDisplay, configs[0], nativeWindow, NULL);
    assert(*eglSurface != EGL_NO_SURFACE);

    EGLBoolean makeCurrent = eglMakeCurrent(*eglDisplay, *eglSurface, *eglSurface, eglContext);
    assert(makeCurrent == EGL_TRUE);
}

/*
 * Connect Wayland and make EGL
 * input width
 * input height
 * output wlDisplay
 * output eglDisplay
 * output eglSurface
 */
static void initWindow(GLint width, GLint height, struct wl_display** wlDisplay, EGLDisplay* eglDisplay, EGLSurface* eglSurface)
{
    struct wl_surface* wlSurface;
    initWaylandDisplay(wlDisplay, &wlSurface);

    struct wl_egl_window* wlEglWindow = wl_egl_window_create(wlSurface, width, height);
    assert(wlEglWindow != NULL);

    initEGLDisplay((EGLNativeDisplayType) *wlDisplay, (EGLNativeWindowType) wlEglWindow, eglDisplay, eglSurface);
}

/*
 * Return the loaded and compiled shader
 */
GLuint LoadShader(GLenum type, const char* shaderSrc)
{
    GLuint shader = glCreateShader(type);
    assert(shader);

    glShaderSource(shader, 1, &shaderSrc, NULL);
    glCompileShader(shader);

    GLint compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    assert(compiled);

    return shader;
}

/*
 * Initialize the shaders and return the program object
 */
GLuint initProgramObject()
{
    char vShaderStr[] = "#version 300 es                          \n"
                        "layout(location = 0) in vec4 vPosition;  \n"
                        "void main()                              \n"
                        "{                                        \n"
                        "   gl_Position = vPosition;              \n"
                        "}                                        \n";

    char fShaderStr[] = "#version 300 es                              \n"
                        "precision mediump float;                     \n"
                        "out vec4 fragColor;                          \n"
                        "void main()                                  \n"
                        "{                                            \n"
                        "   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  \n"
                        "}                                            \n";

    GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr);
    GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr);

    GLuint programObject = glCreateProgram();
    assert(programObject);

    glAttachShader(programObject, vertexShader);
    glAttachShader(programObject, fragmentShader);

    glLinkProgram(programObject);

    GLint linked;
    glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
    assert(linked);

    return programObject;
}



void load_texture(const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (!file) {
        fprintf(stderr, "Failed to open file: %s\n", filename);
        exit(1);
    }

    unsigned char header[54];
    fread(header, sizeof(unsigned char), 54, file);

    int width = *(int*)&header[18];
    int height = *(int*)&header[22];
    int size = 3 * width * height;
    unsigned char *data = (unsigned char *)malloc(size);
    fread(data, sizeof(unsigned char), size, file);
    fclose(file);

    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    //fclose(file);
    free(data);
}

void draw() {
    glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, 768, 512);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glBindTexture(GL_TEXTURE_2D, texture);

   /* glBegin(GL_QUADS);
    glTexCoord2f(0, 0); glVertex2f(-1, -1);
    glTexCoord2f(1, 0); glVertex2f(1, -1);
    glTexCoord2f(1, 1); glVertex2f(1, 1);
    glTexCoord2f(0, 1); glVertex2f(-1, 1);
    glEnd();*/
    
     // Specify the texture coordinates and vertices for a quad
    GLfloat texCoords[] = {0.0f, 0.0f,
                           1.0f, 0.0f,
                           1.0f, 1.0f,
                           0.0f, 1.0f};

    GLfloat vertices[] = {-1.0f, -1.0f,
                           1.0f, -1.0f,
                           1.0f, 1.0f,
                           -1.0f, 1.0f};

    // Draw the quad
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glDrawArrays(GL_QUADS, 0, 4);

    // Disable texture coordinates
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    // Swap the buffers
    eglSwapBuffers(egl_display, egl_surface);
    //glFlush();
}

/*
 * Draw a triangle
 */
/*void draw(GLuint programObject, GLint width, GLint height)
{
    GLfloat vVertices[] = { -0.5f, -0.5f, 0.0f,
        0.0f,  0.5f, 0.0f,
        0.5f, -0.5f, 0.0f };

    glViewport(0, 0, width, height);
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(programObject);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}*/

int main(int argc, char** argv)
{
    int width = 768;
    int height = 512;
    if (argc < 2) {
            fprintf(stderr, "Usage: %s <image_file>\n", argv[0]);
            return 1;
    }
    struct wl_display* wlDisplay;
    EGLDisplay eglDisplay;		
    EGLSurface eglSurface;
    printf("1\n");
    initWindow(width, height, &wlDisplay, &eglDisplay, &eglSurface);
    printf("2\n");
    GLuint programObject = initProgramObject();
    printf("3\n");
    assert(programObject);
    printf("4\n");
    load_texture(argv[1]);
    printf("5\n");
    //draw();
    //draw(programObject, width, height);
    eglSwapBuffers(eglDisplay, eglSurface);
    printf("6\n");
printf("display:%d\n",wl_display_dispatch(wlDisplay));
    while (wl_display_dispatch(wlDisplay) != -1) {
        draw();
    printf("7\n");
    }

    glDeleteProgram(programObject);
    printf("8\n");
    wl_display_disconnect(wlDisplay);

    return 0;
}


