import sys
import os
from eventlet.support import greenlets as greenlet
from eventlet import patcher

try:
    # try and import pkg_resources ...
    import pkg_resources
except ImportError:
    # ... but do not depend on it
    pkg_resources = None


__all__ = ["use_hub", "get_hub", "get_default_hub", "trampoline"]

threading = patcher.original('threading')
_threadlocal = threading.local()


def get_default_hub():
    """Select the default hub implementation based on what multiplexing
    libraries are installed.  The order that the hubs are tried is:

    * epoll
    * kqueue
    * poll
    * select

    It won't automatically select the pyevent hub, because it's not
    python-thread-safe.

    .. include:: ../doc/common.txt
    .. note :: |internal|
    """

    # pyevent hub disabled for now because it is not thread-safe
    # try:
    #    import eventlet.hubs.pyevent
    #    return eventlet.hubs.pyevent
    # except:
    #    pass

    select = patcher.original('select')
    try:
        import eventlet.hubs.epolls
        return eventlet.hubs.epolls
    except ImportError:
        try:
            import eventlet.hubs.kqueue
            return eventlet.hubs.kqueue
        except ImportError:
            if hasattr(select, 'poll'):
                import eventlet.hubs.poll
                return eventlet.hubs.poll
            else:
                import eventlet.hubs.selects
                return eventlet.hubs.selects


def use_hub(mod=None):
    """Use the module *mod*, containing a class called Hub, as the
    event hub. Usually not required; the default hub is usually fine.

    Mod can be an actual module, a string, or None.  If *mod* is a module,
    it uses it directly.   If *mod* is a string and contains either '.' or ':'
    use_hub tries to import the hub using the 'package.subpackage.module:Class'
    convention, otherwise use_hub looks for a matching setuptools entry point
    in the 'eventlet.hubs' group to load or finally tries to import
    `eventlet.hubs.mod` and use that as the hub module.  If *mod* is None,
    use_hub uses the default hub.  Only call use_hub during application
    initialization,  because it resets the hub's state and any existing
    timers or listeners will never be resumed.
    """
    if mod is None:
        mod = os.environ.get('EVENTLET_HUB', None)
    if mod is None:
        mod = get_default_hub()
    if hasattr(_threadlocal, 'hub'):
        del _threadlocal.hub
    if isinstance(mod, str):
        assert mod.strip(), "Need to specify a hub"
        if '.' in mod or ':' in mod:
            modulename, _, classname = mod.strip().partition(':')
            mod = __import__(modulename, globals(), locals(), [classname])
            if classname:
                mod = getattr(mod, classname)
        else:
            found = False
            if pkg_resources is not None:
                for entry in pkg_resources.iter_entry_points(
                        group='eventlet.hubs', name=mod):
                    mod, found = entry.load(), True
                    break
            if not found:
                mod = __import__(
                    'eventlet.hubs.' + mod, globals(), locals(), ['Hub'])
    if hasattr(mod, 'Hub'):
        _threadlocal.Hub = mod.Hub
    else:
        _threadlocal.Hub = mod


def get_hub():
    """Get the current event hub singleton object.

    .. note :: |internal|
    """
    try:
        hub = _threadlocal.hub
    except AttributeError:
        try:
            _threadlocal.Hub
        except AttributeError:
            use_hub()
        hub = _threadlocal.hub = _threadlocal.Hub()
    return hub

from eventlet import timeout


def trampoline(fd, read=None, write=None, timeout=None,
               timeout_exc=timeout.Timeout):
    """Suspend the current coroutine until the given socket object or file
    descriptor is ready to *read*, ready to *write*, or the specified
    *timeout* elapses, depending on arguments specified.

    To wait for *fd* to be ready to read, pass *read* ``=True``; ready to
    write, pass *write* ``=True``. To specify a timeout, pass the *timeout*
    argument in seconds.

    If the specified *timeout* elapses before the socket is ready to read or
    write, *timeout_exc* will be raised instead of ``trampoline()``
    returning normally.

    .. note :: |internal|
    """
    t = None
    hub = get_hub()
    current = greenlet.getcurrent()
    assert hub.greenlet is not current, 'do not call blocking functions from the mainloop'
    assert not (
        read and write), 'not allowed to trampoline for reading and writing'
    try:
        fileno = fd.fileno()
    except AttributeError:
        fileno = fd
    if timeout is not None:
        t = hub.schedule_call_global(timeout, current.throw, timeout_exc)
    try:
        if read:
            listener = hub.add(hub.READ, fileno, current.switch)
        elif write:
            listener = hub.add(hub.WRITE, fileno, current.switch)
        try:
            return hub.switch()
        finally:
            hub.remove(listener)
    finally:
        if t is not None:
            t.cancel()
