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

from __future__ import annotations

from agentpy.objects import Object
from agentpy.sequences import AgentList

from . import agent as _agent
from . import model as _model


class Location(Object):
    """Base class for location objects."""

    label: str | None = None

    def __init__(self, model: _model.Model) -> None:
        """Location constructor.

        Args:
            model: Model this Location should be associated with.
        """
        super().__init__(model)
        self.model = model
        self.model.add_location(self)
        self.label = self.__class__.__name__ if self.label is None else self.label

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

        Returns:
            List of agents at this location.
        """
        return self.model.agents_of_location(self)

    def add_agent(self, agent: _agent.Agent, weight: float | None = None) -> None:
        """Assigns the given agent to this location.

        Args:
            agent: The agent that should be added to the location.
            weight: The edge weight between the agent 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.model.add_agent_to_location(self, agent=agent, weight=weight)

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

        Args:
            agents (list): An iterable over agents.
            weight(float | None): The edge weight between the agents 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 agent in agents:
            self.add_agent(agent=agent, weight=weight)

    def remove_agent(self, agent: _agent.Agent) -> None:
        """Removes the given agent from this location.

        Args:
            agent: Agent that is to be removed.
        """
        self.model.remove_agent_from_location(
            location=self,
            agent=agent,
        )

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

        Args:
            agents (list): An iterable over agents.
        """
        for agent in agents:
            self.remove_agent(agent=agent)

    def neighbors(self, agent: _agent.Agent) -> AgentList:
        """Returns a list of agents which are connected to the given agent via this location.

        Args:
            agent: Agent of whom the neighbors at this location are to be returned.

        Returns:
            AgentList: A list of all agents at this location except the passed agent.
        """
        agents = self.model.agents_of_location(self)
        agents.remove(agent)
        return agents

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

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

        Args:
            agent (Agent): The agent.
            weight (float): The weight.
        """
        self.model.set_weight(
            agent=agent,
            location=self,
            weight=weight,
        )

    def get_weight(self, agent: _agent.Agent) -> float:
        """Return the edge weight between an agent and the location.

        Args:
            agent: Agent of which the edge weight should be returned.

        Returns:
            Edge weight.
        """
        return self.model.get_weight(agent=agent, location=self)

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

        Args:
            agent (_type_): An agent.

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

    def project_weights(self, agent1: _agent.Agent, agent2: _agent.Agent) -> float:
        """Calculates the edge weight between two agents assigned to the same location instance.

        Defines how the weights are combined when the edge weight between two agents 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:
            agent1: First agent of the pair.
            agent2: Second agent 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(agent1), self.get_weight(agent2)])
