#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <unistd.h>
#include <termios.h>
#include <limits.h>
#include <time.h>
//#include <glad/glad.h>
#include "../include/glad/glad.h"
#include <GLFW/glfw3.h>
#include <cglm/cglm.h>

#include "hash.h"
#include "base.h"
#include "entities.h"
#include "menus.h"

float rectDefault[18] ;
float incr;

static mat4 scale, slide;
void initMenus() {
    rectDefault[0]  = -0.5f; rectDefault[1]  = -0.5f; rectDefault[2]  = -0.99f;
    rectDefault[3]  = -0.5f; rectDefault[4]  =  0.5f; rectDefault[5]  = -0.99f;
    rectDefault[6]  =  0.5f; rectDefault[7]  =  0.5f; rectDefault[8]  = -0.99f;

    rectDefault[9]  = -0.5f; rectDefault[10] = -0.5f; rectDefault[11] = -0.99f;
    rectDefault[12] =  0.5f; rectDefault[13] =  0.5f; rectDefault[14] = -0.99f;
    rectDefault[15] =  0.5f; rectDefault[16] = -0.5f; rectDefault[17] = -0.99f;

    incr = 0.0f;
}

void gl_drawRect(unsigned int fragShader, float x, float y, float w, float h, int r, int g, int b) {
    glm_mat4_identity(scale);
    glm_mat4_identity(slide);

    scale[0][0] = w;
    scale[1][1] = h;
    scale[2][2] = 0.5f+incr;
    incr += 0.000001f;

    glm_translate(slide, (vec3){x+w/2, y+h/2, 0.0f});

    glUniformMatrix4fv(glGetUniformLocation(fragShader, "scale"), 1, GL_FALSE, (float*)scale);
    glUniformMatrix4fv(glGetUniformLocation(fragShader, "slide"), 1, GL_FALSE, (float*)slide);
    glUniform4f(glGetUniformLocation(fragShader, "u_color2"), r/255.0f, g/255.0f, b/255.0f, 1.0f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
}

void gl_drawRectAlpha(unsigned int fragShader, float x, float y, float w, float h, int r, int g, int b, int a) {
    glm_mat4_identity(scale);
    glm_mat4_identity(slide);

    scale[0][0] = w;
    scale[1][1] = h;
    scale[2][2] = 0.5f+incr;
    incr += 0.000001f;

    glm_translate(slide, (vec3){x+w/2, y+h/2, 0.0f});

    glUniformMatrix4fv(glGetUniformLocation(fragShader, "scale"), 1, GL_FALSE, (float*)scale);
    glUniformMatrix4fv(glGetUniformLocation(fragShader, "slide"), 1, GL_FALSE, (float*)slide);
    glUniform4f(glGetUniformLocation(fragShader, "u_color2"), r/255.0f, g/255.0f, b/255.0f, a/255.0f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
}

void gl_drawDigit(unsigned int fragShader, int n, float x, float y, float size, int r, int g, int b, float width) {
    assert(n >= 0 && n < 10);
    if(n == 0) {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(n == 1) {
        gl_drawRect(fragShader, x-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(n == 2) {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(n == 3) {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(n == 4) {
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
    } else if(n == 5) {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(n == 6) {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(n == 7) {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(n == 8) {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    }
}

// 7-segment display
// y is the middle of the text
// side can be -1 (aligned right), 0 (centered) or 1 (aligned left)
void gl_drawInteger(unsigned int fragShader, int n, float x, float y, float size, int r, int g, int b, float width, int side) {
    int left = n;
    float curx = x;
    if(left < 0) {
        left *= (-1);
    }
    if(side == 1) {
        curx += (((n==0)+ln_baseN(abs(n), 10)-1)*(size+4*width));
    } else if(side == 0) {
        curx += (((n==0)+ln_baseN(abs(n), 10)-1)*(size+4*width))/2.0;
    }
    while(left > 0) {
        gl_drawDigit(fragShader, left%10, curx, y, size, r, g, b, width);
        curx -= (size+4*width);
        left = left/10;
    }
    if(n == 0) {
        gl_drawDigit(fragShader, 0, curx, y, size, r, g, b, width);
        curx -= (size+4*width);
    }
    if(n < 0) {
        gl_drawRect(fragShader, curx-size/2-width, y, size+2*width, 2*width, r, g, b);
    }
}

void gl_drawFloat(unsigned int fragShader, float n, float x, float y, float size, int r, int g, int b, float width, int side) {
    int left = (int)(n*1000);
    int acc = max(0, 5-ln_baseN(abs(left), 10));
    float curx = x;
    int countf = 0;
    if(left < 0) {
        left *= (-1);
    }
    if(side == 1) {
        curx += (((left==0)+max(4, ln_baseN(abs(left), 10))-1)*(size+4*width));
    } else if(side == 0) {
        curx += (((left==0)+max(4, ln_baseN(abs(left), 10))-1)*(size+4*width))/2.0;
    }
    while(left > 0 || acc > 0) {
        gl_drawDigit(fragShader, left%10, curx, y, size, r, g, b, width);
        curx -= (size+4*width);
        left = left/10;
        countf += 1;
        if(countf == 3) {
            gl_drawRect(fragShader, curx+size/2+3*width/2, y-size-width, width, 2*width, r, g, b);
        }
        if(left == 0) {
            acc -= 1;
        }
    }
    if(n == 0) {
        gl_drawDigit(fragShader, 0, curx, y, size, r, g, b, width);
        curx -= (size+4*width);
    }
    if(n < 0) {
        gl_drawRect(fragShader, curx-size/2-width, y, size+2*width, 2*width, r, g, b);
    }
}

void gl_drawChar(unsigned int fragShader, char ch, float x, float y, float size, int r, int g, int b, float width) {
    if(ch == 'a' || ch == 'A') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'b' || ch == 'B') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size+width, 2*width, 2*size-width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size, 2*width, r, g, b);
    } else if(ch == 'c' || ch == 'C') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'd' || ch == 'D') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2+width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'e' || ch == 'E') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'f' || ch == 'F') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'g' || ch == 'G') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-width, size/2+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'h' || ch == 'H') {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'i' || ch == 'I') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'j' || ch == 'J') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size/2+2*width, 2*width, r, g, b);
    } else if(ch == 'k' || ch == 'K') {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size/2+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size/2-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y+size/2-width, 2*width, size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y+size/2-width, size/2+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size/2-width, size/2+2*width, 2*width, r, g, b);
    } else if(ch == 'l' || ch == 'L') {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'm' || ch == 'M') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'n' || ch == 'N') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'o' || ch == 'O') {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'p' || ch == 'P') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
    } else if(ch == 'q' || ch == 'Q') {
        gl_drawRect(fragShader, x-size/2-width, y-size/2-width, 2*width, 3*size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size/2-width, 2*width, 3*size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size/2-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/4-width, y-size-width, 2*width, size/2+2*width, r, g, b);
    } else if(ch == 'r' || ch == 'R') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/4-width, y-size-width, 2*width, size+2*width, r, g, b);
    } else if(ch == 's' || ch == 'S') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 't' || ch == 'T') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'u' || ch == 'U') {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'v' || ch == 'V') {
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/4-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/4-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size/4+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/4-width, y-width, size/4+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/4-width, y-size-width, size/2+2*width, 2*width, r, g, b);
    } else if(ch == 'w' || ch == 'W') {
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, 2*size+2*width, r, g, b);
    } else if(ch == 'x' || ch == 'X') {
        gl_drawRect(fragShader, x-width, y-size/2-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y+size/2-width, 2*width, size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-size-width, 2*width, size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y+size/2-width, 2*width, size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, size/2+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y+size/2-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size/2-width, size+2*width, 2*width, r, g, b);
    } else if(ch == 'y' || ch == 'Y') {
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, 2*width, size+2*width, r, g, b);
    } else if(ch == 'z' || ch == 'Z') {
        gl_drawRect(fragShader, x-size/2-width, y+size-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x+size/2-width, y-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-width, size+2*width, 2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, 2*width, size+2*width, r, g, b);
        gl_drawRect(fragShader, x-size/2-width, y-size-width, size+2*width, 2*width, r, g, b);
    } else if((int)ch >= 48 && (int)ch <= 57) {
        gl_drawDigit(fragShader, (int)ch -48, x, y, size, r, g, b, width);
    }
}

int get_string_len(char* s) {
    int k = 0;
    while(s[k] != '\0') {
        k+=1;
    }
    return k;
}

// 7-segment display
// y is the middle of the text
// side can be -1 (aligned right), 0 (center) or 1 (aligned left)
void gl_drawString(unsigned int fragShader, char* str, float x, float y, float size, int r, int g, int b, float width, int side) {
    float curx = x;
    int len = get_string_len(str);
    if(side == 1) {
        curx += ((len-1)*(size+4*width));
    } else if(side == 0) {
        curx += ((len-1)*(size+4*width))/2.0f;
    }
    char c = str[len-1];
    int i = len-1;
    while(i >= 0) {
        gl_drawChar(fragShader, c, curx, y, size, r, g, b, width);
        curx -= (size+4*width);
        i -= 1;
        c = str[i];
    }
}

void gl_printf(unsigned int fragShader, float x, float y, float size, float width, int r, int g, int b, const char* str, ...) {
    va_list args;
    va_start(args, str);
    int i = 0;
    char cur = str[i];
    bool is_perc = false;
    float curx = x;
    while(cur != '\0') {
        //printf("%f\n", curx);
        if(is_perc) {
            is_perc = false;
            switch (cur) {
            case 'd':
                int val = (int)(va_arg(args, int));
                //printf("%d", val);
                gl_drawInteger(fragShader, val, curx, y, size, r, g, b, width, 1);
                curx += (size+4*width)*(ln_baseN(abs(val), 10));
                break;
            case 'f':
                float valf = (float)(va_arg(args, double));
                //printf("%d", val);
                gl_drawFloat(fragShader, valf, curx, y, size, r, g, b, width, 1);
                curx += (size+4*width)*(3+ln_baseN(abs((int)valf), 10));
                break;
            case 's':
                char* chval = (char*)(va_arg(args, const char*));
                //printf("%s", chval);
                gl_drawString(fragShader, chval, curx, y, size, r, g, b, width, 1);
                curx += (size+4*width)*(1+get_string_len(chval));
                break;
            default:
                fprintf(stderr, "warning : format %%%c is not supported\n", cur);
                break;
            }
        } else {
            if(cur == '%') {
                is_perc = true;
            } else {
                //printf("%c", cur);
                gl_drawChar(fragShader, cur, curx, y, size, r, g, b, width);
                curx += (size+4*width);
            }
        }

        i += 1;
        cur = str[i];
    }
    va_end(args);
    //printf("\n");
}

void gl_initDrawRect(unsigned int shaderProgram) {
    glUseProgram(shaderProgram);
}

// ------------------------------------------------------------------------------------------------------------------------------------------------ //
// ------------------------------------------------------------------------------------------------------------------------------------------------ //
// ------------------------------------------------------------------------------------------------------------------------------------------------ //

static onoff_button* buttonList;
static interface* interfaceList;

static int MAX_BUTTON_SIZE = 1024;
static int MAX_INTERFACE_SIZE = 192;

static int bListId;
static int intListId;

static int* current_interface;

static bool noMousePoll;

int mn_get_color(int r, int g, int b) {
    if(r+g+b <= 384) {
        return 255;
    } else {
        return 0;
    }
}

void display_button(int but_id, unsigned int fragShader) {
    if(but_id < 0 || but_id > bListId) {
        fprintf(stderr, "ERROR : attempting to display a non-existing button (%d)\n", but_id);
    } else {
        float size = minf(buttonList[but_id].h/2.3f, (buttonList[but_id].w)/(get_string_len(buttonList[but_id].text)));
        int retcol = mn_get_color(buttonList[but_id].red, buttonList[but_id].green, buttonList[but_id].blue);
        gl_drawRect(fragShader, buttonList[but_id].x, buttonList[but_id].y, buttonList[but_id].w, buttonList[but_id].h, buttonList[but_id].red, buttonList[but_id].green, buttonList[but_id].blue);
        gl_drawString(fragShader, buttonList[but_id].text, buttonList[but_id].x+buttonList[but_id].w/2.0f, buttonList[but_id].y+buttonList[but_id].h/2.0f, 0.7f*size, retcol, retcol, retcol, 0.7f*size/10.0f, 0);

    }
}

bool is_hovering_button(int but_id, float mx, float my) {
    if(but_id < 0 || but_id > bListId) {
        fprintf(stderr, "ERROR : attempting to update a non-existing button (%d)\n", but_id);
        return false;
    } else {
        return (mx >= buttonList[but_id].x && my >= buttonList[but_id].y && mx <= buttonList[but_id].x+buttonList[but_id].w && my <= buttonList[but_id].y+buttonList[but_id].h);
    }
}

void display_interface(int int_id, unsigned int fragShader, float mx, float my) {
    if(int_id < 0 || int_id > intListId) {
        fprintf(stderr, "ERROR : attempting to display a non-existing interface (%d)\n", int_id);
    } else {
        float size = minf(interfaceList[int_id].h/2.1f, (interfaceList[int_id].w)/(get_string_len(interfaceList[int_id].title)));
        int retcol = mn_get_color(interfaceList[int_id].red, interfaceList[int_id].green, interfaceList[int_id].blue);
        gl_drawRect(fragShader, interfaceList[int_id].x, interfaceList[int_id].y, interfaceList[int_id].w, interfaceList[int_id].h, interfaceList[int_id].red, interfaceList[int_id].green, interfaceList[int_id].blue);
        gl_drawString(fragShader, interfaceList[int_id].title, interfaceList[int_id].x+interfaceList[int_id].w/2.0f, interfaceList[int_id].y+interfaceList[int_id].h/2.0f, 0.7f*size, retcol, retcol, retcol, 0.7f*size/10.0f, 0);
    
        for(int k = 0; k < interfaceList[int_id].nButtons; k++) {
            if(is_hovering_button(interfaceList[int_id].buttons[k], mx, my)) {
                gl_drawRect(fragShader, buttonList[interfaceList[int_id].buttons[k]].x-0.015f, buttonList[interfaceList[int_id].buttons[k]].y-0.015f, buttonList[interfaceList[int_id].buttons[k]].w+0.03f, buttonList[interfaceList[int_id].buttons[k]].h+0.03f, 255, 255, 255);
            }
            display_button(interfaceList[int_id].buttons[k], fragShader);
        }
    }
}

bool some_bool = true;
unsigned int fShader;
void menu_actions(GLFWwindow *window, int button, int action, int mods) {
    if(*current_interface < 0 || *current_interface > intListId) {
        //fprintf(stderr, "ERROR : attempting to update a non-existing interface (%d)\n", *current_interface);
    } else{
        if(some_bool && !noMousePoll) {
            some_bool = false;
            double mx = 0.0;
            double my = 0.0;
            glfwGetCursorPos(window, &mx, &my);
            mx = 2.0*mx/1500.0 -1.0;
            my = 1.0 -2.0*my/1000.0;
            for(int k = 0; k < interfaceList[*current_interface].nButtons; k++) {
                int but_id = interfaceList[*current_interface].buttons[k];
                if(is_hovering_button(but_id, (float)mx, (float)my)) {
                    if(buttonList[but_id].onClick != NULL) {
                        (*buttonList[but_id].onClick)(buttonList[but_id].arg);
                    }
                    //float prevIncr = incr+0.0001f;
                    float size = minf(interfaceList[*current_interface].h/2.1f, (interfaceList[*current_interface].w)/(3*(buttonList[but_id].cast != INT)+get_string_len(interfaceList[*current_interface].title)));
                    int retcol = mn_get_color(interfaceList[*current_interface].red, interfaceList[*current_interface].green, interfaceList[*current_interface].blue);
                    switch (buttonList[but_id].type) {
                        case WARP:
                            *current_interface = *((int*)buttonList[but_id].metadata);
                            break;

                        case NONE:
                            break;

                        case SET_VAR:
                            bool halt = false;
                            bool pressed = false;
                            bool clicked = false;
                            double toad = 0.0;
                            double mult = 1.0;
                            noMousePoll = true;
                            while(!halt) {
                                toad = 0.0;
                                mult = 1.0;
                                if(glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
                                    pressed = true;
                                    if(!clicked) {
                                        clicked = true;
                                        toad = (buttonList[but_id].sup-buttonList[but_id].inf)/50.0;
                                    }
                                }
                                if(glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
                                    pressed = true;
                                    if(!clicked) {
                                        clicked = true;
                                        toad = (buttonList[but_id].inf-buttonList[but_id].sup)/50.0;
                                    }
                                }
                                if(glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS) {
                                    pressed = true;
                                    if(!clicked) {
                                        clicked = true;
                                        mult = 2.0;
                                    }
                                }
                                if(glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
                                    pressed = true;
                                    if(!clicked) {
                                        clicked = true;
                                        mult = 0.5;
                                    }
                                }
                                if(glfwGetKey(window, GLFW_KEY_ENTER) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) {
                                    halt = true;
                                }

                                if(!pressed) {
                                    clicked = false;
                                }
                                fflush(stdout);

                                gl_drawRect(fShader, buttonList[but_id].x, buttonList[but_id].y, buttonList[but_id].w, buttonList[but_id].h, buttonList[but_id].red, buttonList[but_id].green, buttonList[but_id].blue);
                                gl_drawRect(fShader, -1.0f, 0.7f, 2.0f, 0.3f, 0, 0, 0);
                                gl_drawString(fShader, "press a s m d to add subtract multiply or divide", 0.0f, 0.9f, 0.03f, 192, 192, 64, 0.002f, 0);
                                gl_printf(fShader, -0.8f, 0.8f, 0.03f, 0.003f, 255, 255, 255, "min %f", buttonList[but_id].inf);
                                gl_printf(fShader, 0.5f, 0.8f, 0.03f, 0.003f, 255, 255, 255, "max %f", buttonList[but_id].sup);

                                switch (buttonList[but_id].cast) {
                                    case INT:
                                        int* valuei = (int*)buttonList[but_id].metadata;
                                        *valuei = max(min(((int)toad) + ((int)mult)*(*valuei), (int)buttonList[but_id].sup), (int)buttonList[but_id].inf);
                                        gl_drawInteger(fShader, *valuei, buttonList[but_id].x+buttonList[but_id].w/2.0f, buttonList[but_id].y+buttonList[but_id].h/2.0f, size*0.7f, retcol, retcol, retcol, size/10.0f*0.7f, 0);
                                        break;

                                    case FLOAT:
                                        float* valuef = (float*)buttonList[but_id].metadata;
                                        *valuef = maxf(minf(((float)toad) + ((float)mult)*(*valuef), (float)buttonList[but_id].sup), (float)buttonList[but_id].inf);
                                        gl_drawFloat(fShader, *valuef, buttonList[but_id].x+buttonList[but_id].w/2.0f, buttonList[but_id].y+buttonList[but_id].h/2.0f, size*0.7f, retcol, retcol, retcol, size/10.0f*0.7f, 0);
                                        break;

                                    case DOUBLE:
                                        double* valued = (double*)buttonList[but_id].metadata;
                                        *valued = maxd(mind(((double)toad) + ((double)mult)*(*valued), (double)buttonList[but_id].sup), (double)buttonList[but_id].inf);
                                        gl_drawFloat(fShader, (float)(*valued), buttonList[but_id].x+buttonList[but_id].w/2.0f, buttonList[but_id].y+buttonList[but_id].h/2.0f, size*0.7f, retcol, retcol, retcol, size/10.0f*0.7f, 0);
                                        break;
                                    
                                    default:
                                        break;
                                    }
                                
                                glfwSwapBuffers(window);
                                glfwPollEvents();
                                pressed = false;
                                //incr = prevIncr;
                                //usleep(1000000/60);
                            }
                            //printf("EXIT\n");
                            noMousePoll = false;
                            break;

                        case EXIT:
                            *current_interface = -1;
                            break;
                        
                        default:
                            break;
                    }
                    tan_fov = tan((fov * 3.14159 / 180.0) / 2.0); // just in case FOV is changed
                    break;
                }
            }
        } else {
            some_bool = true;
        }
    }
}

bool isInMenu(GLFWwindow *win, unsigned int fragShader) {
    fShader = fragShader;
    if(*current_interface == -1) {
        return false;
    } else {
        //printf("displaying %d\n", *current_interface);
        double mx = 0.0;
        double my = 0.0;
        glfwGetCursorPos(win, &mx, &my);
        mx = 2.0*mx/1500.0 -1.0;
        my = 1.0 -2.0*my/1000.0;
        display_interface(*current_interface, fragShader, (float)mx, (float)my);
        //menu_actions(win, *current_interface);
        return true;
    }
}

bool isMenuOpen() {
    return (*current_interface != -1);
}

int* numbers;
void init_interf(GLFWwindow *window) {
    buttonList = malloc(sizeof(onoff_button)*MAX_BUTTON_SIZE);
    for(int k = 0; k < MAX_BUTTON_SIZE; k++) {
        buttonList[k].id = -1;
    }
    interfaceList = malloc(sizeof(interface)*MAX_INTERFACE_SIZE);
    for(int k = 0; k < MAX_INTERFACE_SIZE; k++) {
        interfaceList[k].intfid = -1;
    }
    bListId = 0;
    intListId = 0;
    numbers = malloc(sizeof(int)*256);
    for(int k = 0; k < 256; k++) {
        numbers[k] = k;
    }
    current_interface = malloc(sizeof(int));
    *current_interface = -1;
    noMousePoll = false;
    glfwSetMouseButtonCallback(window, menu_actions);
}

void reset_interf() {
    bListId = 0;
    intListId = 0;
    *current_interface = -1;
    noMousePoll = false;
}

void free_interf() {
    free(buttonList);
    for(int k = 0; k < intListId; k++) {
        free(interfaceList[k].buttons);
    }
    free(interfaceList);
    free(current_interface);
    free(numbers);
}

// returns the ID of the new button (-1 if error)
// actn can be within {NONE, WARP, SET_VAR, EXIT}
// if actn is WARP or SET_VAR then val shall not be NULL
// min and max dont matter is actn is not SET_VAR
int button_create_onoff(char* text, int red, int green, int blue, float x, float y, float w, float h, button_action actn, void* val, double min, double max, void_type cast, void (*onClick)(void*), void* arg) {
    if(bListId < MAX_BUTTON_SIZE) {

        buttonList[bListId].id = bListId;
        buttonList[bListId].text = text;
        buttonList[bListId].red = red;
        buttonList[bListId].green = green;
        buttonList[bListId].blue = blue;

        buttonList[bListId].x = x;
        buttonList[bListId].y = y;
        buttonList[bListId].w = w;
        buttonList[bListId].h = h;

        buttonList[bListId].type = actn;
        buttonList[bListId].metadata = val;
        buttonList[bListId].cast = cast;

        buttonList[bListId].inf = min;
        buttonList[bListId].sup = max;

        buttonList[bListId].onClick = onClick;
        buttonList[bListId].arg = arg;

        bListId += 1;
        return (bListId-1);
    } else {
        fprintf(stderr, "WARNING : max button capacity reached.\n");
        return -1;
    }
}

// returns the ID of the new interface (-1 if error)
int interface_create(char* title, int red, int green, int blue, float x, float y, float w, float h) {
    if(intListId < MAX_INTERFACE_SIZE) {
        interfaceList[intListId].intfid = intListId;

        interfaceList[intListId].title = title;
        interfaceList[intListId].red = red;
        interfaceList[intListId].green = green;
        interfaceList[intListId].blue = blue;

        interfaceList[intListId].x = x;
        interfaceList[intListId].y = y;
        interfaceList[intListId].w = w;
        interfaceList[intListId].h = h;

        interfaceList[intListId].buttons = malloc(sizeof(int*)*16);
        interfaceList[intListId].nButtons = 0;
        interfaceList[intListId].nMemButtons = 16;

        intListId += 1;
        return (intListId-1);
    } else {
        fprintf(stderr, "WARNING : max interface capacity reached.\n");
        return -1;
    }
}

void interface_link_button(int interface_id, int button_id) {
    if(interface_id < 0 || interface_id >= intListId) {
        fprintf(stderr, "ERROR : attempting to link non-existing interface (%d)\n", interface_id);
    } else if(button_id < 0 || button_id >= bListId) {
        fprintf(stderr, "ERROR : attempting to link non-existing button (%d)\n", button_id);
    } else {
        if(interfaceList[interface_id].nMemButtons == interfaceList[interface_id].nButtons) {
            int* newI = malloc(sizeof(int)*2*interfaceList[interface_id].nMemButtons);
            for(int k = 0; k < interfaceList[interface_id].nMemButtons; k++) {
                newI[k] = interfaceList[interface_id].buttons[k];
            }
            free(interfaceList[interface_id].buttons);
            interfaceList[interface_id].buttons = newI;
            interfaceList[interface_id].nMemButtons *= 2;
        }
        interfaceList[interface_id].buttons[interfaceList[interface_id].nButtons] = button_id;
        interfaceList[interface_id].nButtons += 1;
    }
}

void interface_unlink_button(int interface_id, int button_id) {
    if(interface_id < 0 || interface_id >= intListId) {
        fprintf(stderr, "ERROR : attempting to unlink non-existing interface (%d)\n", interface_id);
    } else if(button_id < 0 || button_id >= bListId) {
        fprintf(stderr, "ERROR : attempting to unlink non-existing button (%d)\n", button_id);
    } else {
        int not_found = 1;
        for(int k = 0; k < not_found*interfaceList[interface_id].nButtons; k++) {
            if(interfaceList[interface_id].buttons[k] == button_id) {
                not_found = 0;
                interfaceList[interface_id].buttons[k] = interfaceList[interface_id].buttons[interfaceList[interface_id].nButtons-1];
                interfaceList[interface_id].nButtons -= 1;
            }
            if(not_found) {
                fprintf(stderr, "WARNING : interface %d does not link to buttton %d\n", interface_id, button_id);
            }
        }
    }
}

void interface_set(int interface_id) {
    *current_interface = interface_id;
}

// build and link everything here
int button_ok;
int button_pay;
void build_all_menus() {
    int welcome_start_go = button_create_onoff("start",       0, 255, 255, -0.25f,  0.05f,  0.5f, 0.3f, EXIT, NULL, 0.0, 0.0, INT, NULL, NULL);
    int welcome_start_settings = button_create_onoff("config", 96, 96, 96, -0.25f, -0.35f, 0.5f, 0.3f, WARP, &(numbers[1]), INT, 0.0, 0.0, NULL, NULL);
    int welcome_start_i = interface_create("Welcome",       255, 255, 255, -0.4f,    0.7f,  0.8f, 0.25f);

    interface_link_button(welcome_start_i, welcome_start_go);
    interface_link_button(welcome_start_i, welcome_start_settings);

    
    int settings_speed = button_create_onoff("speed",             0, 192, 192, -0.25f,  0.2f,  0.5f, 0.3f, SET_VAR, &speed, 0.1, 20.0, DOUBLE, NULL, NULL);
    int settings_sensitivity = button_create_onoff("sensibility", 192, 192, 0, -0.25f, -0.15f, 0.5f, 0.3f, SET_VAR, &sensitivity, 0.01, 0.5, DOUBLE, NULL, NULL);
    int settings_fov = button_create_onoff("fov",                 192, 0, 192, -0.25f, -0.5f, 0.5f, 0.3f, SET_VAR, &fov, 30.0, 150.0, DOUBLE, NULL, NULL);
    int settings_exit = button_create_onoff("back",               192, 64, 64, -0.25f, -0.85f,  0.5f, 0.3f, WARP, &(numbers[0]), INT, 0.0, 0.0, NULL, NULL);
    int settings_i = interface_create("Settings",               128, 128, 128, -0.4f ,  0.7f,  0.8f, 0.25f);

    interface_link_button(settings_i, settings_speed);
    interface_link_button(settings_i, settings_sensitivity);
    interface_link_button(settings_i, settings_fov);
    interface_link_button(settings_i, settings_exit);
    
    button_ok = button_create_onoff("ok", 32, 255, 32, -0.2f, -0.9f, 0.4f, 0.3f, EXIT, NULL, 0.0, 0.0, INT, NULL, NULL);
    button_pay = button_create_onoff("advance", 32, 255, 32, -0.2f, -0.9f, 0.4f, 0.3f, EXIT, NULL, 0.0, 0.0, INT, NULL, NULL);
    interface_set(welcome_start_i);
}

// returns the ID of the newly created interface
int build_text_box(char* msg, int red, int green, int blue) {
    int intf = interface_create(msg, red, green, blue, -0.9f, 0.05f, 1.8f, 0.85f);

    interface_link_button(intf, button_ok);

    return intf;
}

// returns the ID of the newly created interface
int build_lock_box(char* msg, int red, int green, int blue) {
    int intf = interface_create(msg, red, green, blue, -0.9f, 0.05f, 1.8f, 0.85f);

    interface_link_button(intf, button_pay);

    return intf;
}