from olives_gamestate import GameState
from olives_object import MessageType, PositionStatus
from olives_objectcontrollable import ObjectControllable
from olives_object_type import ObjectType
from .olives_abstractcontrollableobject import AbstractControllableObject


class Human(AbstractControllableObject):
    is_initialised = False
    hunger = 100
    hygiene = 100
    energy = 100
    hp = 100
    # temperature which will be compared to the surrounding tile
    base_temperature = 20.0
    # effective temperature that may have been modified (e.g. being covered up)
    effective_temperature = 20.0
    min_allowed_temperature = 0
    max_allowed_temperature = 0

    def __init__(self, object_type_name: str, object_id: int, object_type: ObjectType):
        super().__init__(object_type_name, object_id, object_type)

        self.add_effect("moving_hunger", 200)
        self.add_effect("moving_hygiene", 500)
        self.add_effect("moving_energy", 600)
        self.add_effect("passive_hunger", 600)
        self.add_effect("passive_hygiene", 700)
        self.add_effect("passive_energy", 850)
        self.add_effect("hunger_damage", 200)
        self.add_effect("hygiene_damage", 800)
        self.add_effect("energy_damage", 400)
        self.add_effect("temperature_damage", 400)
        self.add_effect("auto_healing", 1000)

    def on_tick(self, obj: ObjectControllable, elapsed_ticks: int, game_state: GameState, temperature: int):
        # synchronise the data from the C++ object so that we
        # do not have to call these multiple times for a single tick
        self.hunger = obj.get_hunger()
        self.hp = obj.get_hit_points()
        self.hygiene = obj.get_hygiene()
        self.energy = obj.get_energy()
        old_position_status = self.position_status
        self.position_status = obj.get_position_status()

        # check if the object is still alive before doing anything else
        if self.hp < 1:
            # this object is dead.
            self.add_job_message(obj, MessageType.CHANGE_OBJECT, 0, "gravestone")

        self.calculate_temperature(obj, temperature, elapsed_ticks, old_position_status != self.position_status)
        self.handle_health_modifiers(obj, elapsed_ticks)
        self.handle_hunger(obj, elapsed_ticks)
        self.handle_hygiene(obj, elapsed_ticks)
        self.handle_energy(obj, elapsed_ticks)

    def calculate_temperature(self, obj: ObjectControllable, temperature: int, elapsed_ticks: int, position_status_changed: bool):
        # synchronise the minimum and maximum allowed temperatures once before
        # taking over control from the python side
        if not self.is_initialised:
            self.is_initialised = True
            self.min_allowed_temperature = obj.get_min_temperature()
            self.max_allowed_temperature = obj.get_max_temperature()

        # if the surrounding tile temperature changed apply the change to the object
        # specific one
        if temperature != round(self.base_temperature) or position_status_changed:
            # slightly modify the base temperature, depending on the tile temperature
            diff = 1 if (self.base_temperature - temperature) * -1 > 0 else -1
            diff = diff * 0.002 * elapsed_ticks
            self.base_temperature += diff
            self.effective_temperature = self.base_temperature

            # boost the temperature when being presumably snuggled up in bed
            if self.position_status == PositionStatus.LYING:
                self.effective_temperature += 5

            if temperature != round(self.base_temperature) or position_status_changed:
                obj.set_temperature(round(self.effective_temperature))

    def handle_health_modifiers(self, obj: ObjectControllable, elapsed_ticks: int):
        effects = ['hunger', 'hygiene', 'energy']

        # iterate through the factors that might reduce the object's hit points
        for effect in effects:
            if getattr(self, effect) < 1:
                self.apply_change(effect + '_damage', self.hp, elapsed_ticks, obj.set_hit_points)

        # temperature damage needs to be handled slightly differently
        has_allowed_temperature = True
        if self.effective_temperature < self.min_allowed_temperature or self.effective_temperature > self.max_allowed_temperature:
            self.apply_change('temperature_damage', self.hp, elapsed_ticks, obj.set_hit_points)
            has_allowed_temperature = False

        # only auto heal if all the basic indicators are positive
        if self.hunger > 1 and self.hygiene > 1 and self.energy > 1 and has_allowed_temperature and self.hp < 100:
            self.apply_change('auto_healing', self.hp, elapsed_ticks, obj.set_hit_points, True, True)

    def handle_hunger(self, obj: ObjectControllable, elapsed_ticks: int):
        self.handle_attribute_change(obj, elapsed_ticks, 'hunger', obj.set_hunger, True, True)

    def handle_hygiene(self, obj: ObjectControllable, elapsed_ticks: int):
        self.handle_attribute_change(obj, elapsed_ticks, 'hygiene', obj.set_hygiene, True, True)

    def handle_energy(self, obj: ObjectControllable, elapsed_ticks: int):
        self.handle_attribute_change(obj, elapsed_ticks, 'energy', obj.set_energy, True, True)
