#include <stdio.h>
#include <stdlib.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 <GLFW/glfw3.h>
#include <cglm/cglm.h>

#include "hash.h"
#include "base.h"
#include "move.h"
#include "menus.h"
#include "proj.h"
#include "entities.h"
#include "generation.h"
#include "display.h"

int flashlight = 10 ;

int MAX_SIZE = 8192 ;

int* drawOrder ;

int loc_scale;
int loc_model;
int loc_view;
int loc_proj;

int loc_frag;
int loc_tex;

int loc_tex_tr;

void init_draworder() {
    drawOrder = malloc(sizeof(int)*6) ;
}

float vertices_tr[9] ;
void gl_renderTriangle(unsigned int shaderProgram, unsigned int VAO, unsigned int VBO, double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, float r, float g, float b) {
    vertices_tr[0] = (float)x0;
    vertices_tr[1] = (float)y0;
    vertices_tr[2] = (float)z0;
    vertices_tr[3] = (float)x1;
    vertices_tr[4] = (float)y1;
    vertices_tr[5] = (float)z1;
    vertices_tr[6] = (float)x2;
    vertices_tr[7] = (float)y2;
    vertices_tr[8] = (float)z2;

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_tr), vertices_tr, GL_STATIC_DRAW);

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

    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    glBindBuffer(GL_ARRAY_BUFFER, 0); 

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0); 

    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

//double near = 0.4 ;
double near = 0.0 ;
double far = 10.0 ;

double top = 1.0 ;
double bottom = -1.0 ;
double left = -1.0 ;
double right = 1.0 ;

mat4 model, view, projection;
mat4 scale;
mat2 texShift;
float vertices[180];

// -x ; +x ; -y ; +y ; -z ; +z //
void init_vertices() {
    vertices[0] = -0.5f; vertices[1] = -0.5f; vertices[2] = -0.5f; vertices[3] = 0.0; vertices[4] = 1.0;
    vertices[5] = -0.5f; vertices[6] =  0.5f; vertices[7] = -0.5f; vertices[8] = 0.0; vertices[9] = 0.0;
    vertices[10] = -0.5f; vertices[11] =  0.5f; vertices[12] =  0.5f; vertices[13] = 1.0; vertices[14] = 0.0;
    vertices[15] = -0.5f; vertices[16] = -0.5f; vertices[17] = -0.5f; vertices[18] = 0.0; vertices[19] = 1.0;
    vertices[20] = -0.5f; vertices[21] =  0.5f; vertices[22] =  0.5f; vertices[23] = 1.0; vertices[24] = 0.0;
    vertices[25] = -0.5f; vertices[26] = -0.5f; vertices[27] =  0.5f; vertices[28] = 1.0; vertices[29] = 1.0;

    vertices[30] =  0.5f; vertices[31] = -0.5f; vertices[32] = -0.5f; vertices[33] = 1.0; vertices[34] = 1.0;
    vertices[35] =  0.5f; vertices[36] =  0.5f; vertices[37] =  0.5f; vertices[38] = 0.0; vertices[39] = 0.0;
    vertices[40] =  0.5f; vertices[41] =  0.5f; vertices[42] = -0.5f; vertices[43] = 1.0; vertices[44] = 0.0;
    vertices[45] =  0.5f; vertices[46] = -0.5f; vertices[47] = -0.5f; vertices[48] = 1.0; vertices[49] = 1.0;
    vertices[50] =  0.5f; vertices[51] = -0.5f; vertices[52] =  0.5f; vertices[53] = 0.0; vertices[54] = 1.0;
    vertices[55] =  0.5f; vertices[56] =  0.5f; vertices[57] =  0.5f; vertices[58] = 0.0; vertices[59] = 0.0;

    vertices[60] = -0.5f; vertices[61] = -0.5f; vertices[62] = -0.5f; vertices[63] = 0.0; vertices[64] = 1.0;
    vertices[65] =  0.5f; vertices[66] = -0.5f; vertices[67] =  0.5f; vertices[68] = 1.0; vertices[69] = 0.0;
    vertices[70] =  0.5f; vertices[71] = -0.5f; vertices[72] = -0.5f; vertices[73] = 0.0; vertices[74] = 0.0;
    vertices[75] = -0.5f; vertices[76] = -0.5f; vertices[77] = -0.5f; vertices[78] = 0.0; vertices[79] = 1.0;
    vertices[80] = -0.5f; vertices[81] = -0.5f; vertices[82] =  0.5f; vertices[83] = 1.0; vertices[84] = 1.0;
    vertices[85] =  0.5f; vertices[86] = -0.5f; vertices[87] =  0.5f; vertices[88] = 1.0; vertices[89] = 0.0;

    vertices[90] = -0.5f; vertices[91] =  0.5f; vertices[92] = -0.5f; vertices[93] = 1.0; vertices[94] = 1.0;
    vertices[95] =  0.5f; vertices[96] =  0.5f; vertices[97] = -0.5f; vertices[98] = 1.0; vertices[99] = 0.0;
    vertices[100] =  0.5f; vertices[101] =  0.5f; vertices[102] =  0.5f; vertices[103] = 0.0; vertices[104] = 0.0;
    vertices[105] = -0.5f; vertices[106] =  0.5f; vertices[107] = -0.5f; vertices[108] = 1.0; vertices[109] = 1.0;
    vertices[110] =  0.5f; vertices[111] =  0.5f; vertices[112] =  0.5f; vertices[113] = 0.0; vertices[114] = 0.0;
    vertices[115] = -0.5f; vertices[116] =  0.5f; vertices[117] =  0.5f; vertices[118] = 0.0; vertices[119] = 1.0;

    vertices[120] = -0.5f; vertices[121] = -0.5f; vertices[122] = -0.5f; vertices[123] = 1.0; vertices[124] = 1.0;
    vertices[125] =  0.5f; vertices[126] = -0.5f; vertices[127] = -0.5f; vertices[128] = 0.0; vertices[129] = 1.0;
    vertices[130] =  0.5f; vertices[131] =  0.5f; vertices[132] = -0.5f; vertices[133] = 0.0; vertices[134] = 0.0;
    vertices[135] = -0.5f; vertices[136] = -0.5f; vertices[137] = -0.5f; vertices[138] = 1.0; vertices[139] = 1.0;
    vertices[140] =  0.5f; vertices[141] =  0.5f; vertices[142] = -0.5f; vertices[143] = 0.0; vertices[144] = 0.0;
    vertices[145] = -0.5f; vertices[146] =  0.5f; vertices[147] = -0.5f; vertices[148] = 1.0; vertices[149] = 0.0;

    vertices[150] = -0.5f; vertices[151] = -0.5f; vertices[152] =  0.5f; vertices[153] = 0.0; vertices[154] = 1.0;
    vertices[155] =  0.5f; vertices[156] =  0.5f; vertices[157] =  0.5f; vertices[158] = 1.0; vertices[159] = 0.0;
    vertices[160] =  0.5f; vertices[161] = -0.5f; vertices[162] =  0.5f; vertices[163] = 1.0; vertices[164] = 1.0;
    vertices[165] = -0.5f; vertices[166] = -0.5f; vertices[167] =  0.5f; vertices[168] = 0.0; vertices[169] = 1.0;
    vertices[170] = -0.5f; vertices[171] =  0.5f; vertices[172] =  0.5f; vertices[173] = 0.0; vertices[174] = 0.0;
    vertices[175] =  0.5f; vertices[176] =  0.5f; vertices[177] =  0.5f; vertices[178] = 1.0; vertices[179] = 0.0;
}

void gl_renderCube(cube_0* c, double offx, double offy, double offz) {
    glm_mat4_identity(model);
    glm_mat4_identity(scale);
    scale[0][0] = (float)(c->w);
    scale[1][1] = (float)(c->h);
    scale[2][2] = (float)(c->d);
    glm_translate(model, (vec3){(float)(c->x+c->w/2.0+offx), (float)(c->y+c->h/2.0+offy), (float)(c->z+c->d/2.0+offz)});
    glm_rotate(model, (float)(c->vt_angle), (vec3){1.0f, 0.0f, 0.0f});
    glm_rotate(model, (float)(c->hz_angle), (vec3){0.0f, 1.0f, 0.0f});
    
    glm_mat2_identity(texShift);
    texShift[0][0] = (float)(maxd(maxd(c->w, c->h), c->d)/mind(mind(c->w, c->h), c->d));
    texShift[1][1] = texShift[0][0];

    glUniformMatrix4fv(loc_scale, 1, GL_FALSE, (float*)scale);
    glUniformMatrix4fv(loc_model, 1, GL_FALSE, (float*)model);
    glUniformMatrix4fv(loc_view, 1, GL_FALSE, (float*)view);
    glUniformMatrix4fv(loc_proj, 1, GL_FALSE, (float*)projection);

    glUniform4f(loc_frag, c->red/255.0f, c->green/255.0f, c->blue/255.0f, 1.0f);
    glUniformMatrix2fv(loc_tex_tr, 1, GL_FALSE, (float*)texShift);

    glDrawArrays(GL_TRIANGLES, 0, 36);
    //glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    triCount += 12;
}

double px, py, pz;
double px2, py2, pz2;
bool is_visible(cube_0* cb, double offx, double offy, double offz) {
    //return true;
    for(int d = 0; d < 8; d++) {
        project_to_cube(cb->x+cb->w*(d%2==0), cb->y+cb->h*((d/2)%2==0), cb->z+cb->d*((d/4)%2==0), &px, &py, &pz, cb);
        project_to_camera(px+offx, py+offy, pz+offz, &px2, &py2, &pz2);
        if(pz2 >= near) {
            return true;
        }
    }
    return false;
}

void gl_resetTexture() {
    glBindTexture(GL_TEXTURE_2D, textures[0]);
}

bool is_button_block_on(entity* ent) {
    return (
        (ent->entity_type != 11 && ent->entity_type != 12) ||
        (ent->entity_type == 11 && buttonSwitch[ent->metai1]) ||
        (ent->entity_type == 12 && xor(buttonSwitch[ent->metai1], ent->metai2))
    );
}

void gl_renderAll(room* rtd, double offx, double offy, double offz) {
    //printf("------------------------\n");
    if(rtd != NULL) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, textures[5]);
        for(int k = 0; k < rtd->map_size; k++) {
            if(is_visible(rtd->map[k], offx, offy, offz)) {
                gl_renderCube(rtd->map[k], offx, offy, offz);
            }
        }
        glBindTexture(GL_TEXTURE_2D, textures[6]);
        for(int k = 0; k < rtd->tps_size; k++) {
            if(is_visible(rtd->tps[k]->hitbox, offx, offy, offz)) {
                gl_renderCube(rtd->tps[k]->hitbox, offx, offy, offz);
            }
        }
        for(int k = 0; k < rtd->ent_len; k++) {
            if(is_visible(rtd->ents[k]->pos, offx, offy, offz)) {
                if(is_button_block_on(rtd->ents[k]) && (rtd->ents[k]->entity_type != 9 || rtd->ents[k]->metai1)) {
                    glBindTexture(GL_TEXTURE_2D, textures[rtd->ents[k]->tex]);
                } else {
                    glBindTexture(GL_TEXTURE_2D, textures[rtd->ents[k]->tex2]);
                }
                gl_renderCube(rtd->ents[k]->pos, offx, offy, offz);
            }
        }
    }
    //printf("+++++++++++++++\n");
}

void gl_renderNearbyChunks(int render_distance) {
    for(int w = -render_distance; w <= render_distance; w++) {
        for(int h = -render_distance; h <= render_distance; h++) {
            //printf("(%d %d -> %d)", w, h, hashtbl_find_opt(visited, player_chx+w, player_chy+h)->ent_len);
            gl_renderAll(hashtbl_find_opt(visited, player_chx+w, player_chy+h), (2.0*room_width)*w, 0.0, (2.0*room_depth)*h);
        }
    }
    //printf("\n\n");
}

vec3 dir0;
vec3 direction;
void gl_initRender(unsigned int shaderProgram, unsigned int fragmentShader, unsigned int VAO, unsigned int VBO) {
    dir0[0] = sinf((float)(rot_hz)) * cosf((float)(rot_vt));
    dir0[1] = -sinf((float)(rot_vt));
    dir0[2] = cosf((float)(rot_hz)) * cosf((float)(rot_vt));

    glm_vec3_normalize(dir0); // Normalize to unit length
    glm_vec3_add((vec3){(float)camx, (float)camy, (float)camz}, dir0, direction);

    glm_mat4_identity(view);
    glm_lookat((vec3){(float)camx, (float)camy, (float)camz}, direction, (vec3){0.0f, 1.0f, 0.0f}, view);

    glm_perspective(glm_rad((float)fov), 1500.0f / 1000.0f, 0.1f, 100.0f, projection);

    loc_scale = glGetUniformLocation(shaderProgram, "scale");
    loc_model = glGetUniformLocation(shaderProgram, "model");
    loc_view = glGetUniformLocation(shaderProgram, "view");
    loc_proj = glGetUniformLocation(shaderProgram, "projection");

    loc_tex_tr = glGetUniformLocation(fragmentShader, "translation");

    loc_frag = glGetUniformLocation(fragmentShader, "u_color");
}

void gl_drawData(unsigned int shaderProg) {
    gl_drawInteger(shaderProg, (int)camx + 2*room_width*player_chx, 0.95f, 0.9f, 0.05f, 255, 255, 255, 0.005, -1);
    gl_drawInteger(shaderProg, (int)camy, 0.95f, 0.75f, 0.05f, 255, 255, 255, 0.005, -1);
    gl_drawInteger(shaderProg, (int)camz + 2*room_depth*player_chy, 0.95f, 0.6f, 0.05f, 255, 255, 255, 0.005, -1);

    gl_drawInteger(shaderProg, (int)10.0*camvx, 0.95f, -0.6f, 0.05f, 255, 255, 255, 0.005, -1);
    gl_drawInteger(shaderProg, (int)10.0*camvy, 0.95f, -0.75f, 0.05f, 255, 255, 255, 0.005, -1);
    gl_drawInteger(shaderProg, (int)10.0*camvz, 0.95f, -0.9f, 0.05f, 255, 255, 255, 0.005, -1);

    gl_drawInteger(shaderProg, njumps, 0.0f, 0.80f, 0.04f, 255, 255, 128, 0.005f, 1);

    gl_printf(shaderProg, -0.95f, 0.9f, 0.04f, 0.004f, 255-player_hp/4, player_hp/4, 0, "HP %d", player_hp);
    gl_printf(shaderProg, -0.95f, 0.8f, 0.04f, 0.004f, 255, 255, 0, "coins %d", coins);
    //gl_drawInteger(shaderProg, player_hp, -0.95f, 0.9f, 0.05f, 255-player_hp/4, player_hp/4, 0, 0.005f, 1);
}

float fadeSideWidth = 0.08f;
void gl_drawDamageFadeout(unsigned int shaderProg) {
    if(dmgCD > 0.0f) {
        gl_drawRectAlpha(shaderProg, -1.0f              , -1.0f              , 2.0f, fadeSideWidth, 255, 32, 32, (int)(dmgCD*510.0f));
        gl_drawRectAlpha(shaderProg, -1.0f              ,  1.0f-fadeSideWidth, 2.0f, fadeSideWidth, 255, 32, 32, (int)(dmgCD*510.0f));
        gl_drawRectAlpha(shaderProg, -1.0f              , -1.0f+fadeSideWidth, fadeSideWidth, 2.0f*(1.0-fadeSideWidth), 255, 32, 32, (int)(dmgCD*510.0f));
        gl_drawRectAlpha(shaderProg,  1.0f-fadeSideWidth, -1.0f+fadeSideWidth, fadeSideWidth, 2.0f*(1.0-fadeSideWidth), 255, 32, 32, (int)(dmgCD*510.0f));
    }
}