import numpy as np


class Agent:
    """
    A single agent in a matching market
    Attributes
    ---------
    discount_rate: float in [0,1]
        Rate of utility discounting
        between entry and match
    is_critical: bool
        1 when agent is critical
        automatically based on sojourn==time_to_critical
    name: int
        used to index agent in others' containers
    myType: object (typically in, sting or list<string>)
        agent's type. Used in matching to compute compatibility
    matchUtil: dict<name, int>
        utility from match with agent "name"
        UtilFct becomes matchUtil[name] * UtilFct
    matchFailProb: dict<name, [0,1]>
        fail probability (at each subsequent period)
        from matching with name
    sojourn: int
        # of periods agent is in market
    time_to_critical: int
        # of rounds from entry to criticality
        Generated by a Poisson process __at instantiation__
    UtilFct: fct(int) -> float
        may be overriden from default
    """
    def __init__(self, name=0, discount_rate=0, time_to_critical=0,
                 myType=1, myType2=None
                 ):
        """
        Default constructor is for 1 period market, homogeneous agents
        """
        self.name = name
        self.discount_rate = discount_rate
        self.time_to_critical = time_to_critical
        self.type = myType
        self.type2 = myType2
        self.match_util = dict()
        self.match_fail_prob = dict()
        self.sojourn = -1
        self.is_critical = 0

    def addNewToMap(self, otherAgent, failProbResult=1,
                    utilityResult=1):
        """
        Adds new agents to self's preference maps
        Arguments
        ---------
        utilityResult: int
            Result of matchUtilFct
        failProbResult: float in [0,1]
            Result of compatFct
        """
        self.match_util[otherAgent.name] = utilityResult
        self.match_fail_prob[otherAgent.name] = failProbResult

    def update(self):
        """
        Called when time advances
        Updates members
        """
        self.sojourn += 1
        if self.sojourn >= self.time_to_critical:
            self.is_critical = 1

    def expUtilFct(self, t):
        """
        exponential utility discounting
        """
        return np.exp(- (1-self.discount_rate) * t)

    def linearUtilFct(self, t):
        """
        Linear time discounting
        """
        if t == 0:
            return 1
        return self.discount_rate ** t

    def utilFct(self, t=None, fct=linearUtilFct, matchUtility=1):
        """
        Used to pass arbitrary utilityFct
        """
        if t is None:
            t = self.sojourn
        return matchUtility * fct(self, t)

    def neighbors(self, with_self=False):
        """
        returns list<mm.Agent.name>
        where match_fail_prob > 0
        """
        neighbors = [
            key for key in self.match_fail_prob.keys()
            if self.match_fail_prob[key] > 0
            ]
        return neighbors
