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

#include "libs/cglm/cglm.h"
#include "libs/glad/include/glad/glad.h"
#include <SDL2/SDL.h>
// #include <SDL2/SDL_events.h>
#include "stb_image.h"
// #include <SDL2/SDL_image.h>
// #include <SDL2/SDL_render.h>
#include "lib.h"

//Screen dimension constants
const int SCREEN_WIDTH = 1920;
const int SCREEN_HEIGHT = 768;

int main(void) {
    SDL_Window *window = NULL;

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("error initializing SDL: %s\n", SDL_GetError());
        return 1;
    }

    SDL_GL_LoadLibrary(NULL);

    // Request an OpenGL 4.5 context (should be core)
    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
    // Also request a depth buffer
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    window = SDL_CreateWindow("Terrain",
                              SDL_WINDOWPOS_UNDEFINED,
                              SDL_WINDOWPOS_UNDEFINED,
                              SCREEN_WIDTH,
                              SCREEN_HEIGHT,
                              SDL_WINDOW_OPENGL );

    if (window == NULL) {
        printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
        return 1;
    }

    SDL_GL_CreateContext(window);
    gladLoadGLLoader(SDL_GL_GetProcAddress);

    printf("Vendor:   %s\n", glGetString(GL_VENDOR));
    printf("Renderer: %s\n", glGetString(GL_RENDERER));
    printf("Version:  %s\n", glGetString(GL_VERSION));

    SDL_GL_SetSwapInterval(1);

    int res = 16;
    int width, height, c;
    unsigned char *terrain_img = stbi_load("terrain.png", &width, &height, &c, 0);
    int w = width / res;
    int h = height / res;
    float tw = 100.0f, th = 35.0f;
    // int w = 20;
    // int h = 20;
    int vcount = w * h * 3;

    float *terrainVerts = malloc(vcount * sizeof(float));
    for (int row = 0; row < h; row++) {
        for (int col = 0; col < w; col++) {
            int idx = (row * w + col) * 3;
            terrainVerts[idx+0] = -tw/2.0f + (float)col/w * tw;
            terrainVerts[idx+1] = *(terrain_img + (row * width + col) * c * res) * 0.01f + 1.0f;
            terrainVerts[idx+2] = -th/2.0f + (float)row/h * th;
            // *(v+1) = y * scale_y + shift_y;
            // *(v+1) = -h/2 + row
        }
    }
    stbi_image_free(terrain_img);

    // There's 1 less row, so (h-1) * width, then we need to add the extra two
    // indices for the degenerate triangles per row, except the first row
    // which only has one extra index, since the first index does not need degeneracy
    // And then since there are duplicate indices in each row
    int icount = (h-1) * (w) * 2 *4;
    unsigned int *terrainIndices = malloc(icount * sizeof(unsigned int));

    int i = 0;
    for (int row = 0; row < h-1; row++) {
        for (int col = 0; col < w-1; col++) {
            unsigned int topleft = row * w + col;
            unsigned int botleft = (row+1) * w + col;
            unsigned int botright = (row+1) * w + col+1;
            unsigned int topright = row * w + col+1;
            terrainIndices[i++] = topleft;
            terrainIndices[i++] = botleft;
            terrainIndices[i++] = botright;
            terrainIndices[i++] = topleft;
            terrainIndices[i++] = topright;
            terrainIndices[i++] = botright;
        }
    }
    // int i = 0;
    // for (int row = 0; row < h-1; row++) {
    //     if (row != 0) {
    //         // Duplicate next num
    //         terrainIndices[i++] = row * w;
    //     }
    //     for (int col = 0; col < w; col++) {
    //         terrainIndices[i++] = (row + 0) * w + col;
    //         terrainIndices[i++] = (row + 1) * w + col;
    //     }
    //     // Duplicate last num
    //     if (row < h-2) {
    //         terrainIndices[i] = terrainIndices[i-1];
    //         i++;
    //     }
    // }

    // float terrainVerts[] = {
    // float tv[] = {
    //     0.f, 0.f, 0.f,
    //     1.f, 0.f, 0.f,
    //     2.f, 0.f, 0.f,
    //     3.f, 0.f, 0.f,
    //     0.f, 1.f, 0.f,
    //     1.f, 1.f, 0.f,
    //     2.f, 1.f, 0.f,
    //     3.f, 1.f, 0.f,
    //     // Second row
    //     0.f, 2.f, 0.f,
    //     1.f, 2.f, 0.f,
    //     2.f, 2.f, 0.f,
    //     3.f, 2.f, 0.f,
    //     0.f, 3.f, 0.f,
    //     1.f, 3.f, 0.f,
    //     2.f, 3.f, 0.f,
    //     3.f, 3.f, 0.f,
    // };

    printf("Loaded %d vertices\n", w * h);

    unsigned int terrainVAO, terrainVBO, terrainIBO;
    glGenVertexArrays(1, &terrainVAO);
    glBindVertexArray(terrainVAO);

    glGenBuffers(1, &terrainVBO);
    glBindBuffer(GL_ARRAY_BUFFER, terrainVBO);
    // glBufferData(GL_ARRAY_BUFFER, vcount * sizeof(float), verts, GL_STATIC_DRAW);
    glBufferData(GL_ARRAY_BUFFER, vcount * sizeof(float), terrainVerts, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glGenBuffers(1, &terrainIBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, terrainIBO);
    int ebosize = icount * sizeof(unsigned int);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, ebosize, terrainIndices, GL_STATIC_DRAW);
    // Disable depth test and face culling.
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    int screen_width,screen_height;
    SDL_GetWindowSize(window, &screen_width, &screen_height);
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);

    GLuint shaderProgram =
        compileShaderProgram("shaders/main.vert", "shaders/main.frag", NULL);

    int matrixLocation;

    SDL_Event e;
    bool quit = false;
    Uint64 now = SDL_GetPerformanceCounter();
    Uint64 last = 0;
    double dt = 0;
    bool draw_wireframe = true;
    float x = 0.0f, y = 0.0f;
    Uint64 frame_count = 0;
    float scroll = -10.0f;
    while (quit == false) {
        frame_count++;
        last = now;
        now = SDL_GetPerformanceCounter();
        dt = (double)((now - last)*1000 / (double)SDL_GetPerformanceFrequency());
        (void)dt;
        const Uint8* keystates = SDL_GetKeyboardState(NULL);
        float speed = 0.5f;
        if (keystates[SDL_SCANCODE_W]) {
            y += speed;
        }
        if (keystates[SDL_SCANCODE_S]) {
            y -= speed;
        }
        if (keystates[SDL_SCANCODE_A]) {
            x += speed;
        }
        if (keystates[SDL_SCANCODE_D]) {
            x -= speed;
        }

        while (SDL_PollEvent( &e)) {
            if (e.type == SDL_QUIT) {
                quit = true;
            } else if (e.type == SDL_KEYDOWN) {
                char key = e.key.keysym.sym;
                if (key == '1') {
                    draw_wireframe = !draw_wireframe;
                }
            }
            if (e.type == SDL_MOUSEWHEEL) {
                if (e.wheel.y > 0) {
                    scroll -= 2.0f;
                } else if(e.wheel.y < 0) {
                    scroll += 2.0f;
                }
            }
        }

        mat4 projection;
        glm_mat4_identity(projection);
        float aspectRatio = (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT;
        glm_perspective(45.0f, aspectRatio, 0.1f, 1000.0f, projection);
        mat4 model;
        glm_mat4_identity(model);
        mat4 view;
        glm_mat4_identity(view);
        glm_translate(view, (vec3){x, -y, scroll});
        // glm_translate(view, (vec3){0.0f, 0.0f, -10.0f});
        glm_rotate(model, 45.0f, (vec3){1.0f, 0.0f, 0.0f});


        matrixLocation = glGetUniformLocation(shaderProgram, "view");
        glUniformMatrix4fv(matrixLocation, 1, GL_FALSE, view[0]);
        matrixLocation = glGetUniformLocation(shaderProgram, "projection");
        glUniformMatrix4fv(matrixLocation, 1, GL_FALSE, projection[0]);
        matrixLocation = glGetUniformLocation(shaderProgram, "model");
        glUniformMatrix4fv(matrixLocation, 1, GL_FALSE, model[0]);

        glUseProgram(shaderProgram);
        glViewport(0, 0, screen_width, screen_height);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        if (draw_wireframe) {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        } else {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        }
        glBindVertexArray(terrainVAO);
        glDrawElements(GL_TRIANGLES, icount, GL_UNSIGNED_INT, (void*)0);

        SDL_GL_SwapWindow(window);
    }

    SDL_DestroyWindow( window );
    return 0;
}
