#include <GL/glew.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>

#include "hex_math.h"

#define log_err(str, ...) do { fprintf(stderr, "[ERROR] (%s:%d): " str "\n", __FILE__, __LINE__, ##__VA_ARGS__); } while (0)
#define log_debug(str, ...) do { fprintf(stderr, "[DEBUG] (%s:%d): " str "\n", __FILE__, __LINE__, ##__VA_ARGS__); } while (0)

/* Globals */
const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";
const char *fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n\0";

static void glfw_error_callback(int errcode, const char *description)
{
    fprintf(stderr, "[GLFW ERROR][ERRCODE %d] %s\n", errcode, description);
}

int main()
{
    GLFWwindow *window;

    if (!glfwInit()) return -1;

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);


    window = glfwCreateWindow(800, 800, "Hello World", NULL, NULL);
    if (!window) return -1;
    glfwMakeContextCurrent(window);
    if (glewInit() != GLEW_OK) return -1;

    glfwSetErrorCallback(glfw_error_callback);

    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertex_shader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertex_shader, 512, NULL, infoLog);
        fprintf(stderr, "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n %s\n", infoLog);
    }
    // fragment shader
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragment_shader);
    // check for shader compile errors
    if (!success)
    {
        glGetShaderInfoLog(fragment_shader, 512, NULL, infoLog);
        fprintf(stderr, "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n %s\n", infoLog);
    }
    // link shaders
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    // check for linking errors
    glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(shader_program, 512, NULL, infoLog);
        fprintf(stderr, "ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n %s\n", infoLog);
    }
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);


    vec4f background_color;
    background_color.r = 0.1f;
    background_color.g = 0.2f;
    background_color.b = 0.3f;
    background_color.a = 1.0f;



    hexgrid_t grid;
    grid.rows = 2;
    grid.columns = 2;
    grid.hexes = calloc(1, sizeof(hex_t) * grid.rows * grid.columns);

    float scale = 1.0f / 5.0f;

    for (size_t i = 0; i < grid.columns; ++i)
    {
        for (size_t j = 0; j < grid.rows; ++j)
        {
            float offset = 0.0f;
            if (j % 2)
                offset = sint(1.0f/6);

            grid.hexes[i*grid.rows+j].position.x = (0.0f + j*(1 + cost(1.0f/6)));
            grid.hexes[i*grid.rows+j].position.y = (0.0f + i*(sint(1.0f/6))*2.0f) + offset;
            grid.hexes[i*grid.rows+j].position.z = 0.0f;
            grid.hexes[i*grid.rows+j].position.w = 1.0f;
            fprintf(stdout, "Position of hex %ld %ld is %f %f\n", i, j, grid.hexes[i*grid.rows+j].position.x, grid.hexes[i*grid.rows+j].position.y);
        }
    }

    // Get OpenGL objects


    for (size_t i = 0; i < grid.rows * grid.columns; ++i)
    {
        hex_t *hex = &grid.hexes[i];
        // VAO setup
        glGenVertexArrays(1, &hex->vao);
        glBindVertexArray(hex->vao);

        // VBO setup
        float *vertices = hex->vertices;
        vertices[0] = hex->position.x * scale;
        vertices[1] = hex->position.y * scale;
        vertices[2] = hex->position.z;
        log_debug("Adding vertex %f %f %f", vertices[0], vertices[1], vertices[2]);
        for (int j = 1; j < 7; ++j)
        {
            vertices[j*3] = (hex->position.x + cost((j-1)/6.0f)) * scale;
            vertices[j*3+1] = (hex->position.y + sint((j-1)/6.0f)) * scale;
            vertices[j*3+2] = hex->position.z;

            log_debug("Adding vertex %f %f %f", vertices[j*3], vertices[j*3+1], vertices[j*3+2]);

        }
        glGenBuffers(1,&hex->vbo);
        glBindBuffer(GL_ARRAY_BUFFER, hex->vbo);
        glBufferData(GL_ARRAY_BUFFER, 7*3*sizeof(float), vertices, GL_STATIC_DRAW);

        // EBO setup
        unsigned int *indices = hex->indices;
        indices[0] = 0;  indices[1] = 1;  indices[2] = 2;
        indices[3] = 0;  indices[4] = 2;  indices[5] = 3;
        indices[6] = 0;  indices[7] = 3;  indices[8] = 4;
        indices[9] = 0;  indices[10] = 4; indices[11] = 5;
        indices[12] = 0; indices[13] = 5; indices[14] = 6;
        indices[15] = 0; indices[16] = 6; indices[17] = 1;
        glGenBuffers(1,&hex->ebo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, hex->ebo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*3 * sizeof(unsigned int), indices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glEnableVertexAttribArray(0);
    }


    //glBindVertexArray(0);
    //glBindBuffer(GL_ARRAY_BUFFER, 0);
    //glBindVertexArray(0);

    while (!glfwWindowShouldClose(window))
    {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
        glViewport(0, 0, width, height);

        glClearColor(background_color.r, background_color.g, background_color.b, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(shader_program);
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

        for (size_t i = 0; i < grid.rows * grid.columns; ++i)
        {
            glBindVertexArray(grid.hexes[i].vao);
            //log_debug("Binding VAO %d VBO %d EBO %d", grid.hexes[i].vao, grid.hexes[i].vbo, grid.hexes[i].ebo);
            glBindBuffer(GL_ARRAY_BUFFER, grid.hexes[i].vbo);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, grid.hexes[i].ebo);
            glDrawElements(GL_TRIANGLES, 3*6, GL_UNSIGNED_INT, 0);
        }
        glfwPollEvents();
        glfwSwapBuffers(window);
    }
}
