#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 "display.h"
#include "menus.h"
#include "proj.h"
#include "entities.h"

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

bool is_colliding_with_map(cube_0* cb) {
    for(int k = 0; k < current_room->map_size; k++) {
        for(int d = 0; d < 8; d++) {
            if(distance_pt_cube_0_3d(cb->x+cb->w*(d%2==0), cb->y+cb->h*((d/2)%2==0), cb->z+cb->d*((d/4)%2==0), current_room->map[k]) <= 0.01) {
                return true;
            }
        }
    }
    return false;
}

bool is_colliding_with_tp(cube_0* cb) {
    for(int k = 0; k < current_room->tps_size; k++) {
        for(int d = 0; d < 8; d++) {
            if(distance_pt_cube_0_3d(cb->x+cb->w*(d%2==0), cb->y+cb->h*((d/2)%2==0), cb->z+cb->d*((d/4)%2==0), current_room->tps[k]->hitbox) <= 0.01) {
                return true;
            }
        }
    }
    return false;
}

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

static double choffx, choffz;
void update_entity(entity* ent, float dtime) {
    (*ent->updatePos)(ent->pos->x, ent->pos->y, ent->pos->z, ent->pos->w, ent->pos->h, ent->pos->d, ent->pos->hz_angle, ent->pos->vt_angle, dtime, ent, ent->pos);
}

void update_entities(float dtime, room* rtd) {
    if(rtd != NULL) {
        for(int k = 0; k < rtd->ent_len; k++) {
            if(rtd->ents[k]->updatePos != NULL) {
                //printf("e\n");
                update_entity(rtd->ents[k], dtime);
            }
        }
    }
}

void update_nearby_entities(float dtime, int render_distance) {
    for(int w = -render_distance; w <= render_distance; w++) {
        for(int h = -render_distance; h <= render_distance; h++) {
            choffx = 2*room_width*w;
            choffz = 2*room_depth*h;
            update_entities(dtime, hashtbl_find_opt(visited, player_chx+w, player_chy+h));
        }
    }
}

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

void speen(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    ret->hz_angle += ((double)dtime)*1.5;
}

void speen2(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    ret->hz_angle += ((double)dtime)*2.5;
    if((int)(5.0*ret->hz_angle) != (int)(5.0*(ret->hz_angle - ((double)dtime)*22.5))) {
        double dx = (x+w/2 - (camx-choffx));
        double dy = (y+h/2 - (camy));
        double dz = (z+d/2 - (camz-choffz));
        double total = sqrt(dx*dx + dy*dy + dz*dz);
        dx = 110.0*dx/total;
        dy = 110.0*dy/total;
        dz = 110.0*dz/total;
        appendProj(x+w/2+choffx, y+h/2, z+d/2+choffz, 0.1, 0.1, 0.1, -dx, -dy, -dz, 0.0, 0.0, 0.0, 255, 0, 0, 10, 3.0);
    }
}

void speen3(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    ret->vt_angle += ((double)dtime)*2.5;
}

// metad{1,2,3} = og pos
// metad{4,5,6} = amplitudes
// metai{1} = frequency multiplier
// metai{2} = frequency divider
// metai{3} = phase
void moving_xyz(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    ret->x = ent->metad1 + ent->metad4*cos((double)(ent->metai1*sim_time/ent->metai2 + ent->metai3*3.14159/180.0));
    ret->y = ent->metad2 + ent->metad5*cos((double)(ent->metai1*sim_time/ent->metai2 + ent->metai3*3.14159/180.0));
    ret->z = ent->metad3 + ent->metad6*cos((double)(ent->metai1*sim_time/ent->metai2 + ent->metai3*3.14159/180.0));
    //printf("%lf %lf %lf\n", ret->x, ret->y, ret->z);
}

// metad{1,2,3} = og pos
// metad{4,5,6} = speed
// metad{7,8,9} = max_delta
// metai{1} = x_side (+/- 1)
// metai{2} = y_side (+/- 1)
// metai{3} = z_side (+/- 1)
void moving_xyz_line(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    //printf("%lf %lf %lf", ent->metad7, ent->metad8, ent->metad9);
    //printf("| %lf %lf %lf\n\n", ent->metad4, ent->metad5, ent->metad6);
    //printf("%lf %lf %lf -> (%lf %lf %lf)\n", x, y, z, (ent->metai1)*ent->metad4*dtime, (ent->metai2)*ent->metad5*dtime, (ent->metai3)*ent->metad6*dtime);
    //printf("(%d %lf) (%d %lf) (%d %lf)\n\n", (ent->metai1), ent->metad4, (ent->metai2), ent->metad5, (ent->metai3), ent->metad6);
    ret->x += (ent->metai1)*ent->metad4*dtime;
    if(absf(ent->metad1 - ret->x) > ent->metad7) {
        //printf("-x-\n");
        ent->metai1 *= (-1);
        ret->x += (ent->metai1)*ent->metad4*dtime;
    }

    ret->y += (ent->metai2)*ent->metad5*dtime;
    if(absf(ent->metad2 - ret->y) > ent->metad8) {
        //printf("-y-\n");
        ent->metai2 *= (-1);
        ret->y += (ent->metai2)*ent->metad5*dtime;
    }

    ret->z += (ent->metai3)*ent->metad6*dtime;
    if(absf(ent->metad3 - ret->z) > ent->metad9) {
        //printf("-z-\n");
        ent->metai3 *= (-1);
        ret->z += (ent->metai3)*ent->metad6*dtime;
    }
}



// metai1 = id of the interface
// metai2 = price
// metai3 = doPay
// metach1 = text (stored here to free() easily)
void subtitle_text_box(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    if(distance_pt_cube_0_3d_infinite(camx+player_chx*room_width*2, camy, camz+player_chy*room_depth*2, ret) <= 1.5) {
        gl_drawString(fShader, ent->metach1, 0.0f, -0.7f, 0.03f, ret->red, ret->green, ret->blue, 0.003f, 0);
    }
}

// metad1 = time ON
// metad2 = time OFF
// metad3 = current time left
// metai1 = 0 if OFF, 1 if ON
void beating_block(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    ent->metad3 -= dtime;
    if(ent->metad3 <= 0.0) {
        if(ent->metai1) {
            ent->metad3 = ent->metad2;
        } else {
            ent->metad3 = ent->metad1;
        }
        ent->metai1 = 1-ent->metai1;
    }
}

// metad1 = hz_speed
// metad2 = vt_speed
void spinning_platform(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    ret->hz_angle += ent->metad1*dtime;
    ret->vt_angle += ent->metad2*dtime;
}

void spinning_translate(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    
}

// metai1 = button freq                 (same for blocks)
// matei2 = trigger state (0/1)         ('OFF state' for blocks)
// metad1 = button activation time
void update_button(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    if(!buttonSwitch[ent->metai1]) {
        buttonSwitch[ent->metai1] = true;
        buttonTimes[ent->metai1] = ent->metad1;
    }
}

void detectHit(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    if(ret->red == 193) {
        ret->red = 0;
        ret->green = 192;
        ret->blue = 0;
        coins += *hp;
        player_hp -= (*dmg);
        if(*dmg != 0) {
            fade_dmg = 255;
        }
        *hp = 0;
    }
}

void money(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    coins += *hp;
    player_hp -= (*dmg);
    if(*dmg != 0) {
        fade_dmg = 255;
    }
    *hp = 0;
}

void translatePlayer(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    double dx = ent->metad4*(cos((double)(ent->metai1*(sim_time+(double)dtime)/ent->metai2 + ent->metai3*3.14159/180.0))-cos((double)(ent->metai1*sim_time/ent->metai2 + ent->metai3*3.14159/180.0)));
    double dy = ent->metad5*(cos((double)(ent->metai1*(sim_time+(double)dtime)/ent->metai2 + ent->metai3*3.14159/180.0))-cos((double)(ent->metai1*sim_time/ent->metai2 + ent->metai3*3.14159/180.0)));
    double dz = ent->metad6*(cos((double)(ent->metai1*(sim_time+(double)dtime)/ent->metai2 + ent->metai3*3.14159/180.0))-cos((double)(ent->metai1*sim_time/ent->metai2 + ent->metai3*3.14159/180.0)));
    //fx += dx/(dtime*dtime);
    //fy += dy/(dtime*dtime);
    //fz += dz/(dtime*dtime);
    camvx += dx/(dtime);
    camvy += dy/(dtime);
    camvz += dz/(dtime);
}

void translatePlayerLine(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    double dx = (ent->metai1)*ent->metad4*dtime;
    double dy = (ent->metai2)*ent->metad5*dtime;
    double dz = (ent->metai3)*ent->metad6*dtime;
    //fx += dx/(dtime*dtime);
    //fy += dy/(dtime*dtime);
    //fz += dz/(dtime*dtime);
    camvx += dx/(dtime);
    camvy += dy/(dtime);
    camvz += dz/(dtime);
}

void go_to_player(double x, double y, double z, double w, double h, double d, double hz_angle, double vt_angle, float dtime, entity* ent, cube_0* ret) {
    double dx = (x+w/2 - (camx-choffx));
    double dy = (y+h/2 - (camy));
    double dz = (z+d/2 - (camz-choffz));
    double total = sqrt(dx*dx + dy*dy + dz*dz);
    dx = 11.0*dx/total;
    dy = 11.0*dy/total;
    dz = 11.0*dz/total;
    ret->x -= dtime*dx;
    if(is_colliding_with_map(ret) || is_colliding_with_tp(ret)) {
        ret->x += dtime*dx;
    }
    ret->y -= dtime*dy;
    if(is_colliding_with_map(ret) || is_colliding_with_tp(ret)) {
        ret->y += dtime*dy;
    }
    ret->z -= dtime*dz;
    if(is_colliding_with_map(ret) || is_colliding_with_tp(ret)) {
        ret->z += dtime*dz;
    }
    if((int)(ret->x+ret->y+ret->z) != (int)(ret->x+ret->y+ret->z-dx-dy-dz)) {
        
    }
}

void explodeOnHit(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    player_hp -= (*dmg);
    if(*dmg != 0) {
        fade_dmg = 255;
    }
    *hp = 0;
}

// metai1 = id of the interface
// metach1 = text (stored here to free() easily)
void pop_text(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    interface_set(ent->metai1);
}

// metai1 = id of the interface
// metai2 = room count
// metach1 = dest folder
// metach2 = text (stored here to free() easily)
void pop_and_tp(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    interface_set(ent->metai1);
    newRoomName = ent->metach1;
    newRoomCount = ent->metai2;
    //printf("%s %d\n", newRoomName, newRoomCount);
    switchRoom = true;
}

// metai1 = id of the interface
// metai2 = price
// metai3 = doPay
// metach1 = text (stored here to free() easily)
void locker(float dtime, int* hp, int* dmg, entity* ent, cube_0* ret) {
    //printf("%d %d %d <<<<\n", ent->metai1, ent->metai2, ent->metai3);
    //interface_set(ent->metai1);
    if(coins >= ent->metai2) {
        if(ent->metai3) {
            coins -= ent->metai2;
        }
        *hp = 0;
    }
}