class NearNothingFound(NearException):
    pass class CantFindWhereToPut(ActionException):
    pass def get_brain_part(context, object_id, brain_part_name):
    object_class = context.metas.value.get(INSTANCE_CLASS, object_id)
    object_brain_schema = context.metas.value.get(BRAIN_SCHEMA, object_class)
    return object_brain_schema[brain_part_name] class AntMoveBrainPart(MoveBrainPart):

    def __init__(self, host_brain):
        super().__init__(host_brain)
        self._exploration_vector = (0, 0)

    def _set_exploration_vector(self, new_vector):
        self._exploration_vector = new_vector
        self._context.metas.value.set(EXPLORATION_VECTOR,
                                      self._host_brain.get_host().get_id(),
                                      new_vector)

    @classmethod
    def get_direction(cls, context, object_id):
        move_mode = context.metas.value.get(MODE, object_id)
        if move_mode == MODE_GOHOME:
            try:
                return cls._get_direction_with_exploration_vector(context, object_id)
            except ExplorationAlreadyResolved:
                raise MovementModeExpired(mode=MODE_SEARCH_AROUND)
        else:
            try:
                return cls._get_direction_with_molecules(context, object_id)
            except NoMolecule:
                return super().get_direction(context, object_id)

        return super().get_direction(context, object_id)

    @classmethod
    def _get_direction_with_molecules(cls, context, object_id):
        object_point = context.metas.value.get(POSITION, object_id)
        molecule_type = context.metas.value.get(MOLECULE_SEARCHING, object_id)
        molecule_way = context.metas.value.get(MOLECULE_SEARCHING_WAY, object_id)
        try:
            direction = cls._get_molecule_direction_for_point(context, object_point, molecule_type, molecule_way)
        except NoMolecule:

            movement_mode = context.metas.value.get(MODE, object_id)
            if movement_mode == MODE_GO_OUTSIDE:
                raise
            if movement_mode == MODE_EXPLO:
                raise

            try:
                direction = cls._get_direction_of_molecule(context, object_point, molecule_type)
            except NoMolecule:
                raise
        return direction

    @staticmethod
    def _get_molecule_direction_for_point(context, point, molecule_type, molecule_way):
        return DirectionMolecule.get_direction_for_point(context, point, molecule_type, molecule_way)

    @staticmethod
    def _get_direction_of_molecule(context, point, molecule_type):
        search_molecule_in_points = context.get_around_points_of_point(point)
        try:
            best_molecule_direction = DirectionMolecule.get_best_molecule_direction_in(context,
                                                                                       point,
                                                                                       search_molecule_in_points,
                                                                                       molecule_type)
            return best_molecule_direction
        except NoMolecule as err:
            raise err

    @classmethod
    def _get_direction_with_exploration_vector(cls, context, object_id):
        exploration_vector = context.metas.value.get(EXPLORATION_VECTOR, object_id)
        if exploration_vector == (0, 0):
            raise ExplorationAlreadyResolved()

        ant_star = cls._get_by_pass_brain(context, object_id)
        ant_star.advance()
        return ant_star.get_host().get_moved_to_direction()

    @classmethod
    def _get_by_pass_brain(cls, context, object_id):
        # We use an adaptation of AntStar
        exploration_vector = context.metas.value.get(EXPLORATION_VECTOR, object_id)
        home_vector = (-exploration_vector[0], -exploration_vector[1])
        ant_host = Host(context, object_id)
        return ByPass(ant_host, home_vector, context, object_id)

    def done(self):
        super().done()
        self._appose_molecule()
        self._check_context()
        self._apply_context()

    def _appose_molecule(self):
        if self._host.get_movement_molecule_gland().is_enabled():
            self._host.get_movement_molecule_gland().appose()

    def _check_context(self):
        """

        If was in exploration, and just found home smell;
            -> home mode (then, in this mode: no update vector, put food if food, etc)
        If was in home, and just loose home smell:
            -> exploration mode

        :param obj:
        :param context:
        :return:
        """
        movement_mode = self._host_brain.get_movement_mode()

        # TODO: Algo de depl. dans les brain part (ex. put)
        put_fail_count = self._context.metas.value.get(PUT_FAIL_COUNT, self._host.get_id(), allow_empty=True,
                                                       empty_value=0)
        put_fail_count_max = Core.get_configuration_manager().get('ant.max_put_fail_count', 20)

        if movement_mode == MODE_GOHOME and self._on_home_smell(self._context, self._host.get_id()):
            self._arrived_at_home()

        if movement_mode == MODE_SEARCH_AROUND and self._on_home_smell(self._context, self._host.get_id()):
            self._arrived_at_home()

        elif movement_mode == MODE_HOME and not self._on_home_smell(self._context, self._host.get_id()):
            self._start_new_exploration()

        elif movement_mode == MODE_EXPLO and self._on_home_smell(self._context, self._host.get_id()):
            self._init_exploration_vector()

        elif movement_mode == MODE_HOME and put_fail_count >= put_fail_count_max:
            self._host.get_brain().switch_to_mode(MODE_GO_OUTSIDE)
            self._host._add_col(COL_PUT_OUTSIDE)  #  TODO: protege pas necessaire ?

        elif movement_mode == MODE_GO_OUTSIDE and not self._on_home_smell(self._context, self._host.get_id()):
            self._start_new_exploration()

        # TODO: sitch explo si rien a faire (rien a poser par exemple) et HOME

    @classmethod
    def _on_home_smell(cls, context, object_id):
        current_position = context.metas.value.get(POSITION, object_id)
        flavour = context.molecules().get_flavour(current_position)
        molecules = flavour.get_molecules_types(MOLECULES_DIRECTION)

        if not molecules:
            return False

        brain_class = get_brain_class(context, object_id)
        for smell_type in brain_class.get_home_smells():
            if smell_type in molecules:
                return True

        return False

    def _update_exploration_vector(self):
        just_move_vector = directions_modifiers[self._host_brain.get_host().get_previous_direction()]
        self._set_exploration_vector((self._exploration_vector[0] + just_move_vector[1],
                                      self._exploration_vector[1] + just_move_vector[2]))

    def _apply_context(self):
        movement_mode = self._host_brain.get_movement_mode()
        if movement_mode == MODE_EXPLO or movement_mode == MODE_GOHOME:
            self._update_exploration_vector()

    def _start_new_exploration(self):
        self._init_exploration_vector()
        self._host_brain.switch_to_mode(MODE_EXPLO)

    def _init_exploration_vector(self):
        # On vient de rentrer dans le monde exterieur, le vecteur de départ pointe vers la case précedente
        # qui est une case dans la forteresse.
        init_exploration_vector = get_position_with_direction_decal(self.get_host().get_previous_direction())
        self._set_exploration_vector(init_exploration_vector)

    def _arrived_at_home(self):
        self._host_brain.switch_to_mode(MODE_HOME)
        ant_star = self._get_by_pass_brain(self._context, self._host.get_id())
        ant_star.erase() class NearEvent(Event):

    _mechanism = AroundMechanism
    _near_name = None
    _near_map = lambda self, near_object_id, context: False

    # TODO: parameters en entre/sortie c pas bon ca
    def map(self, context, parameters, stop_at_first=False, filter=lambda near_object_id, context: True):
        parameters[self._near_name] = []
        for near_object_id in parameters['objects_ids_near']:
            if self._near_map(near_object_id, context) and filter(near_object_id, context):
                parameters[self._near_name].append(near_object_id)
                if stop_at_first:
                    return
        if not parameters[self._near_name]:
            raise NearNothingFound() CANT_PUT_STILL = IncrementedNamedInt.get('intelligine.cantput.still') COL_TRANSPORTER_CARRYING = IncrementedNamedInt.get('intelligine.col.transporter_carrying') TRANSPORTABLE = IncrementedNamedInt.get('intelligine.transportable') BRAIN_SCHEMA = IncrementedNamedInt.get('intelligine.brain_schema') BRAIN_PART_PUT = IncrementedNamedInt.get('intelligine.brain.part.put')
from intelligine.core.exceptions import NearNothingFound, CantFindWhereToPut
from intelligine.shorcut.brain import get_brain_part
from intelligine.simulation.object.brain.part.move.AntMoveBrainPart import AntMoveBrainPart
from intelligine.synergy.event.src.NearEvent import NearEvent
from synergine.core.exceptions import NotConcernedEvent
from intelligine.cst import CANT_PUT_STILL, COL_TRANSPORTER_CARRYING, TRANSPORTABLE, BRAIN_SCHEMA, BRAIN_PART_PUT
from synergine_xyz.mechanism.AroundMechanism import AroundMechanism


class PutableEvent(NearEvent):
    """
    TODO: Refactorise with TakableEvent
    """

    PARAM_PUT = 'put'
    PARAM_PUT_TO = 'put_to'
    PARAM_HOME_FAIL = 'home_fail'
    _mechanism = AroundMechanism
    _concern = COL_TRANSPORTER_CARRYING
    _near_name = 'objects_ids_putable'
    _near_map = lambda self, near_object_id, context: context.metas.states.have(near_object_id, TRANSPORTABLE)

    def _prepare(self, object_id, context, parameters={}):
        if not self._can_put(object_id, context):
            raise NotConcernedEvent()

        try:
            self.map(context, parameters, stop_at_first=False)
        except NearNothingFound:
            raise NotConcernedEvent()

        brain_part = get_brain_part(context, object_id, BRAIN_PART_PUT)
        parameters[self.PARAM_PUT] = []
        parameters[self.PARAM_PUT_TO] = []
        parameters[self.PARAM_HOME_FAIL] = False

        for object_near_id in parameters[self._near_name]:
            if brain_part.can_put(context, object_id, object_near_id):
                try:
                    put_position = brain_part.get_put_position(context, object_id, object_near_id)
                    parameters[self.PARAM_PUT].append(object_near_id)
                    parameters[self.PARAM_PUT_TO].append(put_position)
                    return parameters  # Si a terme on veut tous les calculer a l'avance, ne pas retourner ici

                except CantFindWhereToPut: