import sys
import socket
import linecache
import inspect

import greenlet

from eventlet import greenlib, tls

__all__ = [
    'use_hub', 'get_hub', 'sleep', 'spawn', 'kill',
    'call_after', 'trampoline', 'tcp_listener', 'tcp_server',
]


class TimeoutError(Exception):
    pass

_threadlocal = tls.local()

def tcp_listener(address):
    """
    Listen on the given (ip, port) address with a TCP socket.
    Returns a socket object which one should call accept() on to
    accept a connection on the newly bound socket.

    Generally, the returned socket will be passed to tcp_server,
    which accepts connections forever and spawns greenlets for
    each incoming connection.
    """
    from eventlet import wrappedfd, util
    socket = wrappedfd.wrapped_fd(util.tcp_socket())
    util.socket_bind_and_listen(socket, address)
    return socket

def ssl_listener(address, certificate, private_key):
    """Listen on the given (ip, port) address with a TCP socket that
    can do SSL.

    Returns a socket object which one should call accept() on to
    accept a connection on the newly bound socket.

    Generally, the returned socket will be passed to tcp_server,
    which accepts connections forever and spawns greenlets for
    each incoming connection.
    """
    from eventlet import util
    socket = util.wrap_ssl(util.tcp_socket(), certificate, private_key)
    util.socket_bind_and_listen(socket, address)

    return socket

def connect_tcp(address):
    """
    Create a TCP connection to address (host, port) and return the socket.
    """
    from eventlet import wrappedfd, util
    desc = wrappedfd.wrapped_fd(util.tcp_socket())
    desc.connect(address)
    return desc

def tcp_server(listensocket, server, *args, **kw):
    """
    Given a socket, accept connections forever, spawning greenlets
    and executing "server" for each new incoming connection.
    When listensocket is closed, the tcp_server greenlet will end.

    listensocket:
        The socket to accept connections from.

    server:
        The callable to call when a new connection is made.

    *args:
        The arguments to pass to the call to server.

    **kw:
        The keyword arguments to pass to the call to server.
    """
    try:
        try:
            while True:
                spawn(server, listensocket.accept(), *args, **kw)
        except socket.error, e:
            # Broken pipe means it was shutdown
            if e[0] != 32:
                raise
    finally:
        listensocket.close()

def trampoline(fd, read=None, write=None, timeout=None):
    t = None
    hub = get_hub()
    self = greenlet.getcurrent()
    fileno = getattr(fd, 'fileno', lambda: fd)()
    def _do_close(fn):
        hub.remove_descriptor(fn)
        greenlib.switch(self, exc=socket.error(32, 'Broken pipe'))
    def _do_timeout(fn):
        hub.remove_descriptor(fn)
        greenlib.switch(self, exc=TimeoutError())
    def cb(_fileno):
        if t is not None:
            t.cancel()
        hub.remove_descriptor(fileno)
        greenlib.switch(self, fd)
    if timeout is not None:
        t = hub.schedule_call(timeout, _do_timeout)
    hub.add_descriptor(fileno, read and cb, write and cb, _do_close)
    return hub.switch()

def _spawn_startup(cb, args, kw, cancel=None):
    try:
        greenlib.switch(greenlet.getcurrent().parent)
        cancel = None
    finally:
        if cancel is not None:
            cancel()
    return cb(*args, **kw)

def _spawn(g):
    g.parent = greenlet.getcurrent()
    greenlib.switch(g)

def spawn(cb, *args, **kw):
    # killable
    t = None
    g = greenlib.tracked_greenlet()
    t = get_hub().schedule_call(0, _spawn, g)
    greenlib.switch(g, (_spawn_startup, cb, args, kw, t.cancel))
    return g

kill = greenlib.kill

def call_after(seconds, cb, *args, **kw):
    # cancellable
    def startup():
        g = greenlib.tracked_greenlet()
        greenlib.switch(g, (_spawn_startup, cb, args, kw))
        greenlib.switch(g)
    return get_hub().schedule_call(seconds, startup)

def get_default_hub():
    try:
        import eventlet.kqueuehub
    except ImportError:
        pass
    else:
        return eventlet.kqueuehub
    import select
    if hasattr(select, 'poll'):
        import eventlet.pollhub
        return eventlet.pollhub
    else:
        import eventlet.selecthub
        return eventlet.selecthub

def use_hub(mod=None):
    if mod is None:
        mod = get_default_hub()
    if hasattr(_threadlocal, 'hub'):
        del _threadlocal.hub
    if hasattr(mod, 'Hub'):
        _threadlocal.Hub = mod.Hub
    else:
        _threadlocal.Hub = mod

def get_hub():
    try:
        hub = _threadlocal.hub
    except AttributeError:
        try:
            _threadlocal.Hub
        except AttributeError:
            use_hub()
        hub = _threadlocal.hub = _threadlocal.Hub()
    return hub

def sleep(timeout=0):
    hub = get_hub()
    hub.schedule_call(timeout, greenlib.switch, greenlet.getcurrent())
    hub.switch()


def traceit(frame, event, arg):
    if event == 'line':
        lineno = frame.f_lineno
        if '__file__' in frame.f_globals:
            filename = frame.f_globals['__file__']
            if (filename.endswith('.pyc') or
                filename.endswith('.pyo')):
                filename = filename[:-1]
            name = frame.f_globals['__name__']
            line = linecache.getline(filename, lineno)
        else:
            name = '[unknown]'
            try:
                src = inspect.getsourcelines(frame)
                line = src[lineno]
            except IOError:
                line = 'Unknown code named [%s].  VM instruction #%d' % (
                    frame.f_code.co_name, frame.f_lasti)
        print '%s:%s: %s' % (name, lineno, line.rstrip())
    return traceit


def spew():
    sys.settrace(traceit)


def unspew():
    sys.settrace(None)

