#include "entity.h"
#include "entity_i.h"
#include <stdlib.h>
#include <furi.h>

#ifdef ENTITY_DEBUG
#define ENTITY_D(...) FURI_LOG_D("Entity", __VA_ARGS__)
#else
#define ENTITY_D(...)
#endif

static int32_t entities_count = 0;

int32_t entities_get_count(void) {
    return entities_count;
}

Entity* entity_alloc(const EntityDescription* description) {
    entities_count++;
    Entity* entity = malloc(sizeof(Entity));
    entity->position = VECTOR_ZERO;
    entity->description = description;
    entity->context = NULL;
    if(description && description->context_size > 0) {
        entity->context = malloc(description->context_size);
    }
    entity->collider = NULL;
    entity->collider_offset = VECTOR_ZERO;
    ENTITY_D("Allocated at %p", entity);
    entity->collider_dirty = false;
    return entity;
}

void entity_collider_add_circle(Entity* entity, float radius) {
    furi_check(entity->collider == NULL, "Collider already added");
    entity->collider = malloc(sizeof(Collider));
    entity->collider->type = ColliderTypeCircle;
    entity->collider->circle.radius = radius;
    entity->collider_dirty = true;
}

void entity_collider_add_rect(Entity* entity, float width, float height) {
    furi_check(entity->collider == NULL, "Collider already added");
    entity->collider = malloc(sizeof(Collider));
    entity->collider->type = ColliderTypeRect;
    entity->collider->rect.half_width = width / 2;
    entity->collider->rect.half_height = height / 2;
    entity->collider_dirty = true;
}

void entity_collider_remove(Entity* entity) {
    furi_check(entity->collider != NULL, "Collider not added");
    free(entity->collider);
    entity->collider = NULL;
    entity->collider_dirty = false;
}

void entity_collider_offset_set(Entity* entity, Vector offset) {
    entity->collider_offset = offset;
    entity->collider_dirty = true;
}

Vector entity_collider_offset_get(Entity* entity) {
    return entity->collider_offset;
}

static Vector entity_collider_position_get(Entity* entity) {
    return (Vector){
        .x = entity->position.x + entity->collider_offset.x,
        .y = entity->position.y + entity->collider_offset.y,
    };
}

void entity_free(Entity* entity) {
    entities_count--;
    ENTITY_D("Freeing at %p", entity);
    if(entity->context) {
        free(entity->context);
    }
    if(entity->collider) {
        free(entity->collider);
    }
    free(entity);
}

const EntityDescription* entity_description_get(Entity* entity) {
    return entity->description;
}

Vector entity_pos_get(Entity* entity) {
    return entity->position;
}

void entity_pos_set(Entity* entity, Vector position) {
    entity->position = position;
    entity->collider_dirty = true;
}

void* entity_context_get(Entity* entity) {
    return entity->context;
}

void entity_call_start(Entity* entity, GameManager* manager) {
    if(entity->description && entity->description->start) {
        entity->description->start(entity, manager, entity->context);
    }
}

void entity_call_stop(Entity* entity, GameManager* manager) {
    if(entity->description && entity->description->stop) {
        entity->description->stop(entity, manager, entity->context);
    }
}

void entity_call_update(Entity* entity, GameManager* manager) {
    if(entity->description && entity->description->update) {
        entity->description->update(entity, manager, entity->context);
    }
}

void entity_call_render(Entity* entity, GameManager* manager, Canvas* canvas) {
    if(entity->description && entity->description->render) {
        entity->description->render(entity, manager, canvas, entity->context);
    }
}

void entity_call_collision(Entity* entity, Entity* other, GameManager* manager) {
    if(entity->description && entity->description->collision) {
        entity->description->collision(entity, other, manager, entity->context);
    }
}

bool entity_collider_circle_circle(Entity* entity, Entity* other) {
    Vector pos1 = entity_collider_position_get(entity);
    Vector pos2 = entity_collider_position_get(other);

    float dx = pos1.x - pos2.x;
    float dy = pos1.y - pos2.y;
    float distance = sqrtf(dx * dx + dy * dy);
    return distance < entity->collider->circle.radius + other->collider->circle.radius;
}

bool entity_collider_rect_rect(Entity* entity, Entity* other) {
    Vector pos1 = entity_collider_position_get(entity);
    Vector pos2 = entity_collider_position_get(other);

    float left1 = pos1.x - entity->collider->rect.half_width;
    float right1 = pos1.x + entity->collider->rect.half_width;
    float top1 = pos1.y - entity->collider->rect.half_height;
    float bottom1 = pos1.y + entity->collider->rect.half_height;

    float left2 = pos2.x - other->collider->rect.half_width;
    float right2 = pos2.x + other->collider->rect.half_width;
    float top2 = pos2.y - other->collider->rect.half_height;
    float bottom2 = pos2.y + other->collider->rect.half_height;

    return left1 < right2 && right1 > left2 && top1 < bottom2 && bottom1 > top2;
}

bool entity_collider_circle_rect(Entity* circle, Entity* rect) {
    Vector pos1 = entity_collider_position_get(circle);
    Vector pos2 = entity_collider_position_get(rect);

    float left = pos2.x - rect->collider->rect.half_width;
    float right = pos2.x + rect->collider->rect.half_width;
    float top = pos2.y - rect->collider->rect.half_height;
    float bottom = pos2.y + rect->collider->rect.half_height;

    float closestX = fmaxf(left, fminf(pos1.x, right));
    float closestY = fmaxf(top, fminf(pos1.y, bottom));

    float dx = pos1.x - closestX;
    float dy = pos1.y - closestY;
    float distance = sqrtf(dx * dx + dy * dy);
    return distance < circle->collider->circle.radius;
}

bool entity_collider_check_collision(Entity* entity, Entity* other) {
    furi_check(entity->collider);
    furi_check(other->collider);

    if(entity->collider->type == ColliderTypeCircle) {
        Entity* circle = entity;
        if(other->collider->type == ColliderTypeCircle) {
            return entity_collider_circle_circle(circle, other);
        } else {
            return entity_collider_circle_rect(circle, other);
        }
    } else {
        Entity* rect = entity;
        if(other->collider->type == ColliderTypeCircle) {
            return entity_collider_circle_rect(other, rect);
        } else {
            return entity_collider_rect_rect(rect, other);
        }
    }
}

bool entity_collider_exists(Entity* entity) {
    return entity->collider != NULL;
}

void entity_send_event(
    Entity* sender,
    Entity* receiver,
    GameManager* manager,
    uint32_t type,
    EntityEventValue value) {
    if(receiver->description && receiver->description->event) {
        EntityEvent event = {
            .type = type,
            .sender = sender,
            .value = value,
        };
        receiver->description->event(receiver, manager, event, receiver->context);
    }
}