#include "entity_health.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp"

void EntityHealth::_bind_methods() {
#define CLASSNAME EntityHealth
    GDFUNCTION_ARGS(damaged_by, "amount", "source");
    GDFUNCTION_ARGS(healed_by, "amount", "source");
    GDPROPERTY(injury_max, gd::Variant::INT);
    GDPROPERTY(wounds_max, gd::Variant::INT);

    GDSIGNAL("damage",
             gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
             gd::PropertyInfo(gd::Variant::INT, "change"),
             gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
    GDSIGNAL("heal",
             gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
             gd::PropertyInfo(gd::Variant::INT, "change"),
             gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
    GDSIGNAL("health_changed",
             gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
             gd::PropertyInfo(gd::Variant::INT, "change"));
    GDSIGNAL("death", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
    GDSIGNAL("unconscious", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
    GDSIGNAL("revived",
             gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "Unit"),
             gd::PropertyInfo(gd::Variant::INT, "healed"),
             gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
}

void EntityHealth::_enter_tree() {
    this->injury_current = this->injury_max;
    this->wounds_current = this->wounds_max;
}

void EntityHealth::damaged_by(int amount, Unit *source) {
    if(!this->is_dead()) {
        amount = gd::Math::abs(amount);
        this->receive_wounds(amount, source);
        this->receive_injury(amount, source);
        this->emit_signal("damage", this, amount, source);
        this->emit_signal("health_changed", this, -amount);
    }
}

void EntityHealth::healed_by(int amount, Unit *source) {
    if(this->can_be_healed()) {
        amount = gd::Math::abs(amount);
        this->injury_current = gd::Math::min(this->injury_max, this->injury_current + amount);
        this->emit_signal("heal", this, amount, source);
        this->emit_signal("health_changed", this, amount);
    }
}

void EntityHealth::receive_wounds(int incoming_amount, Unit *source) {
    if(this->is_conscious()) {
        this->wounds_current -= incoming_amount;
        if(this->injury_current <= 0)
            this->emit_signal("unconscious", source);
    }
}

void EntityHealth::receive_injury(int incoming_amount, Unit *source) {
    if(!this->is_dead()) {
        this->injury_current -= gd::Math::max(1, int(float(incoming_amount) * this->get_wounds_damage_factor()));
        if(this->injury_current <= 0)
            this->emit_signal("death", source);
    }
}

float EntityHealth::get_wounds_damage_factor() const {
    return float(this->injury_current) / float(this->injury_max);
}

bool EntityHealth::is_conscious() const {
    return !this->is_dead()
        && this->wounds_current > 0;
}

bool EntityHealth::can_be_revived() const {
    return this->wounds_current <= 0
        && this->injury_current > 0;
}

bool EntityHealth::can_be_healed() const {
    return this->injury_current > 0
        && this->wounds_current > 0
        && this->wounds_current < this->wounds_max;
}

bool EntityHealth::is_dead() const {
    return this->injury_current <= 0;
}

void EntityHealth::set_stats(Stats const *stats) {
    this->stats = stats;
}

Stats const *EntityHealth::get_stats() const {
    return this->stats;
}

void EntityHealth::set_injury_max(int max_health) {
    this->injury_max = max_health;
}

int EntityHealth::get_injury_max() const {
    return this->injury_max;
}

int EntityHealth::get_injury_current() const {
    return this->injury_current;
}

void EntityHealth::set_wounds_max(int max_health) {
    this->wounds_max = max_health;
}

int EntityHealth::get_wounds_max() const {
    return this->wounds_max;
}

int EntityHealth::get_wounds_current() const {
    return this->wounds_current;
}
