class Agent(object):
    def __init__(self, jid: str, password: str, verify_security: bool = False):
        """
        Creates an agent

        Args:
          jid (str): The identifier of the agent in the form username@server
          password (str): The password to connect to the server
          verify_security (bool): Wether to verify or not the SSL certificates
        """
        self.jid = aioxmpp.JID.fromstr(jid)
        self.password = password
        self.verify_security = verify_security

        self.behaviours = []
        self._values = {}

        self.conn_coro = None
        self.stream = None
        self.client = None
        self.message_dispatcher = None
        self.presence = None
        self.loop = None

        self.container = Container()
        self.container.register(self)

        self.loop = self.container.loop

        # Web service
        self.web = WebApp(agent=self)

        self.traces = TraceStore(size=1000)

        self._alive = Event()

    def set_loop(self, loop) -> None:
        self.loop = loop

    def set_container(self, container: Container) -> None:
        """
        Sets the container to which the agent is attached

        Args:
            container (spade.container.Container): the container to be attached to
        """
        self.container = container

    def start(self, auto_register: bool = True) -> Union[Coroutine, Future]:
        """
        Tells the container to start this agent.
        It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method.

        Args:
            auto_register (bool): register the agent in the server (Default value = True)

        Returns:
            Coroutine: if called from an async method
            Future: if called from a synchronized method
        """
        return self.container.start_agent(agent=self, auto_register=auto_register)

    async def _async_start(self, auto_register: bool = True) -> None:
        """
        Starts the agent from a coroutine. This fires some actions:

            * if auto_register: register the agent in the server
            * runs the event loop
            * connects the agent to the server
            * runs the registered behaviours

        Args:
          auto_register (bool, optional): register the agent in the server (Default value = True)

        """

        await self._hook_plugin_before_connection()

        if auto_register:
            await self._async_register()
        self.client = aioxmpp.PresenceManagedClient(
            self.jid,
            aioxmpp.make_security_layer(
                self.password, no_verify=not self.verify_security
            ),
            loop=self.loop,
            logger=logging.getLogger(self.jid.localpart),
        )

        # obtain an instance of the service
        self.message_dispatcher = self.client.summon(SimpleMessageDispatcher)

        # Presence service
        self.presence = PresenceManager(self)

        await self._async_connect()

        # register a message callback here
        self.message_dispatcher.register_callback(
            aioxmpp.MessageType.CHAT,
            None,
            self._message_received,
        )

        await self._hook_plugin_after_connection()

        await self.setup()
        self._alive.set()
        for behaviour in self.behaviours:
            if not behaviour.is_running:
                behaviour.set_agent(self)
                if issubclass(type(behaviour), FSMBehaviour):
                    for _, state in behaviour.get_states().items():
                        state.set_agent(self)
                behaviour.start()

    async def _hook_plugin_before_connection(self) -> None:
        """
        Overload this method to hook a plugin before connetion is done
        """
        pass

    async def _hook_plugin_after_connection(self) -> None:
        """
        Overload this method to hook a plugin after connetion is done
        """
        pass

    async def _async_connect(self) -> None:  # pragma: no cover
        """ connect and authenticate to the XMPP server. Async mode. """
        try:
            self.conn_coro = self.client.connected()
            aenter = type(self.conn_coro).__aenter__(self.conn_coro)
            self.stream = await aenter
            logger.info(f"Agent {str(self.jid)} connected and authenticated.")
        except aiosasl.AuthenticationFailure:
            raise AuthenticationFailure(
                "Could not authenticate the agent. Check user and password or use auto_register=True"
            )

    async def _async_register(self) -> None:  # pragma: no cover
        """ Register the agent in the XMPP server from a coroutine. """
        metadata = aioxmpp.make_security_layer(None, no_verify=not self.verify_security)
        query = ibr.Query(self.jid.localpart, self.password)
        _, stream, features = await aioxmpp.node.connect_xmlstream(
            self.jid, metadata, loop=self.loop
        )
        await ibr.register(stream, query)

    async def setup(self) -> None:
        """
        Setup agent before startup.
        This coroutine may be overloaded.
        """
        await asyncio.sleep(0)

    @property
    def name(self) -> str:
        """
        Returns the name of the agent (the string before the '@')

        Returns:
            str: the name of the agent (the string before the '@')
        """
        return self.jid.localpart

    @property
    def avatar(self) -> str:
        """
        Generates a unique avatar for the agent based on its JID.
        Uses Gravatar service with MonsterID option.

        Returns:
          str: the url of the agent's avatar

        """
        return self.build_avatar_url(self.jid.bare())

    @staticmethod
    def build_avatar_url(jid: str) -> str:
        """
        Static method to build a gravatar url with the agent's JID

        Args:
          jid (aioxmpp.JID): an XMPP identifier

        Returns:
          str: an URL for the gravatar

        """
        digest = md5(str(jid).encode("utf-8")).hexdigest()
        return "http://www.gravatar.com/avatar/{md5}?d=monsterid".format(md5=digest)

    def submit(self, coro: Coroutine) -> Future:
        """
        Runs a coroutine in the event loop of the agent.
        this call is not blocking.

        Args:
          coro (Coroutine): the coroutine to be run

        Returns:
            asyncio.Future: the future of the coroutine execution

        """
        return asyncio.run_coroutine_threadsafe(coro, loop=self.loop)

    def add_behaviour(
        self, behaviour: Type[CyclicBehaviour], template: Optional[Template] = None
    ) -> None:
        """
        Adds and starts a behaviour to the agent.
        If template is not None it is used to match
        new messages and deliver them to the behaviour.

        Args:
          behaviour (Type[spade.behaviour.CyclicBehaviour]): the behaviour to be started
          template (spade.template.Template, optional): the template to match messages with (Default value = None)

        """
        behaviour.set_agent(agent=self)
        if issubclass(type(behaviour), FSMBehaviour):
            for _, state in behaviour.get_states().items():
                state.set_agent(self)
        behaviour.set_template(template)
        self.behaviours.append(behaviour)
        if self.is_alive():
            behaviour.start()

    def remove_behaviour(self, behaviour: Type[CyclicBehaviour]) -> None:
        """
        Removes a behaviour from the agent.
        The behaviour is first killed.

        Args:
          behaviour (Type[spade.behaviour.CyclicBehaviour]): the behaviour instance to be removed

        """
        if not self.has_behaviour(behaviour):
            raise ValueError("This behaviour is not registered")
        index = self.behaviours.index(behaviour)
        self.behaviours[index].kill()
        self.behaviours.pop(index)

    def has_behaviour(self, behaviour: Type[CyclicBehaviour]) -> bool:
        """
        Checks if a behaviour is added to an agent.

        Args:
          behaviour (Type[spade.behaviour.CyclicBehaviour]): the behaviour instance to check

        Returns:
          bool: a boolean that indicates wether the behaviour is inside the agent.

        """
        return behaviour in self.behaviours

    def stop(self) -> Union[Coroutine, Future]:
        """
        Tells the container to start this agent.
        It returns a coroutine or a future depending on whether it is called from a coroutine or a synchronous method.
        """
        return self.container.stop_agent(self)

    async def _async_stop(self) -> None:
        """ Stops an agent and kills all its behaviours. """
        if self.presence:
            self.presence.set_unavailable()
        for behav in self.behaviours:
            behav.kill()
        if self.web.is_started():
            await self.web.runner.cleanup()

        """ Discconnect from XMPP server. """
        if self.is_alive():
            # Disconnect from XMPP server
            self.client.stop()
            aexit = self.conn_coro.__aexit__(*sys.exc_info())
            await aexit
            logger.info("Client disconnected.")

        self._alive.clear()

    def is_alive(self) -> bool:
        """
        Checks if the agent is alive.

        Returns:
          bool: wheter the agent is alive or not

        """
        return self._alive.is_set()

    def set(self, name: str, value: Any):
        """
        Stores a knowledge item in the agent knowledge base.

        Args:
          name (str): name of the item
          value (object): value of the item

        """
        self._values[name] = value

    def get(self, name: str) -> Any:
        """
        Recovers a knowledge item from the agent's knowledge base.

        Args:
          name(str): name of the item

        Returns:
          object: the object retrieved or None

        """
        if name in self._values:
            return self._values[name]
        else:
            return None

    def _message_received(self, msg: aioxmpp.Message) -> List[Future]:
        """
        Callback run when an XMPP Message is reveived.
        This callback delivers the message to every behaviour
        that is waiting for it. First, the aioxmpp.Message is
        converted to spade.message.Message

        Args:
          msg (aioxmpp.Messagge): the message just received.

        Returns:
            list(asyncio.Future): a list of futures of the append of the message at each matched behaviour.

        """

        msg = Message.from_node(msg)
        return self.dispatch(msg)

    def dispatch(self, msg: Message) -> List[Future]:
        """
        Dispatch the message to every behaviour that is waiting for
        it using their templates match.

        Args:
          msg (spade.message.Messagge): the message to dispatch.

        Returns:
            list(asyncio.Future): a list of futures of the append of the message at each matched behaviour.

        """
        logger.debug(f"Got message: {msg}")
        futures = []
        matched = False
        for behaviour in (x for x in self.behaviours if x.match(msg)):
            futures.append(self.submit(behaviour.enqueue(msg)))
            logger.debug(f"Message enqueued to behaviour: {behaviour}")
            self.traces.append(msg, category=str(behaviour))
            matched = True
        if not matched:
            logger.warning(f"No behaviour matched for message: {msg}")
            self.traces.append(msg)
        return futures class OneShotBehaviour(CyclicBehaviour, metaclass=ABCMeta):
    """This behaviour is only executed once"""

    def __init__(self):
        super().__init__()
        self._already_executed = False

    def _done(self) -> bool:
        """ """
        if not self._already_executed:
            self._already_executed = True
            return False
        return True class CyclicBehaviour(object, metaclass=ABCMeta):
    """ This behaviour is executed cyclically until it is stopped. """

    def __init__(self):
        self.agent = None
        self.template = None
        self._force_kill = Event()
        self._is_done = asyncio.Event()
        self._is_done.set()
        self._exit_code = 0
        self.presence = None
        self.web = None
        self.is_running = False

        self.queue = None

    def set_agent(self, agent) -> None:
        """
        Links behaviour with its owner agent

        Args:
          agent (spade.agent.Agent): the agent who owns the behaviour

        """
        self.agent = agent
        self.queue = asyncio.Queue(loop=self.agent.loop)
        self.presence = agent.presence
        self.web = agent.web

    def set_template(self, template: Template) -> None:
        """
        Sets the template that is used to match incoming
        messages with this behaviour.

        Args:
          template (spade.template.Template): the template to match with

        """
        self.template = template

    def match(self, message: Message) -> bool:
        """
        Matches a message with the behaviour's template

        Args:
          message(spade.message.Message): the message to match with

        Returns:
          bool: wheter the messaged matches or not

        """
        if self.template:
            return self.template.match(message)
        return True

    def set(self, name: str, value: Any) -> None:
        """
        Stores a knowledge item in the agent knowledge base.

        Args:
          name (str): name of the item
          value (Any): value of the item

        """
        self.agent.set(name, value)

    def get(self, name: str) -> Any:
        """
        Recovers a knowledge item from the agent's knowledge base.

        Args:
          name (str): name of the item

        Returns:
          Any: the object retrieved or None

        """
        return self.agent.get(name)

    def start(self) -> None:
        """starts behaviour in the event loop"""
        self.agent.submit(self._start())
        self.is_running = True

    async def _start(self) -> None:
        """
        Start coroutine. runs on_start coroutine and then
        runs the _step coroutine where the body of the behaviour
        is called.
        """
        self.agent._alive.wait()
        try:
            await self.on_start()
        except Exception as e:
            logger.error(
                "Exception running on_start in behaviour {}: {}".format(self, e)
            )
            self.kill(exit_code=e)
        await self._step()
        self._is_done.clear()

    def kill(self, exit_code: Optional[Any] = None) -> None:
        """
        Stops the behaviour

        Args:
          exit_code (object, optional): the exit code of the behaviour (Default value = None)

        """
        self._force_kill.set()
        if exit_code is not None:
            self._exit_code = exit_code
        logger.info("Killing behavior {0} with exit code: {1}".format(self, exit_code))

    def is_killed(self) -> bool:
        """
        Checks if the behaviour was killed by means of the kill() method.

        Returns:
          bool: whether the behaviour is killed or not

        """
        return self._force_kill.is_set()

    @property
    def exit_code(self) -> Any:
        """
        Returns the exit_code of the behaviour.
        It only works when the behaviour is done or killed,
        otherwise it raises an exception.

        Returns:
          object: the exit code of the behaviour

        Raises:
            BehaviourNotFinishedException: if the behaviour is not yet finished

        """
        if self._done() or self.is_killed():
            return self._exit_code
        else:
            raise BehaviourNotFinishedException

    @exit_code.setter
    def exit_code(self, value: Any) -> None:
        """
        Sets a new exit code to the behaviour.

        Args:
          value (object): the new exit code

        """
        self._exit_code = value

    def _done(self) -> bool:
        """
        Returns True if the behaviour has finished
        else returns False

        Returns:
          bool: whether the behaviour is finished or not

        """
        return False

    def is_done(self) -> bool:
        """
        Check if the behaviour is finished

        Returns:
             bool: whether the behaviour is finished or not
        """
        return not self._is_done.is_set()

    def join(self, timeout: Optional[float] = None) -> Optional[Coroutine]:
        """
        Wait for the behaviour to complete

        Args:
            timeout (Optional[float]): an optional timeout to wait to join (if None, the join is blocking)

        Returns:
            None: if called from a synchronous method
            Coroutine: if called from an async method

        Raises:
            TimeoutError: if the timeout is reached
        """

        try:
            in_coroutine = asyncio.get_event_loop() == self.agent.loop
        except RuntimeError:  # pragma: no cover
            in_coroutine = False

        if not in_coroutine:
            t_start = time.time()
            while not self.is_done():
                time.sleep(0.001)
                t = time.time()
                if timeout is not None and t - t_start > timeout:
                    raise TimeoutError
        else:
            return self._async_join(timeout=timeout)

    async def _async_join(self, timeout: Optional[float]) -> None:
        """
        Coroutine to wait until a behaviour is finished

        Args:
            timeout (Optional[float]): an optional timeout to wait to join

        Raises:
            TimeoutError: fi the timeout is reached
        """
        t_start = time.time()
        while not self.is_done():
            await asyncio.sleep(0.001)
            t = time.time()
            if timeout is not None and t - t_start > timeout:
                raise TimeoutError

    async def on_start(self) -> None:
        """
        Coroutine called before the behaviour is started.
        """
        pass

    async def on_end(self) -> None:
        """
        Coroutine called after the behaviour is done or killed.
        """
        pass

    @abstractmethod
    async def run(self) -> None:
        """
        Body of the behaviour.
        To be implemented by user.
        """
        raise NotImplementedError  # pragma: no cover

    async def _run(self) -> None:
        """
        Function to be overload by more complex behaviours.
        In other case it just calls run() coroutine.
        """
        await self.run()

    async def _step(self) -> None:
        """
        Main loop of the behaviour.
        checks whether behaviour is done or killed,
        ortherwise it calls run() coroutine.
        """
        cancelled = False
        while not self._done() and not self.is_killed():
            try:
                await self._run()
                await asyncio.sleep(0)  # relinquish cpu
            except CancelledError:
                logger.info("Behaviour {} cancelled".format(self))
                cancelled = True
            except Exception as e:
                logger.error(
                    "Exception running behaviour {behav}: {exc}".format(
                        behav=self, exc=e
                    )
                )
                logger.error(traceback.format_exc())
                self.kill(exit_code=e)
        try:
            if not cancelled:
                await self.on_end()
        except Exception as e:
            logger.error("Exception running on_end in behaviour {}: {}".format(self, e))
            self.kill(exit_code=e)
        self.agent.remove_behaviour(self)

    async def enqueue(self, message: Message) -> None:
        """
        Enqueues a message in the behaviour's mailbox

        Args:
            message (spade.message.Message): the message to be enqueued
        """
        await self.queue.put(message)

    def mailbox_size(self) -> int:
        """
        Checks if there is a message in the mailbox

        Returns:
          int: the number of messages in the mailbox

        """
        return self.queue.qsize()

    async def send(self, msg: Message) -> None:
        """
        Sends a message.

        Args:
            msg (spade.message.Message): the message to be sent.
        """
        if not msg.sender:
            msg.sender = str(self.agent.jid)
            logger.debug(f"Adding agent's jid as sender to message: {msg}")
        await self.agent.container.send(msg, self)
        msg.sent = True
        self.agent.traces.append(msg, category=str(self))

    async def _xmpp_send(self, msg: Message) -> None:
        aioxmpp_msg = msg.prepare()
        await self.agent.client.send(aioxmpp_msg)

    async def receive(self, timeout: Optional[float] = None) -> Optional[Message]:
        """
        Receives a message for this behaviour.
        If timeout is not None it returns the message or "None"
        after timeout is done.

        Args:
            timeout (float, optional): number of seconds until return

        Returns:
            spade.message.Message: a Message or None
        """
        if timeout:
            coro = self.queue.get()
            try:
                msg = await asyncio.wait_for(coro, timeout=timeout)
            except asyncio.TimeoutError:
                msg = None
        else:
            try:
                msg = self.queue.get_nowait()
            except asyncio.QueueEmpty:
                msg = None
        return msg

    def __str__(self) -> str:
        return "{}/{}".format(
            "/".join(base.__name__ for base in self.__class__.__bases__),
            self.__class__.__name__,
        ) class PeriodicBehaviour(CyclicBehaviour, metaclass=ABCMeta):
    """This behaviour is executed periodically with an interval"""

    def __init__(self, period: float, start_at: Optional[datetime] = None):
        """
        Creates a periodic behaviour.

        Args:
            period (float): interval of the behaviour in seconds
            start_at (datetime.datetime): whether to start the behaviour with an offset
        """
        super().__init__()
        self._period = None
        self.period = period

        if start_at:
            self._next_activation = start_at
        else:
            self._next_activation = now()

    @property
    def period(self) -> timedelta:
        """ Get the period. """
        return self._period

    @period.setter
    def period(self, value: float) -> None:
        """
        Set the period.

        Args:
          value (float): seconds
        """
        if value < 0:
            raise ValueError("Period must be greater or equal than zero.")
        self._period = timedelta(seconds=value)

    async def _run(self) -> None:
        if now() >= self._next_activation:
            logger.debug(f"Periodic behaviour activated: {self}")
            await self.run()
            if self.period <= timedelta(seconds=0):
                self._next_activation = now()
            else:
                while self._next_activation <= now():
                    self._next_activation += self._period
        else:
            seconds = (self._next_activation - now()).total_seconds()
            if seconds > 0:
                logger.debug(
                    f"Periodic behaviour going to sleep for {seconds} seconds: {self}"
                )
                await asyncio.sleep(seconds) class TimeoutBehaviour(OneShotBehaviour, metaclass=ABCMeta):
    """This behaviour is executed once at after specified datetime"""

    def __init__(self, start_at):
        """
        Creates a timeout behaviour, which is run at start_at

        Args:
            start_at (datetime.datetime): when to start the behaviour
        """
        super().__init__()

        self._timeout = start_at
        self._timeout_triggered = False

    async def _run(self) -> None:
        if now() >= self._timeout:
            logger.debug(f"Timeout behaviour activated: {self}")
            await self.run()
            self._timeout_triggered = True
        else:
            seconds = (self._timeout - now()).total_seconds()
            if seconds > 0:
                logger.debug(
                    f"Timeout behaviour going to sleep for {seconds} seconds: {self}"
                )
                await asyncio.sleep(seconds)
                await self.run()
                self._timeout_triggered = True

    def _done(self) -> bool:
        """ """
        return self._timeout_triggered class FSMBehaviour(CyclicBehaviour):
    """A behaviour composed of states (oneshotbehaviours) that may transition from one state to another."""

    def __init__(self):
        super().__init__()
        self._states: Dict[str, State] = {}
        self._transitions = collections.defaultdict(list)
        self.current_state: Optional[str] = None
        self.setup()

    def setup(self) -> None:
        """ """
        pass

    def add_state(self, name: str, state: State, initial: bool = False) -> None:
        """Adds a new state to the FSM.

        Args:
          name (str): the name of the state, which is used as its identifier.
          state (spade.behaviour.State): The state class
          initial (bool, optional): wether the state is the initial state or not. (Only one initial state is allowed) (Default value = False)

        """
        if not issubclass(state.__class__, State):
            raise AttributeError("state must be subclass of spade.behaviour.State")
        self._states[name] = state
        if initial:
            self.current_state = name

    def get_state(self, name) -> State:
        return self._states[name]

    def get_states(self) -> Dict[str, State]:
        return self._states

    def add_transition(self, source: str, dest: str) -> None:
        """Adds a transition from one state to another.

        Args:
          source (str): the name of the state from where the transition starts
          dest (str): the name of the state where the transition ends

        """
        self._transitions[source].append(dest)

    def is_valid_transition(self, source: str, dest: str) -> bool:
        """
        Checks if a transitions is registered in the FSM

        Args:
          source (str): the source state name
          dest (str): the destination state name

        Returns:
          bool: wether the transition is valid or not

        """
        if dest not in self._states or source not in self._states:
            raise NotValidState
        elif dest not in self._transitions[source]:
            raise NotValidTransition
        return True

    async def _run(self) -> None:
        behaviour = self._states[self.current_state]
        behaviour.set_agent(self.agent)
        behaviour.receive = self.receive
        logger.info(f"FSM running state {self.current_state}")
        try:
            await behaviour.on_start()
        except Exception as e:
            logger.error("Exception running on_start in state {}: {}".format(self, e))
            self.kill(exit_code=e)
        try:
            await behaviour.run()
        except Exception as e:
            logger.error("Exception running state {}: {}".format(self, e))
            self.kill(exit_code=e)
        try:
            await behaviour.on_end()
        except Exception as e:
            logger.error("Exception running on_start in state {}: {}".format(self, e))
            self.kill(exit_code=e)

        dest = behaviour.next_state
        behaviour._is_done.clear()

        if dest:
            try:
                if self.is_valid_transition(self.current_state, dest):
                    logger.info(f"FSM transiting from {self.current_state} to {dest}.")
                    self.current_state = dest
            except NotValidState as e:
                logger.error(
                    f"FSM could not transitate to state {dest}. That state does not exist."
                )
                self.kill(exit_code=e)
            except NotValidTransition as e:
                logger.error(
                    f"FSM could not transitate to state {dest}. That transition is not registered."
                )
                self.kill(exit_code=e)
        else:
            logger.info(
                "FSM arrived to a final state (no transitions found). Killing FSM."
            )
            self.kill()

    async def run(self) -> None:
        """
        In this kind of behaviour there is no need to overload run.
        The run methods to be overloaded are in the State class.
        """
        raise RuntimeError  # pragma: no cover

    def to_graphviz(self) -> str:
        """
        Converts the FSM behaviour structure to Graphviz syntax

        Returns:
          str: the graph in Graphviz syntax

        """
        graph = "digraph finite_state_machine { rankdir=LR; node [fixedsize=true];"
        for origin, dest in self._transitions.items():
            origin = origin.replace(" ", "_")
            for d in dest:
                d = d.replace(" ", "_")
                graph += "{0} -> {1};".format(origin, d)
        graph += "}"
        return graph class State(OneShotBehaviour, metaclass=ABCMeta):
    """A state of a FSMBehaviour is a OneShotBehaviour"""

    def __init__(self):
        super().__init__()
        self.next_state = None

    def set_next_state(self, state_name: str) -> None:
        """
        Set the state to transition to when this state is finished.
        state_name must be a valid state and the transition must be registered.
        If set_next_state is not called then current state is a final state.

        Args:
          state_name (str): the name of the state to transition to

        """
        self.next_state = state_name class NotValidState(Exception):
    """ """

    pass class NotValidTransition(Exception):
    """ """

    pass class BehaviourNotFinishedException(Exception):
    """ """

    pass class Message(MessageBase):
    """ """

    def make_reply(self) -> 'Message':
        """
        Creates a copy of the message, exchanging sender and receiver

        Returns:
          spade.message.Message: a new message with exchanged sender and receiver

        """
        return Message(
            to=str(self.sender),
            sender=str(self.to),
            body=self.body,
            thread=self.thread,
            metadata=self.metadata,
        )

    def prepare(self) -> aioxmpp.Message:
        """
        Returns an aioxmpp.stanza.Message built from the Message and prepared to be sent.

        Returns:
          aioxmpp.stanza.Message: the message prepared to be sent

        """

        msg = aioxmpp.stanza.Message(
            to=self.to,
            from_=self.sender,
            type_=aioxmpp.MessageType.CHAT,
        )

        msg.body[None] = self.body

        # Send metadata using xep-0004: Data Forms (https://xmpp.org/extensions/xep-0004.html)
        if len(self.metadata):
            data = forms_xso.Data(type_=forms_xso.DataType.FORM)

            for name, value in self.metadata.items():
                data.fields.append(
                    forms_xso.Field(
                        var=name,
                        type_=forms_xso.FieldType.TEXT_SINGLE,
                        values=[value],
                    )
                )

            if self.thread:
                data.fields.append(
                    forms_xso.Field(
                        var="_thread_node",
                        type_=forms_xso.FieldType.TEXT_SINGLE,
                        values=[self.thread],
                    )
                )

            data.title = SPADE_X_METADATA
            msg.xep0004_data = [data]

        return msg

    def __str__(self) -> str:
        s = f'<message to="{self.to}" from="{self.sender}" thread="{self.thread}" metadata={self.metadata}>'
        if self.body:
            s += "\n" + self.body + "\n"
        s += "</message>"
        return s SPADE_X_METADATA = "spade:x:metadata" class Template(BaseTemplate, MessageBase):
    """Template for message matching"""

    def __str__(self) -> str:
        s = f'<template to="{self.to}" from="{self.sender}" thread="{self.thread}" metadata={self.metadata}>'
        if self.body:
            s += "\n" + self.body + "\n"
        s += "</template>"
        return s def wait_for_behaviour_is_killed(behaviour, tries=500, sleep=0.01):
    counter = 0
    while not behaviour.is_killed() and counter < tries:
        time.sleep(sleep)
        counter += 1
    if not behaviour.is_killed():
        raise Exception("Behaviour not finished") class MockedAgentFactory(factory.Factory):
    class Meta:
        model = MockedConnectedAgent

    jid = "fake@jid"
    password = "fake_password"
import asyncio
import datetime
import aioxmpp
import pytest
from threading import Event
from asynctest import CoroutineMock, MagicMock, Mock
from spade.agent import Agent
from spade.behaviour import (
    OneShotBehaviour,
    CyclicBehaviour,
    PeriodicBehaviour,
    TimeoutBehaviour,
    FSMBehaviour,
    State,
    NotValidState,
    NotValidTransition,
    BehaviourNotFinishedException,
)
from spade.message import Message, SPADE_X_METADATA
from spade.template import Template
from .conftest import wait_for_behaviour_is_killed
from .factories import MockedAgentFactory
    assert agent.cycles == 0

    agent.add_behaviour(behaviour)
    behaviour.join()

    assert agent.cycles == 3
    assert behaviour.is_killed()

    agent.stop()


def test_oneshot_behaviour():
    class TestOneShotBehaviour(OneShotBehaviour):
        async def run(self):
            self.agent.one_shot_behaviour_executed = True
            self.kill()

    agent = MockedAgentFactory()
    agent.one_shot_behaviour_executed = False
    behaviour = TestOneShotBehaviour()
    agent.add_behaviour(behaviour)

    assert agent.one_shot_behaviour_executed is False

    future = agent.start(auto_register=False)
    future.result()
    behaviour.join()

    assert agent.one_shot_behaviour_executed is True
    agent.stop()


def test_periodic_behaviour():
    class TestPeriodicBehaviour(PeriodicBehaviour):
        async def run(self):
            self.agent.periodic_behaviour_execution_counter += 1
            self.kill()

    agent = MockedAgentFactory()
    agent.periodic_behaviour_execution_counter = 0
    behaviour = TestPeriodicBehaviour(0.01)
    agent.add_behaviour(behaviour)

    assert agent.periodic_behaviour_execution_counter == 0

    future = agent.start(auto_register=False)
    future.result()
    behaviour.join()

    assert agent.periodic_behaviour_execution_counter == 1
    agent.stop()


def test_periodic_behaviour_period_zero():
    class TestPeriodicBehaviour(PeriodicBehaviour):
        async def run(self):
            self.agent.periodic_behaviour_execution_counter += 1
            self.kill()

    agent = MockedAgentFactory()
    agent.periodic_behaviour_execution_counter = 0
    behaviour = TestPeriodicBehaviour(0)
    agent.add_behaviour(behaviour)

    assert agent.periodic_behaviour_execution_counter == 0

    future = agent.start(auto_register=False)
    future.result()
    behaviour.join()

    assert agent.periodic_behaviour_execution_counter == 1
    agent.stop()


def test_periodic_behaviour_negative_period():
    class TestPeriodicBehaviour(PeriodicBehaviour):
        async def run(self):
            pass

    with pytest.raises(ValueError):
        TestPeriodicBehaviour(-1)


def test_set_period():
    class TestPeriodicBehaviour(PeriodicBehaviour):
        async def run(self):
            pass

    behaviour = TestPeriodicBehaviour(1)
    assert behaviour.period == datetime.timedelta(seconds=1)

    behaviour.period = 2
    assert behaviour.period == datetime.timedelta(seconds=2)


def test_periodic_start_at():
    class TestPeriodicBehaviour(PeriodicBehaviour):
        async def run(self):
            self.agent.delay = datetime.datetime.now()
            self.kill()

    agent = MockedAgentFactory()
    future = agent.start(auto_register=False)
    future.result()

    start_at = datetime.datetime.now() + datetime.timedelta(seconds=0.01)
    behaviour = TestPeriodicBehaviour(period=0.01, start_at=start_at)

    assert behaviour._next_activation == start_at

    agent.add_behaviour(behaviour)

    behaviour.join()

    assert agent.delay >= start_at

    agent.stop()


def test_timeout_behaviour():

    class TestTimeoutBehaviour(TimeoutBehaviour):