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

    _brain_parts = {}

    def __init__(self, context, host):
        self._context = context
        self._host = host
        self._parts = {}
        self._init_parts()

    def _init_parts(self):
        for brain_part_name in self._brain_parts:
            self._parts[brain_part_name] = self._brain_parts[brain_part_name](self)
        self._context.metas.value.set(BRAIN_SCHEMA, self._host.__class__, self._brain_parts)

    def get_part(self, name):
        return self._parts[name]

    def get_context(self):
        return self._context

    def get_host(self):
        return self._host class AttackBrainPart(BrainPart):

    @classmethod
    def can_attack(cls, context, object_id, concerned_object_id):
        # Pour le moment on ne passe ici que si c'est un object ATTACKABLE d'une colonie differente. On attaque
        # toujours. On codera dans ce brain part la decision d'attaquer.
        return True MODE = IncrementedNamedInt.get('intelligine.basebug.move') MODE_EXPLO = IncrementedNamedInt.get('intelligine.basebug.move.explo') MODE_GOHOME = IncrementedNamedInt.get('intelligine.basebug.move.gohome') PHEROMON_DIR_EXPLO = IncrementedNamedInt.get('intelligine.phero.direction.explo') BRAIN_PART_TAKE = IncrementedNamedInt.get('intelligine.brain.part.take') BRAIN_PART_PUT = IncrementedNamedInt.get('intelligine.brain.part.put') MODE_NURSE = IncrementedNamedInt.get('intelligine.basebug.move.nurse') PHEROMON_DIR_NONE = IncrementedNamedInt.get('intelligine.phero.direction.none') BRAIN_PART_ATTACK = IncrementedNamedInt.get('intelligine.brain.part.attack') MODE_HOME = IncrementedNamedInt.get('intelligine.basebug.move.home') SMELL_FOOD = IncrementedNamedInt.get('intelligine.smell.food') SMELL_EGG = IncrementedNamedInt.get('intelligine.smell.egg') MODE_GO_OUTSIDE = IncrementedNamedInt.get('intelligine.basebug.move.go_outside') MOLECULE_SEARCHING_WAY = IncrementedNamedInt.get('intelligine.molecule_searching_way') MODE_SEARCH_AROUND = IncrementedNamedInt.get('intelligine.basebug.move.search_around') MOLECULE_SEARCHING = IncrementedNamedInt.get('intelligine.molecule_searching') BRAIN_PART_MOVE = IncrementedNamedInt.get('intelligine.brain.part.move') class AntPutBrainPart(TransportBrainPart):

    _mode_matches = TransportBrainPart._mode_matches.copy()
    _mode_matches.update({
        MODE_NURSE: [TYPE_NURSERY],
        MODE_HOME: [TYPE_RESOURCE_EATABLE],
        MODE_GOHOME: [],
        MODE_SEARCH_AROUND: []
    })

    _types_matches = {
        TYPE_RESOURCE_EXPLOITABLE: [TYPE_RESOURCE_EATABLE],
        TYPE_NURSERY: [TYPE_NURSERY]
    }

    @classmethod
    def can_put(cls, context, object_id, object_near_id):
        put_fail_count = context.metas.value.get(PUT_FAIL_COUNT, object_id, allow_empty=True, empty_value=0)
        put_fail_count_max = Core.get_configuration_manager().get('ant.max_put_fail_count', 20)
        if put_fail_count >= put_fail_count_max:
            return False

        # Si l'objet à coté fait partie des objets concernés par le mode du porteur
        if cls._match_with_mode(context, object_id, object_near_id):
            # Et si les objet sont rangeable enssemble:
            object_carried_id = context.metas.value.get(CARRY, object_id)
            return cls._objects_types_match(context, object_carried_id, object_near_id)
        return False

    @classmethod
    def get_put_position(cls, context, object_id, object_near_id):
        """
        Maybe part "found available position arround" should be in other class.
        :param context:
        :param object_id:
        :param object_near_id:
        :return:
        """
        obj_near_position = context.metas.value.get(POSITION, object_near_id)
        if cls._is_available_position(context, obj_near_position):
            return obj_near_position

        obj_transporter_position = context.metas.value.get(POSITION, object_id)
        obj_transporter_around_positions = context.get_around_points_of_point(obj_transporter_position)
        obj_near_around_positions = context.get_around_points_of_point(obj_near_position)

        # For each position between target and current transporter
        for pos_around_target in obj_near_around_positions:
            if pos_around_target in obj_transporter_around_positions:
                if cls._is_available_position(context, pos_around_target):
                    return pos_around_target
        raise CantFindWhereToPut()

    @classmethod
    def _is_available_position(cls, context, position):
        if not context.position_is_penetrable(position):
            return False

        if not cls._position_is_enought_place(context, position):
            return False

        if not cls._position_have_free_space_around(context, position):
            return False

        return True

    @classmethod
    def _position_is_enought_place(cls, context, position):
        count_obj_here = len(context.metas.list.get(POSITIONS, position, allow_empty=True))
        if count_obj_here < Core.get_configuration_manager().get('ant.put.max_objects_at_same_position', 5):
            return True
        return False

    @classmethod
    def _position_have_free_space_around(cls, context, position):
        # TODO: On regarde ces cases qui ne sont pas forcement visible par l'individu. On presupose ici qu'elle peut les
        # voir. Pour etre au top il faudrait une logie propre a l'individu qui definis ce qu'il voie.
        around_positions = context.get_around_points_of_point(position)
        for around_position in around_positions:
            if not context.position_is_penetrable(around_position):
                return False
        return True

    def done(self, puted_object):
        # TODO: lancer le choix d'un nouveau mode dans le brain.
        if isinstance(puted_object, Food):
            self._host.get_brain().switch_to_mode(MODE_EXPLO) class AntTakeBrainPart(TakeBrainPart):

    # TODO: methode __init_ pour la classe ? Pour surcharger ici.
    _mode_matches = {
        MODE_EXPLO: [TYPE_RESOURCE_EXPLOITABLE],
        MODE_NURSE: [TYPE_NURSERY],
        MODE_GOHOME: [],
        MODE_SEARCH_AROUND: []
    }

    def __init__(self, host_brain):
        super().__init__(host_brain)
        self._smell_target = None

    def get_smell_target(self):
        if not self._smell_target:
            raise Exception('No _smell_target')
        return self._smell_target

    @classmethod
    def can_take(cls, context, object_id, object_to_take_id):
        # Pour le moment si le type de l'objet fait partie des types admis pour le mode courant du porteur, c'est bon.
        return cls._match_with_mode(context, object_id, object_to_take_id)

    def done(self, take_object):
        # TODO: Ranger ca ? Truc plus dynamique/configurable ?
        # TODO: qqch plus generique ... (attention aux eggs)
        if isinstance(take_object, Resource):
            self._smell_target = self._host_brain.get_smell_for_object_taken(take_object)
            self._host.get_brain().switch_to_mode(MODE_GOHOME)
            self._host.get_movement_molecule_gland().appose() class Food(Resource):

    def __init__(self, collection, context):
        super().__init__(collection, context)
        context.metas.list.add(TYPE, self.get_id(), TYPE_RESOURCE_EXPLOITABLE)

    def get_what_carry(self):
        return self  # dev
        clone = self.__class__(self._collection, self._context)
        self._collection.add_object(clone)
        return clone

    def transform_to_stocked(self):
        self._context.metas.list.remove(TYPE, self.get_id(), TYPE_RESOURCE_EXPLOITABLE)
        self._context.metas.list.add(TYPE, self.get_id(), TYPE_RESOURCE_EATABLE)
        self._add_col(COL_EATABLE)
        self._add_col(COL_SMELL)
        self._set_smell(SMELL_FOOD) class Egg(BaseEgg):
    pass 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()
from intelligine.simulation.molecule.DirectionMolecule import DirectionMolecule
from intelligine.simulation.object.brain.Brain import Brain
from intelligine.simulation.object.brain.part.attack.AttackBrainPart import AttackBrainPart
from intelligine.cst import MODE, MODE_EXPLO, MODE_GOHOME, PHEROMON_DIR_EXPLO, \
    BRAIN_PART_TAKE, BRAIN_PART_PUT, MODE_NURSE, PHEROMON_DIR_NONE, BRAIN_PART_ATTACK, MODE_HOME, \
    SMELL_FOOD, SMELL_EGG, MODE_GO_OUTSIDE, MOLECULE_SEARCHING_WAY, MODE_SEARCH_AROUND
from intelligine.cst import MOLECULE_SEARCHING
from intelligine.cst import BRAIN_PART_MOVE
from intelligine.simulation.object.brain.part.transport.AntPutBrainPart import AntPutBrainPart
from intelligine.simulation.object.brain.part.transport.AntTakeBrainPart import AntTakeBrainPart
from intelligine.synergy.object.Food import Food
from intelligine.synergy.object.ant.Egg import Egg
from intelligine.simulation.object.brain.part.move.AntMoveBrainPart import AntMoveBrainPart
from synergine.core.exceptions import NotFound


class AntBrain(Brain):

    _brain_parts = Brain._brain_parts.copy()
    _brain_parts.update({
        BRAIN_PART_MOVE: AntMoveBrainPart,
        BRAIN_PART_TAKE: AntTakeBrainPart,
        BRAIN_PART_PUT: AntPutBrainPart,
        BRAIN_PART_ATTACK: AttackBrainPart
    })

    _taken_smell_matches = {
        Food: SMELL_FOOD,
        Egg: SMELL_EGG
    }
    """ Correspondance entre ce qui est ramassé et où ce doit être stocké """

    def __init__(self, context, host):
        super().__init__(context, host)
        self._movement_mode = MODE_HOME
        self._distance_from_objective = 0
        self._molecule_searching = PHEROMON_DIR_EXPLO

    def switch_to_mode(self, mode):
        self._movement_mode = mode
        self._update_molecule_gland(mode)
        self._context.metas.value.set(MODE, self._host.get_id(), mode)
        self._update_molecule_searching(mode)

    def _update_molecule_gland(self, mode):
        if mode == MODE_EXPLO:
            molecule_direction_type = None

        elif mode == MODE_GOHOME: