"""Base class to create Location objects."""

from __future__ import annotations

from . import actor as _actor


class Location:
    """Base class for location objects."""

    label: str | None = None

    def __init__(self, *args, **kwargs) -> None:
        """Location constructor."""
        self.label = self.__class__.__name__ if self.label is None else self.label
        self.env = None
        self.id_p2n = None
        self.model = None
        self.type = type(self).__name__
        super().__init__(*args, **kwargs)

    def setup(self):
        pass

    @property
    def actors(self) -> list:
        """Return the list of actors affiliated with this location.

        Returns:
            List of actors at this location.
        """
        return self.env.actors_of_location(self)

    def add_actor(self, actor: _actor.Actor, weight: float | None = None) -> None:
        """Assigns the given actor to this location.

        Args:
            actor: The actor that should be added to the location.
            weight: The edge weight between the actor and the location.
                Defaults to None. If weight is set to None, the weight is generated by
                location.weight(), which returns 1 by default.
        """
        self.env.add_actor_to_location(self, actor=actor, weight=weight)

    def add_actors(self, actors: list, weight: float | None = None) -> None:
        """Add multiple actors at once.

        Args:
            actors (list): An iterable over actors.
            weight(float | None): The edge weight between the actors and the location.
                Defaults to None. If weight is set to None, the weight is generated by
                location.weight(), which returns 1 by default.

        """
        for actor in actors:
            self.add_actor(actor=actor, weight=weight)

    def remove_actor(self, actor: _actor.Actor) -> None:
        """Removes the given actor from this location.

        Args:
            actor: Actor that is to be removed.
        """
        self.env.remove_actor_from_location(
            location=self,
            actor=actor,
        )

    def remove_actors(self, actors: list) -> None:
        """Remove multiple actors at once.

        Args:
            actors (list): An iterable over actors.
        """
        for actor in actors:
            self.remove_actor(actor=actor)

    def neighbors(self, actor: _actor.Actor) -> ActorList:
        """Returns a list of actors which are connected to the given actor via this location.

        Args:
            actor: Actor of whom the neighbors at this location are to be returned.

        Returns:
            ActorList: A list of all actors at this location except the passed actor.
        """
        actors = self.env.actors_of_location(self)
        actors.remove(actor)
        return actors

    def set_weight(self, actor, weight: float | None = None) -> None:
        """Set the weight of an actor at the current location.

        If weight is None the method location.weight() will be used to generate a weight.

        Args:
            actor (Actor): The actor.
            weight (float): The weight.
        """
        self.env.set_weight(
            actor=actor,
            location=self,
            weight=weight,
        )

    def get_weight(self, actor: _actor.Actor) -> float:
        """Return the edge weight between an actor and the location.

        Args:
            actor: Actor of which the edge weight should be returned.

        Returns:
            Edge weight.
        """
        return self.env.get_weight(actor=actor, location=self)

    def weight(self, actor) -> float:  # noqa: ARG002
        """Generates the edge weight between a given actor and the location instance.

        Args:
            actor (_type_): An actor.

        Returns:
            float: The weight between the given actor and the location.
        """
        return 1

    def project_weights(self, actor1: _actor.Actor, actor2: _actor.Actor) -> float:
        """Calculates the edge weight between two actors assigned to the same location instance.

        Defines how the weights are combined when the edge weight between two actors is determined.
        Can be completely rewritten to have location-specific methods of this kind with the same
        name or can be used as it is in the simulation code.

        Args:
            actor1: First actor of the pair.
            actor2: Second actor of the pair.

        Raises:
            Exception: Raised if `self.weight_projection_function` is not in ["average", "simultan"]

        Returns:
            Combined edge weight.
        """
        return min([self.get_weight(actor1), self.get_weight(actor2)])
