def get_brain_class(context, object_id):
    object_class = context.metas.value.get(INSTANCE_CLASS, object_id)
    return context.metas.value.get(BRAIN, object_class) class ByPass(StickWallAntBrain):

    def __init__(self, host, home_vector, context, object_id):
        """

        Note: We broke Liskov principle here.

        :param host:
        :param home_vector:
        :param context:
        :param object_id:
        :return:
        """
        super().__init__(host, home_vector)
        self._context = context
        self._object_id = object_id
        self._memory_since_blocked = context.metas.value.get(MOVE_BYBASS_MEMORY,
                                                             object_id,
                                                             allow_empty=True,
                                                             empty_value=[])
        self._by_passing = context.metas.value.get(MOVE_BYBASS,
                                                   object_id,
                                                   allow_empty=True,
                                                   empty_value=False)
        self._distance_when_blocked = context.metas.value.get(MOVE_BYBASS_DISTANCE,
                                                              object_id,
                                                              allow_empty=True,
                                                              empty_value=None)
        self._current_wall_position = context.metas.value.get(MOVE_BYBASS_WALL,
                                                              object_id,
                                                              allow_empty=True,
                                                              empty_value=None)
        self._previous_wall_position = context.metas.value.get(MOVE_BYBASS_PREV_WALL,
                                                               object_id,
                                                               allow_empty=True,
                                                               empty_value=None)

    def _set_home_vector(self, home_vector):
        super()._set_home_vector(home_vector)
        self._context.metas.value.set(EXPLORATION_VECTOR, self._object_id, home_vector)

    def _set_memory_since_blocked(self, memory_since_blocked):
        super()._set_memory_since_blocked(memory_since_blocked)
        self._context.metas.value.set(MOVE_BYBASS_MEMORY, self._object_id, memory_since_blocked)

    def _set_by_passing(self, by_passing):
        super()._set_by_passing(by_passing)
        self._context.metas.value.set(MOVE_BYBASS, self._object_id, by_passing)

    def _set_distance_when_blocked(self, distance):
        super()._set_distance_when_blocked(distance)
        self._context.metas.value.set(MOVE_BYBASS_DISTANCE, self._object_id, distance)

    def _set_current_wall_position(self, position):
        super()._set_current_wall_position(position)
        self._context.metas.value.set(MOVE_BYBASS_WALL, self._object_id, position)

    def _set_previous_wall_position(self, position):
        super()._set_previous_wall_position(position)
        self._context.metas.value.set(MOVE_BYBASS_PREV_WALL, self._object_id, position) class Host:

    def __init__(self, context, object_id):
        self._context = context
        self._object_id = object_id
        self._feeler = HostFeeler(context, object_id)
        self._moved_to_direction = None
        self._position_3d = self._context.metas.value.get(POSITION, self._object_id)

    def get_position(self):
        return self._position_3d[1], self._position_3d[2]

    def get_feeler(self):
        return self._feeler

    def move_to(self, direction):
        self._moved_to_direction = direction
        self._position_3d = get_position_with_direction_decal(direction, self._position_3d)

    def get_moved_to_direction(self):
        return self._moved_to_direction class MoveBrainPart(BrainPart):

    @classmethod
    def get_direction(cls, context, object_id):
        try:
            return cls._get_slighty_direction(context, object_id)
        except DirectionException:
            return cls._get_random_direction(context, object_id)

    @classmethod
    def _get_slighty_direction(cls, context, object_id):
        # TODO: A terme le calcul de la direction devra prendre en compte les directions bloques
        blocked_since = context.metas.value.get(BLOCKED_SINCE, object_id, allow_empty=True, empty_value=0)
        if blocked_since <= 3:  #TODO: config
            try:
                previous_direction = context.metas.value.get(PREVIOUS_DIRECTION, object_id)
            except KeyError:
                # No previous direction
                return cls._get_random_direction(context, object_id)
            # TODO: Faut mettre ca en plus propre (proba d'aller tou droit, config, etc)
            if randrange(100) < 75:  # 75% de change d'aller tout droit
                # Dans le futur: les fourmis vont moins tout droit quand elle se croient et se touche
                return previous_direction

            directions_list = directions_slighty[previous_direction]
            # TODO: TMP tant que 1 niveau (z)
            directions_list = [direction for direction in directions_list if 9 < direction < 19]
            return choice(directions_list)

        raise DirectionException()

    @classmethod
    def _get_random_direction(cls, context, object_id):
        return randint(directions_same_level[0], directions_same_level[-1]) NORTH = 11
NORTH_EST = 12
EST = 15
SOUTH_EST = 18
SOUTH = 17
SOUTH_WEST = 16
WEST = 13
NORTH_WEST = 10
CENTER = 14
def get_direction_for_degrees(degrees):
def get_direction_opposite(direction):
def get_position_with_direction_decal(direction=CENTER, point=(0, 0, 0)): class NoMolecule(MoleculeException):
    pass class ExplorationAlreadyResolved(DirectionException):
    pass class MovementModeExpired(MovementException):

    def __init__(self, *args, mode, **kwargs):
        super().__init__(*args, **kwargs)
        self._switch_to_mode = mode

    def get_switch_to_mode(self):
        return self._switch_to_mode MOLECULE_SEARCHING = IncrementedNamedInt.get('intelligine.molecule_searching') MODE_EXPLO = IncrementedNamedInt.get('intelligine.basebug.move.explo') MODE_HOME = IncrementedNamedInt.get('intelligine.basebug.move.home') MODE = IncrementedNamedInt.get('intelligine.basebug.move') MODE_GOHOME = IncrementedNamedInt.get('intelligine.basebug.move.gohome') EXPLORATION_VECTOR = IncrementedNamedInt.get('intelligine.exploration_vector') MOLECULES_DIRECTION = IncrementedNamedInt.get('intelligine.mol.direction') PUT_FAIL_COUNT = IncrementedNamedInt.get('intelligine.put.fail.count') MODE_GO_OUTSIDE = IncrementedNamedInt.get('intelligine.basebug.move.go_outside') MOLECULE_SEARCHING_WAY = IncrementedNamedInt.get('intelligine.molecule_searching_way') COL_PUT_OUTSIDE = IncrementedNamedInt.get('intelligine.col.put_outside') MODE_SEARCH_AROUND = IncrementedNamedInt.get('intelligine.basebug.move.search_around') class DirectionMolecule():

    WAY_UP = 'u'
    WAY_DOWN = 'd'

    _positions_key = None

    @classmethod
    def appose(cls, context, point, molecule):
        context.molecules().increment_with_molecule(point, molecule, context.get_cycle())
        context.metas.list.add(MOLECULES, MOLECULES, point, assert_not_in=False)

    @classmethod
    def get_direction_for_point(cls, context, point, molecule_type, molecule_way=WAY_UP):
        flavour = context.molecules().get_flavour(point)
        molecule = flavour.get_molecule(category=MOLECULES_DIRECTION, type=molecule_type)

        distance = molecule.get_distance()
        around_molecule_filter = lambda around_molecule: around_molecule.get_distance() <= distance #  TODO <= ?
        if molecule_way == cls.WAY_DOWN:
            around_molecule_filter = lambda around_molecule: around_molecule.get_distance() >= distance
        around_molecules_points = cls._get_around_molecules(context, point, molecule_type,
                                                            molecule_filter=around_molecule_filter)

        if not around_molecules_points \
           or (len(around_molecules_points) == 1 and around_molecules_points[0][0] == point):
            raise NoMolecule()

        shuffle(around_molecules_points)
        around_molecules_sorted = sorted(around_molecules_points, key=lambda x: x[1].get_intensity(), reverse=True)
        max_intensity = around_molecules_sorted[0][1].get_intensity()

        around_molecules_max = []
        for around_molecule_sorted in around_molecules_sorted:
            if around_molecule_sorted[1].get_intensity() == max_intensity:
                around_molecules_max.append(around_molecule_sorted)

        reverse = False
        if molecule_way == cls.WAY_DOWN:
            reverse = True
        around_molecules_sorted_by_distance = sorted(around_molecules_max,
                                                      key=lambda x: x[1].get_distance(),
                                                      reverse=reverse)

        all_same_distance = True
        for mol in around_molecules_sorted_by_distance:
            mol_mol = mol[1]
            if mol_mol.get_distance() != distance:
                all_same_distance = False
                break

        if all_same_distance:
            raise NoMolecule()

        go_to_point = around_molecules_sorted_by_distance[0][0]

        direction_degrees = get_degree_from_north(point, go_to_point)
        direction = get_direction_for_degrees(direction_degrees)

        return direction

    @classmethod
    def _get_around_molecules(cls, context, reference_point, molecule_type,
                              molecule_filter=lambda around_molecule: True):
        around_points = context.get_around_points_of_point(reference_point)
        around_molecules_points = []
        for around_point in around_points:
            flavour = context.molecules().get_flavour(around_point)
            try:
                around_molecule = flavour.get_molecule(category=MOLECULES_DIRECTION, type=molecule_type)
                if molecule_filter(around_molecule):
                    around_molecules_points.append((around_point, around_molecule))
            except NoMolecule:
                pass  # No molecule, ok continue to sniff around

        return around_molecules_points

    @classmethod
    def get_best_molecule_direction_in(cls, context, reference_point, points, molecule_type):
        around_molecules_points = []
        for around_point in points:
            flavour = context.molecules().get_flavour(around_point)
            try:
                around_molecule = flavour.get_molecule(category=MOLECULES_DIRECTION, type=molecule_type)
                around_molecules_points.append((around_point, around_molecule))
            except NoMolecule:
                pass  # Ok, no molecule, continue to sniff around

        if not around_molecules_points \
           or (len(around_molecules_points) == 1 and around_molecules_points[0][0] == reference_point):
            raise NoMolecule()

        shuffle(around_molecules_points)
        around_molecules_sorted = sorted(around_molecules_points, key=lambda x: x[1].get_intensity(), reverse=True)
        go_to_point = around_molecules_sorted[0][0]

        direction_degrees = get_degree_from_north(reference_point, go_to_point)
        direction = get_direction_for_degrees(direction_degrees)

        return direction
from synergine.core.Core import Core
from intelligine.shorcut.brain import get_brain_class
from intelligine.simulation.object.brain.part.move.AntStar.ByPass import ByPass
from intelligine.simulation.object.brain.part.move.AntStar.Host import Host
from intelligine.simulation.object.brain.part.move.MoveBrainPart import MoveBrainPart
from intelligine.synergy.event.move.direction import directions_modifiers, get_position_with_direction_decal
from synergine_xyz.cst import POSITION
from intelligine.core.exceptions import NoMolecule, ExplorationAlreadyResolved, MovementModeExpired
from intelligine.cst import MOLECULE_SEARCHING, MODE_EXPLO, MODE_HOME, MODE, MODE_GOHOME, \
    EXPLORATION_VECTOR, MOLECULES_DIRECTION, PUT_FAIL_COUNT, MODE_GO_OUTSIDE, MOLECULE_SEARCHING_WAY, COL_PUT_OUTSIDE, \
    MODE_SEARCH_AROUND
from intelligine.simulation.molecule.DirectionMolecule import DirectionMolecule

    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()):