// stdlib
#include <stdio.h>

// graphics
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include "glad.c"

#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM

#include "imgui/imgui.cpp"
#include "imgui/imgui_demo.cpp"
#include "imgui/imgui_draw.cpp"
#include "imgui/imgui_tables.cpp"
#include "imgui/imgui_widgets.cpp"

#include "imgui/backends/imgui_impl_opengl3.cpp"
#include "imgui/backends/imgui_impl_glfw.cpp"

// fonts
#include <ft2build.h>
#include FT_FREETYPE_H

// math types
#include <glm/matrix.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/gtx/string_cast.hpp>

// STL
#include <iostream>

// POSIX
#include <time.h>

struct debug_timer_t
{
  const char *name;
  int64_t usec;
};

extern debug_timer_t debug_timers[];
extern int ntimers;

struct debug_clock_t
{
  const char *name;
  int id;
  int64_t start;
  int64_t end;

  debug_clock_t(const char *name, int id)
  {
      this->name = name;
      this->id = id;
      struct timespec spec;
      clock_gettime(CLOCK_MONOTONIC, &spec);
      start = spec.tv_sec * 1000 * 1000 + spec.tv_nsec / 1000;
      
  }
  ~debug_clock_t()
  {
      struct timespec spec;
      clock_gettime(CLOCK_MONOTONIC, &spec);
      end = spec.tv_sec * 1000 * 1000 + spec.tv_nsec / 1000;
      debug_timers[id].name = this->name;
      debug_timers[id].usec = end - start;
  }
};

#define TIME_BLOCK(name) debug_clock_t _name_##timer(#name, __COUNTER__);

struct charglyph_t {
  uint32_t tex;       // handle to glyph texture
  glm::ivec2 size;    // 2d dimensions
  glm::ivec2 bearing; // offset from baseline to left/top of glyph
  uint32_t advance;   // offset to next glyph
};

struct hex_t {
  int32_t id;
  glm::vec3 position;
  float radius;
  glm::vec3 vertices[7];
  glm::vec4 color;
  uint32_t vao;
  uint32_t vbo;
  uint32_t ebo;
};

struct grid_t {
  int rows;
  int columns;
  hex_t *hexes;
};

struct camera_t {
  glm::vec3 position;
  glm::vec2 size;
};

struct selection_t {
  int32_t *indices;
  int32_t nselected;
};

charglyph_t glyphmap[128];
uint32_t text_program;
uint32_t textvao, textvbo;
uint32_t hex_program;

// error processing
enum err_type {
  OK = 0,
  ERR_GLFW_INIT,
  ERR_GLFW_WINDOW_CREATION,
  ERR_GLAD_LOAD,
  ERR_FREETYPE_INIT,
  ERR_FONT_INIT,
  ERR_CHAR_LOAD, };
uint32_t main_errcode = 0;
const char *errcode_string = "";

double mouse_scroll = 0.0;

#define MAIN_ERROR(e) do { main_errcode = e; errcode_string = #e; goto exit_main_with_error; } while(0)

bool is_selected(int idx, selection_t *sel)
{
    for (int i = 0; i < sel->nselected; ++i)
        if (sel->indices[i] == idx)
            return true;
    return false;
}

void select(int idx, selection_t *sel)
{
    if (!is_selected(idx, sel))
        sel->indices[sel->nselected++] = idx;
}

void deselect(int idx, selection_t *sel)
{
    for (int i = 0; i < sel->nselected; ++i)
        if (sel->indices[i] == idx)
        {
            sel->indices[i] = -1;
            --sel->nselected;
        }
}

void glfw_error_callback(int error, const char* description)
{
    fprintf(stderr, "GLFW error %d: %s\n", error, description);
}

void scroll_callback([[maybe_unused]]GLFWwindow* window, [[maybe_unused]]double xoffset, double yoffset)
{
    mouse_scroll = yoffset;
}

char* load_file(const char *pathname)
{
    FILE *text_file = fopen(pathname, "r");
    if (!text_file)
    {
        fprintf(stderr, "Failed trying to read file at %s", pathname);
        return NULL;
    }

    fseek(text_file, 0, SEEK_END);
    long file_size = ftell(text_file);
    fseek(text_file, 0, SEEK_SET);  /* same as rewind(f); */

    char *file_contents = (char*)calloc(sizeof(char), file_size + 1);
    fread(file_contents, 1, file_size, text_file);
    fclose(text_file);
    return file_contents;
}

uint32_t build_shader_from_source(const char *source, int32_t type)
{
    char infoLog[512];
    unsigned int shader = glCreateShader(type);
    glShaderSource(shader, 1, (const GLchar * const *)(&source), NULL);
    glCompileShader(shader);
    int success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        const char *type_str = type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT";
        fprintf(stderr, "Error: %s shader compilation failed: %s\n", type_str, infoLog);
    }
    // TODO: Maybe better error handling?
    return shader;
}

uint32_t make_gl_program(const char *pathname_vertex, const char *pathname_fragment)
{
    char infoLog[512];

    char *vertex_source = load_file(pathname_vertex);
    char *fragment_source = load_file(pathname_fragment);
    
    unsigned int vertex_shader = build_shader_from_source(vertex_source, GL_VERTEX_SHADER);
    unsigned int fragment_shader = build_shader_from_source(fragment_source, GL_FRAGMENT_SHADER);

    uint32_t program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);

    // print linking errors if any
    int32_t success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success)
    {
        // TODO: Maybe better error handling?
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        fprintf(stderr, "Error: shader linking failed: %s\n", infoLog);
    }
    else
    {
        // Cleanup of unneeded structures
        glDeleteShader(vertex_shader);
        glDeleteShader(fragment_shader);
    }
    return program;
}

void render_text(const char *text, float x, float y, float scale, glm::vec3 color)
{
    // activate corresponding render state	
    glUseProgram(text_program);
    glUniform3f(glGetUniformLocation(text_program, "text_color"), color.x, color.y, color.z);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(textvao);

    // iterate through 128 ASCI characters
    for (const char *p = text; *p != '\0'; ++p)
    {
        char c = *p;
        charglyph_t charglyph = glyphmap[(size_t)c];

        float xpos = x + charglyph.bearing.x * scale;
        float ypos = y - (charglyph.size.y - charglyph.bearing.y) * scale;

        float w = charglyph.size.x * scale;
        float h = charglyph.size.y * scale;
        // update VBO for each character
        float vertices[6][4] = {
          { xpos,     ypos + h,   0.0f, 0.0f },            
          { xpos,     ypos,       0.0f, 1.0f },
          { xpos + w, ypos,       1.0f, 1.0f },

          { xpos,     ypos + h,   0.0f, 0.0f },
          { xpos + w, ypos,       1.0f, 1.0f },
          { xpos + w, ypos + h,   1.0f, 0.0f }           
        };
        // render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, charglyph.tex);
        // update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, textvbo);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); 
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // render quad
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // now advance cursors for next glyph (note that advance is number of 1/64 pixels)
        x += (charglyph.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64)
    }
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

bool inside_hex(hex_t *hex, glm::vec2 point)
{
    for (int i = 1; i < 7; ++i)
    {
        glm::vec2 outer_edge = glm::vec2(hex->vertices[(i % 6) +1].x, hex->vertices[(i % 6) + 1].y)  - glm::vec2(hex->vertices[i].x, hex->vertices[i].y);
        glm::vec2 inner_vec = glm::vec2(point - glm::vec2(hex->vertices[i].x, hex->vertices[i].y));
        float angle = glm::orientedAngle(glm::normalize(outer_edge), glm::normalize(inner_vec));
        if (angle > 0.0f)
            return false;
    }
    return true;
}

void create_grid(grid_t *grid, int rows, int columns)
{
    grid->rows = rows;
    grid->columns = columns;
    if (!grid->hexes) 
        grid->hexes = (hex_t*)calloc(grid->rows * grid->columns, sizeof(hex_t));
    else
    {
        hex_t *new_ptr = (hex_t*)realloc(grid->hexes, grid->rows * grid->columns * sizeof(hex_t));
        if (new_ptr)
            grid->hexes = new_ptr;
        else
        {
            free(grid->hexes);
            grid->hexes = (hex_t*)calloc(grid->rows * grid->columns, sizeof(hex_t));
        }
    }

        


    for (int i = 0; i < grid->rows; ++i)
        for (int j = 0; j < grid->columns; ++j)
        {
            hex_t *the_hexagon = &grid->hexes[i*grid->columns+j];
            float radius = 0.2;
            the_hexagon->position = glm::vec3(0,0,0) + glm::vec3(j * radius * 3.0f * glm::cos(glm::radians(60.0))
                                                                 , (i * radius * 2.0f * glm::sin(glm::radians(60.0))) + (j % 2 ? 0 : radius * glm::sin(glm::radians(60.0))),
                                                                 0);
            the_hexagon->radius = radius;
            the_hexagon->id = i * grid->columns + j;
            the_hexagon->color = glm::vec4(1.0 - i * 0.1, 1.0 - j * 0.1, 1.0, 1.0);
            the_hexagon->vertices[0] = the_hexagon->position;
            the_hexagon->vertices[1] = the_hexagon->position + glm::vec3(-radius, 0, 0);
            the_hexagon->vertices[2] = the_hexagon->position + glm::vec3(-radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0)), 0);
            the_hexagon->vertices[3] = the_hexagon->position + glm::vec3(radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0)), 0);
            the_hexagon->vertices[4] = the_hexagon->position + glm::vec3(radius, 0, 0);
            the_hexagon->vertices[5] = the_hexagon->position + glm::vec3(radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0)), 0);
            the_hexagon->vertices[6] = the_hexagon->position + glm::vec3(-radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0)), 0);
          
            glGenVertexArrays(1, &the_hexagon->vao);
            glGenBuffers(1, &the_hexagon->vbo);
            glBindVertexArray(the_hexagon->vao);
            glBindBuffer(GL_ARRAY_BUFFER, the_hexagon->vbo);
            glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 7 * 3, the_hexagon->vertices, GL_STATIC_DRAW);
          
            // EBO setup
            unsigned int indices[18];
            indices[0] = 0;  indices[1] = 2;  indices[2] = 1;
            indices[3] = 0;  indices[4] = 3;  indices[5] = 2;
            indices[6] = 0;  indices[7] = 4;  indices[8] = 3;
            indices[9] = 0;  indices[10] = 5; indices[11] = 4;
            indices[12] = 0; indices[13] = 6; indices[14] = 5;
            indices[15] = 0; indices[16] = 1; indices[17] = 6;

            glGenBuffers(1, &the_hexagon->ebo);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, the_hexagon->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);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        }
}

// global state
GLFWwindow *window = nullptr;
hex_t hexes[4];

int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
{
    fprintf(stdout, "Hexnando\n");

    // Window initialization
    glfwSetErrorCallback(glfw_error_callback);
    if (!glfwInit())
    {
        MAIN_ERROR(ERR_GLFW_INIT);
    }

    window = glfwCreateWindow(640, 480, "Hexnando", NULL, NULL);
    if (!window)
    {
        MAIN_ERROR(ERR_GLFW_WINDOW_CREATION);
    }

    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);
    glfwSetScrollCallback(window, scroll_callback);

    if (!gladLoadGL())
    {
        MAIN_ERROR(ERR_GLAD_LOAD);
    }

    int gl_version_major, gl_version_minor;
    glGetIntegerv(GL_MAJOR_VERSION, &gl_version_major);
    glGetIntegerv(GL_MINOR_VERSION, &gl_version_minor);

    fprintf(stdout, "Using OpenGL %d.%d\n", gl_version_major, gl_version_minor);

    // font library initialization
    FT_Library ft;
    if (FT_Init_FreeType(&ft))
    {
        MAIN_ERROR(ERR_FREETYPE_INIT);
    }

    FT_Face face;
    if (FT_New_Face(ft, "fonts/Inconsolata-Regular.ttf", 0, &face))
    {
        MAIN_ERROR(ERR_FONT_INIT);
    }

    FT_Set_Pixel_Sizes(face, 0, 48);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction
  
    for (unsigned char c = 0; c < 128; c++)
    {
        // load character glyph 
        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            MAIN_ERROR(ERR_CHAR_LOAD);
        }
        // generate texture
        uint32_t texture;
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(
                     GL_TEXTURE_2D,
                     0,
                     GL_RED,
                     face->glyph->bitmap.width,
                     face->glyph->bitmap.rows,
                     0,
                     GL_RED,
                     GL_UNSIGNED_BYTE,
                     face->glyph->bitmap.buffer
                     );
        // set texture options
        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);
        // now store character for later use
        charglyph_t charglyph = {
          texture, 
          glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
          glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
          (uint32_t)face->glyph->advance.x
        };
        glyphmap[c] = charglyph;
    }
    // clear ft2 resources
    FT_Done_Face(face);
    FT_Done_FreeType(ft);

    // text initialization
    text_program = make_gl_program("shaders/text.vert", "shaders/text.frag");

    
    glGenVertexArrays(1, &textvao);
    glGenBuffers(1, &textvbo);
    glBindVertexArray(textvao);
    glBindBuffer(GL_ARRAY_BUFFER, textvbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    static grid_t grid = {};

    create_grid(&grid, 10, 10);

    hex_program = make_gl_program("shaders/hex.vert", "shaders/hex.frag");

    /* IMGui initalization */
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    static ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    ImGui::StyleColorsDark();
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init("#version 330 core"); // same as shader version
 
    double cursor_x;
    double cursor_y;
    camera_t the_camera;
    bool mouse_pressed;
    bool mouse_over_gui;
    static selection_t selection;
    selection.indices = (int32_t*)calloc(1, sizeof(int32_t) * grid.rows * grid.columns);
    selection.nselected = 0;
    
    the_camera.position = glm::vec3(0.0f, 0.0f, 1.0f);
    the_camera.size = glm::vec2(1.0f, 1.0f);
    // main loop
    while (!glfwWindowShouldClose(window))
    {
        TIME_BLOCK(full_frame);
        double cursor_dx = 0.0;
        double cursor_dy = 0.0;
        int hovered_hex = -1;
        int32_t window_width, window_height;
        {
            TIME_BLOCK(window_size_polling);
            glfwGetWindowSize(window, &window_width, &window_height);
            glViewport(0, 0, window_width, window_height);
        }
        float aspect_ratio = (float)window_width/window_height;

        {
            TIME_BLOCK(input_handling);
            /* Input handling */
            if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
                the_camera.position.y += 0.1;
            if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
                the_camera.position.y -= 0.1;
            if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
                the_camera.position.x -= 0.1;
            if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
                the_camera.position.x += 0.1;
            if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS)
                the_camera.size -= glm::vec2(0.1);
            if (glfwGetKey(window, GLFW_KEY_F) == GLFW_PRESS)
                the_camera.size += glm::vec2(0.1);
            if (mouse_scroll > 0.0)
            {
                the_camera.size -= glm::vec2(0.1);
                mouse_scroll = 0.0;
            }
            if (mouse_scroll < 0.0)
            {
                the_camera.size += glm::vec2(0.1);
                mouse_scroll = 0.0;
            }

            mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
            mouse_over_gui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);

            cursor_dx = cursor_x;
            cursor_dy = cursor_y;

            glfwGetCursorPos(window, &cursor_x, &cursor_y);
        
            cursor_dx -= cursor_x;
            cursor_dy -= cursor_y;

            // Mouse movement
            if (!mouse_over_gui && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS)
            {
                fprintf(stdout, "Moving camera by %f %f\n", cursor_dx, cursor_dx);
                // mouse delta!
                the_camera.position.x += cursor_dx * the_camera.size.x/window_width;
                // The Y coord is inverted
                the_camera.position.y -= cursor_dy * the_camera.size.y/window_height;
            }

            if (the_camera.size.x < 0.1)
                the_camera.size.x = 0.1;
            if (the_camera.size.y < 0.1)
                the_camera.size.y = 0.1;
        }

        glm::mat4 model_matrix;
        glm::mat4 view_matrix;
        glm::mat4 proj_matrix;
        glm::vec2 cursor_world;

        /* Rendering */
        {
            TIME_BLOCK(hex_render);
            glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);

            model_matrix = glm::mat4(1.0f);
            view_matrix = glm::mat4(1.0f);
            proj_matrix = glm::ortho((the_camera.position.x - the_camera.size.x/2) * aspect_ratio, (the_camera.position.x + the_camera.size.x/2) * aspect_ratio, the_camera.position.y - the_camera.size.y/2, the_camera.position.y + the_camera.size.y/2);

            glm::vec4 aux((float)cursor_x, (float)cursor_y, 0.0f, 1.0f);
        
            // move screen coordinates to NDC
            aux.x -= window_width/2.0f;
            aux.x /= window_width/2.0f;
            aux.y -= window_height/2.0f;
            aux.y /= -window_height/2.0f;
            aux = glm::inverse(proj_matrix) * aux;
            cursor_world = glm::vec2(aux.x, aux.y);
            
            glUseProgram(hex_program);
            glUniformMatrix4fv(glGetUniformLocation(hex_program, "model"), 1, GL_FALSE, &model_matrix[0][0]);
            glUniformMatrix4fv(glGetUniformLocation(hex_program, "view"), 1, GL_FALSE, &view_matrix[0][0]);
            glUniformMatrix4fv(glGetUniformLocation(hex_program, "proj"), 1, GL_FALSE, &proj_matrix[0][0]);

            for (int i = 0; i < grid.rows; ++i)
                for (int j = 0; j < grid.columns; j++)
                {
                    int idx = i * grid.columns + j;
                    if (!mouse_over_gui && inside_hex(&grid.hexes[idx], cursor_world)) // draw hovered hex differently
                    {
                        hovered_hex = i * grid.columns + j;
                        if (mouse_pressed)
                        {
                            select(idx, &selection);
                        }
                        else
                            glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f);
                    }
                    else
                    {

                        if (is_selected(idx, &selection))
                        {
                            glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f);
                        }
                        else
                            glUniform4f(glGetUniformLocation(hex_program, "hex_color"), grid.hexes[idx].color.x, grid.hexes[idx].color.y, grid.hexes[idx].color.z, grid.hexes[idx].color.w);
                    }
              
                    glBindVertexArray(grid.hexes[idx].vao);
                    glBindBuffer(GL_ARRAY_BUFFER, grid.hexes[idx].vbo);
                    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, grid.hexes[idx].ebo);
                    glEnable(GL_CULL_FACE);
                    glDrawElements(GL_TRIANGLES, 3*6, GL_UNSIGNED_INT, 0);
                    glDisable(GL_CULL_FACE);
                    glBindVertexArray(0);
                }

            glUseProgram(text_program);
            // text rendering
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        }

        glm::mat4 text_proj_matrix = glm::ortho(0.0f, (float)window_width, 0.0f, (float)window_height);
        glUniformMatrix4fv(glGetUniformLocation(text_program, "projection"), 1, GL_FALSE, &text_proj_matrix[0][0]);

        static char debug_text_buf[256];
        {
            TIME_BLOCK(text_render);
            snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x, cursor_y, cursor_world.x, cursor_world.y);
            render_text(debug_text_buf, 25, 25, .5f, glm::vec3(1.0f, 1.0f, 1.0f));
        
            for (int i = 0; i < grid.rows * grid.columns; ++i)
            {
                // Get position of hexes in screen
                glm::vec4 v = glm::vec4(grid.hexes[i].position.x, grid.hexes[i].position.y, grid.hexes[i].position.z, 1.0);
                v = proj_matrix * view_matrix * model_matrix * v;
                v.x += 1;
                v.x *= window_width/2.0f;
                v.y += 1;
                v.y *= window_height/2.0f;
                char number_string[11];
                snprintf(number_string, 11, "%d", i);
                render_text(number_string, v.x, v.y, .2f + (0.3f / the_camera.size.x), glm::vec3(0.0f, 0.0f, 0.0f));
            }
            if (hovered_hex != -1)
            {
                char buf[30];
                snprintf(buf, 30, "Cursor INSIDE hex %d", hovered_hex);
                render_text(buf, 25.0f, window_height - 25.0f, .5f, glm::vec3(1.0f, 1.0f, 1.0f));
            }
        }
              
        glDisable(GL_BLEND);
        glfwPollEvents();

        static int grid_rows = grid.rows;
        static int grid_columns = grid.columns;

        if (grid_rows != grid.rows || grid_columns != grid.columns)
        {
            create_grid(&grid, grid_rows, grid_columns);
        }
        
        // Start the Dear ImGui frame
        {
            TIME_BLOCK(gui_render);
            ImGui_ImplOpenGL3_NewFrame();
            ImGui_ImplGlfw_NewFrame();
            ImGui::NewFrame();
            ImGui::Begin("Hi there");
            ImGui::SliderInt("Rows", &grid_rows, 1, 100);
            ImGui::SliderInt("Columns", &grid_columns, 1, 100);
            if (ImGui::CollapsingHeader("Debug timers"))
            {
                for (int i = 0; i < ntimers; ++i)
                {
                    ImGui::Text("%s: %d nsecs", debug_timers[i].name, debug_timers[i].usec);
                }
            }
            ImGui::End();
            ImGui::Render();
            ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
        }
        
        glfwSwapBuffers(window);
    }

    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
    
    return 0;
 exit_main_with_error:
    if (window)
        glfwDestroyWindow(window);
    glfwTerminate();
    fprintf(stderr, "Hexnando failed with errcode %d: %s\n", main_errcode, errcode_string);
    return main_errcode;
}

#define NTIMERS __COUNTER__
int ntimers = NTIMERS;
debug_timer_t debug_timers[NTIMERS];
