#include <stdint.h>
#include <stdbool.h>
#include "common.hpp"
#include <cstring>
#include <list>
#include <raymath.h>
#include <iostream>
#include <rlgl.h>
#include <vector>
#include <algorithm>

#define PIXUNIT 1.0f / 16.0f

#ifndef SERVER
extern Camera camera;
extern Font fnt;
extern Texture2D terrain;

Texture2D playerTex;
Texture2D bomb;
Sound nekit_idle;
void EntitiesInit() {
    playerTex = LoadTexture(Pathify("player.png"));
    bomb = LoadTexture(Pathify("bomb.png"));
    nekit_idle = LoadSound(Pathify("sound/nekit/idle.ogg"));
}
extern bool online;
extern Vector3 player_position;
extern Vector3 player_velocity;
void TakeDamage(float amount);
void SetBlockNetwork(Chunk world[], int bx, int by, int bz, int t);
#else
    bool online = true;
#endif

std::list<Entity> entities;

Entity NewEntity() {
    Entity ent;
    ent.id = entities.size();
    ent.type = 0;
    ent.yaw = 0;
    ent.free = false;
    return ent;
}

void AddEntity(Entity ent) {
    entities.push_back(ent);
}

static void UpdateEntityPhysics(Chunk world[], Entity& ent, float dt) {
    Vector3 velocityTgt = Vector3Add(ent.velocity, {0, -dt * 20.0f, 0});
    Vector3 tgt = Vector3Add(ent.position, Vector3Scale(velocityTgt, dt));
    Vector3 final = ent.position;
    if(!GetBlock(world, (int)tgt.x, (int)final.y, (int)final.z)) {
        final.x = tgt.x;
    }
    if(!GetBlock(world, (int)final.x, (int)tgt.y, (int)final.z)) {
        final.y = tgt.y;
    }
    else {
        velocityTgt.y = 0;
    }
    if(!GetBlock(world, (int)final.x, (int)final.y, (int)tgt.z)) {
        final.z = tgt.z;
    }
    ent.velocity = velocityTgt;
    ent.position = final;
}
void UpdateEntities(Chunk world[], float dt) {
    if(entities.size() > 0) {
        int idx = 0;
        for(Entity& ent : entities) {
            ent.smoothing = 0;
            if(ent.type != 1) {
                ent.lifetime += dt;
            }
            else {
                ent.lifetime += dt * ent.data[1] / 255.0f;
                if(ent.data[1]-4 >= 0) {
                    ent.data[1]-=4;
                }
            }
            if(ent.damage_flash != 0) {
                ent.damage_flash--;
            }
            if(ent.type == 2) {
                Vector3 target = Vector3Zero();

                #ifndef SERVER
                    target = player_position;
                #endif

                Vector3 direction = Vector3Normalize(Vector3Subtract(target, ent.position));
                float degYaw = -atan2f(direction.z, direction.x) * RAD2DEG + 90;
                if(GetRandomValue(0, 2000) == 0) {
                    MS_PlaySound(nekit_idle, GetRandomValue(75,125)/100.0f, 1.0f, ent.position);
                }
                ent.velocity = Vector3Add(ent.velocity, (Vector3){direction.x * 20.0f, 0, direction.z * 20.0f}*dt);
                if(GetBlock(world, (int)ent.position.x, (int)ent.position.y-1, (int)ent.position.z) && GetRandomValue(0, 100) == 0) {
                    ent.velocity.y += 10.0f;
                }
                UpdateEntityPhysics(world, ent, dt);
                if(Vector3Distance(target, ent.position) < 1.5f && ent.data[2] > 80) {
                    player_velocity += (Vector3){direction.x, 0, direction.z} * 30.0f;
                    player_velocity += {0, 2, 0};
                    ent.data[2] = 0;
                    TakeDamage(10.0f);
                }
                if(ent.health < 0.1f) {
                    ent.free = true;
                }
                if(ent.data[2] < 90)
                    ent.data[2]++;
                ent.yaw = (unsigned short)(degYaw/360.0f*65535);
            }
            /*if(ent.type == 3) {
                Vector3 velocityTgt = Vector3Clamp(Vector3Add(ent.velocity, {0, -dt * 20.0f, 0}), {-30.0f, -30.0f, -30.0f}, {30.0f, 30.0f, 30.0f});

                Vector3 tgt = Vector3Add(ent.position, Vector3Scale(velocityTgt, dt));
                Vector3 final = ent.position;
                if(!GetBlock(world, (int)tgt.x, (int)final.y, (int)final.z)) {
                    final.x = tgt.x;
                }
                if(!GetBlock(world, (int)final.x, (int)tgt.y, (int)final.z)) {
                    final.y = tgt.y;
                }
                else {
                    velocityTgt.y = -velocityTgt.y / 2.0f;
                    velocityTgt = Vector3Scale(velocityTgt, 0.9f);
                }
                if(!GetBlock(world, (int)final.x, (int)final.y, (int)tgt.z)) {
                    final.z = tgt.z;
                }
                Entity* entp = &ent;
                entp->yaw = 0;
                entp->position = final;
                entp->velocity = velocityTgt;
                if(ent.lifetime > 5.0f) {
                    int radius = 3;
                    for (int x = -radius; x < radius; x++)
                    {
                        for (int y = -radius; y < radius; y++)
                        {
                            for (int z = -radius; z < radius; z++)
                            {
                                if(Vector3Distance({final.x + x, final.y + y, final.z + z}, final) <= radius) {
                                    if(!CheckOOBWorld(final.x + x, final.y + y, final.z + z)) {
                                        #ifndef SERVER
                                            SetBlockNetwork(world, final.x + x, final.y + y, final.z + z, 0);
                                        #endif
                                    }
                                }
                            }
                        }
                    }
                    entp->free = true;
                }
                idx++;
            }*/
            if(ent.type == 4) {
                Vector3 velocityTgt = Vector3Clamp(Vector3Add(ent.velocity, {0, -dt * 20.0f, 0}), {-30.0f, -30.0f, -30.0f}, {30.0f, 30.0f, 30.0f});

                Vector3 tgt = Vector3Add(ent.position, Vector3Scale(velocityTgt, dt));
                Vector3 final = ent.position;
                bool hit = false;
                if(!GetBlock(world, (int)tgt.x, (int)final.y, (int)final.z)) {
                    final.x = tgt.x;
                }
                else {
                    hit = true;
                }
                if(!GetBlock(world, (int)final.x, (int)tgt.y, (int)final.z)) {
                    final.y = tgt.y;
                }
                else {
                    hit = true;
                }
                if(!GetBlock(world, (int)final.x, (int)final.y, (int)tgt.z)) {
                    final.z = tgt.z;
                }
                else {
                    hit = true;
                }
                Entity* entp = &ent;
                if(hit) {
                    SetBlockNetwork(world, (int)ent.position.x, (int)ent.position.y, (int)ent.position.z, ent.data[1]);
                    entp->free = true;
                }
                entp->yaw = 0;
                entp->position = final;
                entp->velocity = velocityTgt;
                idx++;
            }
        }
        auto match = [&](const Entity ent){
            return ent.free;
        };
        auto it = std::find_if(entities.begin(), entities.end(), match);
        if (it != entities.end()) {
            entities.erase(it);
        }
    }
}
float globalCounter = 0;
#ifndef SERVER
void DrawEntities(float dt) {
    globalCounter += dt;
    for(Entity& ent : entities) {
        ent.smoothing += dt;
        if(ent.smoothing > TPS_DIV) {
            ent.smoothing = 0;
        }
        Vector3 pos_lerped = Vector3Lerp(ent.position, Vector3Add(ent.position, ent.velocity*TPS_DIV), ent.smoothing * TPS);
        if(ent.type == 0 || ent.type == 2) {
            Color color = ColorLerp(WHITE, RED, ent.damage_flash / 50.0f);
            Vector3 posLerp = Vector3Add(pos_lerped, {-0.5f, -0.1f, -0.5f});

            float time = (ent.lifetime + ent.smoothing) * 4.0f;
            float armWave0 = sinf(time * PI);
            float armWave1 = -abs(cosf(time * PI));
            float headWave = cosf(time * PI);
            posLerp = Vector3Add(posLerp, {0, abs(sinf(time * PI)) * 0.1f, 0});
            Vector3 headPivot = Vector3Add(posLerp, (Vector3){0, 16*PIXUNIT, 0});
            float yaw = ent.yaw / 65535.0f * 360.0f;

            rlColor4f(color.r, color.g, color.b, color.a);
            rlPushMatrix();
            rlTranslatef(posLerp.x, posLerp.y, posLerp.z);    // move to model origin
            rlRotatef(yaw, 0, 1, 0);                        // rotate around Y
            rlTranslatef(-posLerp.x, -posLerp.y, -posLerp.z);  // move back (optional depending on your pivots)

            // 

            rlPushMatrix();
            rlTranslatef(headPivot.x, headPivot.y, headPivot.z);
            rlRotatef(25.0f * armWave0, 1, 0, 0);
            rlRotatef(25.0f * headWave, 0, 0, 1);
            // Head
            DrawCubeTexture(playerTex, {0, 4*PIXUNIT, 0}, 8*PIXUNIT, 8*PIXUNIT, 8*PIXUNIT, WHITE, 8, 8, 8, 8);
            rlPopMatrix();

            Vector3 leftPivot = Vector3Add(posLerp, (Vector3){-6*PIXUNIT, 1.25f - 5*PIXUNIT, 0});
            Vector3 rightPivot = Vector3Add(posLerp, (Vector3){6*PIXUNIT, 1.25f - 5*PIXUNIT, 0});
            
            rlPushMatrix();
            rlTranslatef(leftPivot.x, leftPivot.y, leftPivot.z);
            rlRotatef(45.0f * armWave0, 1, 0, 0);
            rlRotatef(45.0f * armWave1, 0, 0, 1);
            DrawCubeTexture(playerTex, (Vector3){0, -6*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 44,20,4,4);
            rlPopMatrix();

            rlPushMatrix();
            rlTranslatef(rightPivot.x, rightPivot.y, rightPivot.z);
            rlRotatef(-45.0f * armWave0, 1, 0, 0);
            rlRotatef(-45.0f * armWave1, 0, 0, 1);
            DrawCubeTexture(playerTex, (Vector3){0, -6*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 44,20,4,4);
            rlPopMatrix();

            Vector3 body = Vector3Add(posLerp, {0, 1.25f - 10*PIXUNIT, 0});
            rlPushMatrix();
            rlTranslatef(body.x, body.y, body.z);
            DrawCubeTexture(playerTex, Vector3Zero(), 8*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 20, 20, 8, 4);
            rlPopMatrix();

            leftPivot = Vector3Add(posLerp, (Vector3){-2*PIXUNIT, 1.25f - 14*PIXUNIT, 0});
            rightPivot = Vector3Add(posLerp, (Vector3){2*PIXUNIT, 1.25f - 14*PIXUNIT, 0});
            
            rlPushMatrix();
            rlTranslatef(leftPivot.x, leftPivot.y, leftPivot.z);
            rlRotatef(-45.0f * armWave0, 1, 0, 0);
            DrawCubeTexture(playerTex, {0*PIXUNIT, -8*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 4, 20, 4, 4);
            rlPopMatrix();

            rlPushMatrix();
            rlTranslatef(rightPivot.x, rightPivot.y, rightPivot.z);
            rlRotatef(45.0f * armWave0, 1, 0, 0);
            DrawCubeTexture(playerTex, {0*PIXUNIT, -8*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 4, 20, 4, 4);
            rlPopMatrix();

            rlPopMatrix();
        }
        if(ent.type == 1) {
            Color color = WHITE;
            Vector3 posLerp = Vector3Add(ent.position, {-0.5f, -0.1f, -0.5f});

            float time = ent.lifetime * 4.0f;
            float armWave0 = sinf(time * PI);
            float armWave1 = -abs(cosf(time * PI));
            float headWave = cosf(time * PI);
            posLerp = Vector3Add(posLerp, {0, abs(sinf(time * PI)) * 0.1f, 0});
            Vector3 headPivot = Vector3Add(posLerp, (Vector3){0, 16*PIXUNIT, 0});
            float yaw = -ent.yaw / 65535.0f * 360.0f - 90;

            rlPushMatrix();
                rlTranslatef(posLerp.x, posLerp.y, posLerp.z);
                rlRotatef(yaw, 0, 1, 0);
                rlTranslatef(-posLerp.x, -posLerp.y, -posLerp.z);

                // 

                rlPushMatrix();
                    rlTranslatef(headPivot.x, headPivot.y, headPivot.z);
                    rlRotatef(25.0f * armWave0, 1, 0, 0);
                    rlRotatef(25.0f * headWave, 0, 0, 1);
                    // Head
                    DrawCubeTexture(playerTex, {0, 4*PIXUNIT, 0}, 8*PIXUNIT, 8*PIXUNIT, 8*PIXUNIT, WHITE, 8, 8, 8, 8);
                rlPopMatrix();

                Vector3 leftPivot = Vector3Add(posLerp, (Vector3){-6*PIXUNIT, 1.25f - 5*PIXUNIT, 0});
                Vector3 rightPivot = Vector3Add(posLerp, (Vector3){6*PIXUNIT, 1.25f - 5*PIXUNIT, 0});
                
                rlPushMatrix();
                    rlTranslatef(leftPivot.x, leftPivot.y, leftPivot.z);
                    rlRotatef(45.0f * armWave0, 1, 0, 0);
                    rlRotatef(45.0f * armWave1, 0, 0, 1);
                    DrawCubeTexture(playerTex, (Vector3){0, -6*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 44,20,4,4);
                rlPopMatrix();

                rlPushMatrix();
                    rlTranslatef(rightPivot.x, rightPivot.y, rightPivot.z);
                    rlRotatef(-45.0f * armWave0, 1, 0, 0);
                    rlRotatef(-45.0f * armWave1, 0, 0, 1);
                    DrawCubeTexture(playerTex, (Vector3){0, -6*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 44,20,4,4);
                rlPopMatrix();

                Vector3 body = Vector3Add(posLerp, {0, 1.25f - 10*PIXUNIT, 0});

                rlPushMatrix();
                    rlTranslatef(body.x, body.y, body.z);
                    DrawCubeTexture(playerTex, Vector3Zero(), 8*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 20, 20, 8, 4);
                rlPopMatrix();

                leftPivot = Vector3Add(posLerp, (Vector3){-2*PIXUNIT, 1.25f - 14*PIXUNIT, 0});
                rightPivot = Vector3Add(posLerp, (Vector3){2*PIXUNIT, 1.25f - 14*PIXUNIT, 0});
                
                rlPushMatrix();
                    rlTranslatef(leftPivot.x, leftPivot.y, leftPivot.z);
                    rlRotatef(-45.0f * armWave0, 1, 0, 0);
                    DrawCubeTexture(playerTex, {0*PIXUNIT, -8*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 4, 20, 4, 4);
                rlPopMatrix();

                rlPushMatrix();
                    rlTranslatef(rightPivot.x, rightPivot.y, rightPivot.z);
                    rlRotatef(45.0f * armWave0, 1, 0, 0);
                    DrawCubeTexture(playerTex, {0*PIXUNIT, -8*PIXUNIT, 0}, 4*PIXUNIT, 12*PIXUNIT, 4*PIXUNIT, WHITE, 4, 20, 4, 4);
                rlPopMatrix();

            rlPopMatrix();

            Vector3 dir = Vector3Subtract(camera.position, ent.position);
            
            rlPushMatrix();
                rlTranslatef(ent.position.x, ent.position.y + 1.5f, ent.position.z);
                rlRotatef(atan2f(dir.x, dir.z) * RAD2DEG, 0.0f, 1.0f, 0.0f);
                rlRotatef(90.0f, 1.0f, 0.0f, 0.0f);
                char name[12];
                sprintf(name, "Player %d", (int)ent.data[0]);
                DrawText3D(fnt, name, {-MeasureTextEx(fnt, name, PIXUNIT * 4, 0).x * 1.5f, 0, 0}, PIXUNIT * 4, PIXUNIT, 1, true, WHITE);
            rlPopMatrix();
        }
        if(ent.type == 3) {
            DrawCube(pos_lerped, 1, 1, 1, RED);
        }
        if(ent.type == 4) {
            DrawCubeBlock(terrain, pos_lerped, 1,1,1, BLOCKS[ent.data[1]]);
        }
    }
}
#endif