"""Implements the standard threading module, using greenthreads."""
import eventlet
from eventlet.green import thread
from eventlet.green import time
from eventlet.support import greenlets as greenlet
import six

__patched__ = ['_start_new_thread', '_allocate_lock',
               '_sleep', 'local', 'stack_size', 'Lock', 'currentThread',
               'current_thread', '_after_fork', '_shutdown']

if six.PY2:
    __patched__ += ['_get_ident']
else:
    __patched__ += ['get_ident', '_set_sentinel']

__orig_threading = eventlet.patcher.original('threading')
__threadlocal = __orig_threading.local()
__patched_enumerate = None


eventlet.patcher.inject(
    'threading',
    globals(),
    ('thread' if six.PY2 else '_thread', thread),
    ('time', time))


_count = 1


class _GreenThread(object):
    """Wrapper for GreenThread objects to provide Thread-like attributes
    and methods"""

    def __init__(self, g):
        global _count
        self._g = g
        self._name = 'GreenThread-%d' % _count
        _count += 1

    def __repr__(self):
        return '<_GreenThread(%s, %r)>' % (self._name, self._g)

    def join(self, timeout=None):
        return self._g.wait()

    def getName(self):
        return self._name
    get_name = getName

    def setName(self, name):
        self._name = str(name)
    set_name = setName

    name = property(getName, setName)

    ident = property(lambda self: id(self._g))

    def isAlive(self):
        return True
    is_alive = isAlive

    daemon = property(lambda self: True)

    def isDaemon(self):
        return self.daemon
    is_daemon = isDaemon


__threading = None


def _fixup_thread(t):
    # Some third-party packages (lockfile) will try to patch the
    # threading.Thread class with a get_name attribute if it doesn't
    # exist. Since we might return Thread objects from the original
    # threading package that won't get patched, let's make sure each
    # individual object gets patched too our patched threading.Thread
    # class has been patched. This is why monkey patching can be bad...
    global __threading
    if not __threading:
        __threading = __import__('threading')

    if (hasattr(__threading.Thread, 'get_name') and
            not hasattr(t, 'get_name')):
        t.get_name = t.getName
    return t


def current_thread():
    global __patched_enumerate
    g = greenlet.getcurrent()
    if not g:
        # Not currently in a greenthread, fall back to standard function
        return _fixup_thread(__orig_threading.current_thread())

    try:
        active = __threadlocal.active
    except AttributeError:
        active = __threadlocal.active = {}

    g_id = id(g)
    t = active.get(g_id)
    if t is not None:
        return t

    # FIXME: move import from function body to top
    # (jaketesler@github) Furthermore, I was unable to have the current_thread() return correct results from
    # threading.enumerate() unless the enumerate() function was a) imported at runtime using the gross __import__() call
    # and b) was hot-patched using patch_function().
    # https://github.com/eventlet/eventlet/issues/172#issuecomment-379421165
    if __patched_enumerate is None:
        __patched_enumerate = eventlet.patcher.patch_function(__import__('threading').enumerate)
    found = [th for th in __patched_enumerate() if th.ident == g_id]
    if found:
        return found[0]

    # Add green thread to active if we can clean it up on exit
    def cleanup(g):
        del active[g_id]
    try:
        g.link(cleanup)
    except AttributeError:
        # Not a GreenThread type, so there's no way to hook into
        # the green thread exiting. Fall back to the standard
        # function then.
        t = _fixup_thread(__orig_threading.current_thread())
    else:
        t = active[g_id] = _GreenThread(g)

    return t

currentThread = current_thread
