# Path: txtorcon/interface.py
# class IRouterContainer(Interface):
# 
#     unique_routers = Attribute("contains a list of all the Router instances")
# 
#     def router_from_id(routerid):
#         """
#         Note that this method MUST always return a Router instance --
#         if you ask for a router ID that didn't yet exist, it is
#         created (although without IP addresses and such because it
#         wasn't in the consensus). You may find out if a Router came
#         from the 'GETINFO ns/all' list by checking the from_consensus
#         attribute. This is to simplify code like in Circuit.update()
#         that needs to handle the case where an EXTENDED circuit event
#         is the only time we've seen a Router -- it's possible for Tor
#         to do things with routers not in the consensus (like extend
#         circuits to them).
# 
#         :return: a router by its ID.
#         """
# 
# class IStreamAttacher(Interface):
#     """
#     Used by :class:`txtorcon.TorState` to map streams to circuits (see
#     :meth:`txtorcon.TorState.set_attacher`).
# 
#     Each time a new :class:`txtorcon.Stream` is created, this
#     interface will be queried to find out which
#     :class:`txtorcon.Circuit` it should be attached to.
# 
#     Only advanced use-cases should need to use this directly; for most
#     users, using the :func:`txtorcon.Circuit.stream_via` interface
#     should be preferred.
#     """
# 
#     def attach_stream_failure(stream, fail):
#         """
#         :param stream:
#             The stream we were trying to attach.
# 
#         :param fail:
#             A Failure instance.
# 
#         A failure has occurred while trying to attach the stream.
#         """
# 
#     def attach_stream(stream, circuits):
#         """
#         :param stream:
#             The stream to attach, which will be in NEW or NEWRESOLVE
#             state.
# 
#         :param circuits:
#             all currently available :class:`txtorcon.Circuit` objects
#             in the :class:`txtorcon.TorState` in a dict indexed by id.
#             Note they are *not* limited to BUILT circuits.
# 
#         You should return a :class:`txtorcon.Circuit` instance which
#         should be at state BUILT in the currently running Tor. You may
#         also return a Deferred which will callback with the desired
#         circuit. In this case, you will probably need to be aware that
#         the callback from :meth:`txtorcon.TorState.build_circuit` does
#         not wait for the circuit to be in BUILT state.
# 
#         Alternatively, you may return None in which case the Tor
#         controller will be told to choose a circuit itself.
# 
#         Note that Tor will refuse to attach to any circuit not in
#         BUILT state; see ATTACHSTREAM in control-spec.txt
# 
#         Note also that although you get a request to attach a stream
#         that ends in .onion Tor doesn't currently let you specify how
#         to attach .onion addresses and will always give a 551 error.
#         """
# 
# Path: txtorcon/util.py
# def find_keywords(args, key_filter=lambda x: not x.startswith("$")):
#     """
#     This splits up strings like name=value, foo=bar into a dict. Does NOT deal
#     with quotes in value (e.g. key="value with space" will not work
# 
#     By default, note that it takes OUT any key which starts with $ (i.e. a
#     single dollar sign) since for many use-cases the way Tor encodes nodes
#     with "$hash=name" looks like a keyword argument (but it isn't). If you
#     don't want this, override the "key_filter" argument to this method.
# 
#     :param args: a list of strings, each with one key=value pair
# 
#     :return:
#         a dict of key->value (both strings) of all name=value type
#         keywords found in args.
#     """
#     filtered = [x for x in args if '=' in x and key_filter(x.split('=')[0])]
#     return dict(x.split('=', 1) for x in filtered)
# 
# def maybe_ip_addr(addr):
#     """
#     Tries to return an IPAddress, otherwise returns a string.
# 
#     TODO consider explicitly checking for .exit or .onion at the end?
#     """
# 
#     if six.PY2 and isinstance(addr, str):
#         addr = unicode(addr)  # noqa
#     try:
#         return ipaddress.ip_address(addr)
#     except ValueError:
#         pass
#     return str(addr)
# 
# class SingleObserver(object):
#     """
#     A helper for ".when_*()" sort of functions.
#     """
#     _NotFired = object()
# 
#     def __init__(self):
#         self._observers = []
#         self._fired = self._NotFired
# 
#     def when_fired(self):
#         d = defer.Deferred()
#         if self._fired is not self._NotFired:
#             d.callback(self._fired)
#         else:
#             self._observers.append(d)
#         return d
# 
#     def fire(self, value):
#         if self._observers is None:
#             return  # raise RuntimeError("already fired") ?
#         self._fired = value
#         for d in self._observers:
#             d.callback(self._fired)
#         self._observers = None
#         return value  # so we're transparent if used as a callback


import six
import time
from datetime import datetime
from twisted.python.failure import Failure
from twisted.python import log
from twisted.internet import defer
from twisted.internet.interfaces import IStreamClientEndpoint
from zope.interface import implementer
from .interface import IRouterContainer, IStreamAttacher
from txtorcon.util import find_keywords, maybe_ip_addr, SingleObserver
        from txtorcon import web
        from .endpoints import TorClientEndpoint
    :ivar id: The ID of this circuit, a number (or None if unset).

    """

    def __init__(self, routercontainer):
        """
        :param routercontainer:
            should implement :class:`txtorcon.interface.IRouterContainer`.
        """
        self.listeners = []
        self.router_container = IRouterContainer(routercontainer)
        self._torstate = routercontainer  # XXX FIXME
        self.path = []
        self.streams = []
        self.purpose = None
        self.id = None
        self.state = 'UNKNOWN'
        self.build_flags = []
        self.flags = {}

        # this is used to hold a Deferred that will callback() when
        # this circuit is being CLOSED or FAILED.
        self._closing_deferred = None
        # XXX ^ should probably be when_closed() etc etc...

        # caches parsed value for time_created()
        self._time_created = None

        # all notifications for when_built, when_closed
        self._when_built = SingleObserver()

# Path: txtorcon/interface.py
# class IRouterContainer(Interface):
# 
#     unique_routers = Attribute("contains a list of all the Router instances")
# 
#     def router_from_id(routerid):
#         """
#         Note that this method MUST always return a Router instance --
#         if you ask for a router ID that didn't yet exist, it is
#         created (although without IP addresses and such because it
#         wasn't in the consensus). You may find out if a Router came
#         from the 'GETINFO ns/all' list by checking the from_consensus
#         attribute. This is to simplify code like in Circuit.update()
#         that needs to handle the case where an EXTENDED circuit event
#         is the only time we've seen a Router -- it's possible for Tor
#         to do things with routers not in the consensus (like extend
#         circuits to them).
# 
#         :return: a router by its ID.
#         """
# 
# class IStreamAttacher(Interface):
#     """
#     Used by :class:`txtorcon.TorState` to map streams to circuits (see
#     :meth:`txtorcon.TorState.set_attacher`).
# 
#     Each time a new :class:`txtorcon.Stream` is created, this
#     interface will be queried to find out which
#     :class:`txtorcon.Circuit` it should be attached to.
# 
#     Only advanced use-cases should need to use this directly; for most
#     users, using the :func:`txtorcon.Circuit.stream_via` interface
#     should be preferred.
#     """
# 
#     def attach_stream_failure(stream, fail):
#         """
#         :param stream:
#             The stream we were trying to attach.
# 
#         :param fail:
#             A Failure instance.
# 
#         A failure has occurred while trying to attach the stream.
#         """
# 
#     def attach_stream(stream, circuits):
#         """
#         :param stream:
#             The stream to attach, which will be in NEW or NEWRESOLVE
#             state.
# 
#         :param circuits:
#             all currently available :class:`txtorcon.Circuit` objects
#             in the :class:`txtorcon.TorState` in a dict indexed by id.
#             Note they are *not* limited to BUILT circuits.
# 
#         You should return a :class:`txtorcon.Circuit` instance which
#         should be at state BUILT in the currently running Tor. You may
#         also return a Deferred which will callback with the desired
#         circuit. In this case, you will probably need to be aware that
#         the callback from :meth:`txtorcon.TorState.build_circuit` does
#         not wait for the circuit to be in BUILT state.
# 
#         Alternatively, you may return None in which case the Tor
#         controller will be told to choose a circuit itself.
# 
#         Note that Tor will refuse to attach to any circuit not in
#         BUILT state; see ATTACHSTREAM in control-spec.txt
# 
#         Note also that although you get a request to attach a stream
#         that ends in .onion Tor doesn't currently let you specify how
#         to attach .onion addresses and will always give a 551 error.
#         """
# 
# Path: txtorcon/util.py
# def find_keywords(args, key_filter=lambda x: not x.startswith("$")):
#     """
#     This splits up strings like name=value, foo=bar into a dict. Does NOT deal
#     with quotes in value (e.g. key="value with space" will not work
# 
#     By default, note that it takes OUT any key which starts with $ (i.e. a
#     single dollar sign) since for many use-cases the way Tor encodes nodes
#     with "$hash=name" looks like a keyword argument (but it isn't). If you
#     don't want this, override the "key_filter" argument to this method.
# 
#     :param args: a list of strings, each with one key=value pair
# 
#     :return:
#         a dict of key->value (both strings) of all name=value type
#         keywords found in args.
#     """
#     filtered = [x for x in args if '=' in x and key_filter(x.split('=')[0])]
#     return dict(x.split('=', 1) for x in filtered)
# 
# def maybe_ip_addr(addr):
#     """
#     Tries to return an IPAddress, otherwise returns a string.
# 
#     TODO consider explicitly checking for .exit or .onion at the end?
#     """
# 
#     if six.PY2 and isinstance(addr, str):
#         addr = unicode(addr)  # noqa
#     try:
#         return ipaddress.ip_address(addr)
#     except ValueError:
#         pass
#     return str(addr)
# 
# class SingleObserver(object):
#     """
#     A helper for ".when_*()" sort of functions.
#     """
#     _NotFired = object()
# 
#     def __init__(self):
#         self._observers = []
#         self._fired = self._NotFired
# 
#     def when_fired(self):
#         d = defer.Deferred()
#         if self._fired is not self._NotFired:
#             d.callback(self._fired)
#         else:
#             self._observers.append(d)
#         return d
# 
#     def fire(self, value):
#         if self._observers is None:
#             return  # raise RuntimeError("already fired") ?
#         self._fired = value
#         for d in self._observers:
#             d.callback(self._fired)
#         self._observers = None
#         return value  # so we're transparent if used as a callback


# Path: txtorcon/circuit.py
import six
import time
from datetime import datetime
from twisted.python.failure import Failure
from twisted.python import log
from twisted.internet import defer
from twisted.internet.interfaces import IStreamClientEndpoint
from zope.interface import implementer
from .interface import IRouterContainer, IStreamAttacher
from txtorcon.util import find_keywords, maybe_ip_addr, SingleObserver
        from txtorcon import web
        from .endpoints import TorClientEndpoint

    :ivar id: The ID of this circuit, a number (or None if unset).

    """

    def __init__(self, routercontainer):
        """
        :param routercontainer:
            should implement :class:`txtorcon.interface.IRouterContainer`.
        """
        self.listeners = []
        self.router_container = IRouterContainer(routercontainer)
        self._torstate = routercontainer  # XXX FIXME
        self.path = []
        self.streams = []
        self.purpose = None
        self.id = None
        self.state = 'UNKNOWN'
        self.build_flags = []
        self.flags = {}

        # this is used to hold a Deferred that will callback() when
        # this circuit is being CLOSED or FAILED.
        self._closing_deferred = None
        # XXX ^ should probably be when_closed() etc etc...

        # caches parsed value for time_created()
        self._time_created = None

        # all notifications for when_built, when_closed
        self._when_built = SingleObserver()

        self._when_closed = SingleObserver()