"""
Implementation of the standard :mod:`threading` using greenlets.

.. note::

    This module is a helper for :mod:`gevent.monkey` and is not
    intended to be used directly. For spawning greenlets in your
    applications, prefer higher level constructs like
    :class:`gevent.Greenlet` class or :func:`gevent.spawn`.
"""
from __future__ import absolute_import


__implements__ = [
    'local',
    '_start_new_thread',
    '_allocate_lock',
    'Lock',
    '_get_ident',
    '_sleep',
    '_DummyThread',
]


import threading as __threading__
_DummyThread_ = __threading__._DummyThread
from gevent.local import local
from gevent.thread import start_new_thread as _start_new_thread, allocate_lock as _allocate_lock, get_ident as _get_ident
from gevent._compat import PYPY
from gevent.hub import sleep as _sleep, getcurrent

# Exports, prevent unused import warnings
local = local
start_new_thread = _start_new_thread
allocate_lock = _allocate_lock
_get_ident = _get_ident
_sleep = _sleep
getcurrent = getcurrent

Lock = _allocate_lock


def _cleanup(g):
    __threading__._active.pop(id(g), None)

def _make_cleanup_id(gid):
    def _(_r):
        __threading__._active.pop(gid, None)
    return _

_weakref = None

class _DummyThread(_DummyThread_):
    # We avoid calling the superclass constructor. This makes us about
    # twice as fast (1.16 vs 0.68usec on PyPy, 29.3 vs 17.7usec on
    # CPython 2.7), and has the important effect of avoiding
    # allocation and then immediate deletion of _Thread__block, a
    # lock. This is especially important on PyPy where locks go
    # through the cpyext API and Cython, which is known to be slow and
    # potentially buggy (e.g.,
    # https://bitbucket.org/pypy/pypy/issues/2149/memory-leak-for-python-subclass-of-cpyext#comment-22347393)

    # These objects are constructed quite frequently in some cases, so
    # the optimization matters: for example, in gunicorn, which uses
    # pywsgi.WSGIServer, every request is handled in a new greenlet,
    # and every request uses a logging.Logger to write the access log,
    # and every call to a log method captures the current thread (by
    # default).
    #
    # (Obviously we have to duplicate the effects of the constructor,
    # at least for external state purposes, which is potentially
    # slightly fragile.)

    # For the same reason, instances of this class will cleanup their own entry
    # in ``threading._active``

    # Capture the static things as class vars to save on memory/
    # construction time.
    # In Py2, they're all private; in Py3, they become protected
    _Thread__stopped = _is_stopped = _stopped = False
    _Thread__initialized = _initialized = True
    _Thread__daemonic = _daemonic = True
    _Thread__args = _args = ()
    _Thread__kwargs = _kwargs = None
    _Thread__target = _target = None
    _Thread_ident = _ident = None
    _Thread__started = _started = __threading__.Event()
    _Thread__started.set()
    _tstate_lock = None

    def __init__(self):
        #_DummyThread_.__init__(self) # pylint:disable=super-init-not-called

        # It'd be nice to use a pattern like "greenlet-%d", but maybe somebody out
        # there is checking thread names...
        self._name = self._Thread__name = __threading__._newname("DummyThread-%d")
        self._set_ident()

        g = getcurrent()
        gid = _get_ident(g) # same as id(g)
        __threading__._active[gid] = self
        rawlink = getattr(g, 'rawlink', None)
        if rawlink is not None:
            # raw greenlet.greenlet greenlets don't
            # have rawlink...
            rawlink(_cleanup)
        else:
            # ... so for them we use weakrefs.
            # See https://github.com/gevent/gevent/issues/918
            global _weakref
            if _weakref is None:
                _weakref = __import__('weakref')
            ref = _weakref.ref(g, _make_cleanup_id(gid))
            self.__raw_ref = ref

    def _Thread__stop(self):
        pass

    _stop = _Thread__stop # py3

    def _wait_for_tstate_lock(self, *args, **kwargs):
        # pylint:disable=arguments-differ
        pass

if hasattr(__threading__, 'main_thread'): # py 3.4+
    def main_native_thread():
        return __threading__.main_thread() # pylint:disable=no-member
else:
    _main_threads = [(_k, _v) for _k, _v in __threading__._active.items()
                     if isinstance(_v, __threading__._MainThread)]
    assert len(_main_threads) == 1, "Too many main threads"

    def main_native_thread():
        return _main_threads[0][1]

# Make sure the MainThread can be found by our current greenlet ID,
# otherwise we get a new DummyThread, which cannot be joined.
# Fixes tests in test_threading_2 under PyPy, and generally makes things nicer
# when gevent.threading is imported before monkey patching or not at all
# XXX: This assumes that the import is happening in the "main" greenlet/thread.
# XXX: We should really only be doing this from gevent.monkey.
if _get_ident() not in __threading__._active:
    _v = main_native_thread()
    _k = _v.ident
    del __threading__._active[_k]
    _v._ident = _v._Thread__ident = _get_ident()
    __threading__._active[_get_ident()] = _v
    del _k
    del _v

    # Avoid printing an error on shutdown trying to remove the thread entry
    # we just replaced if we're not fully monkey patched in
    # XXX: This causes a hang on PyPy for some unknown reason (as soon as class _active
    # defines __delitem__, shutdown hangs. Maybe due to something with the GC?
    # XXX: This may be fixed in 2.6.1+
    if not PYPY:
        # pylint:disable=no-member
        _MAIN_THREAD = __threading__._get_ident() if hasattr(__threading__, '_get_ident') else __threading__.get_ident()

        class _active(dict):
            def __delitem__(self, k):
                if k == _MAIN_THREAD and k not in self:
                    return
                dict.__delitem__(self, k)

        __threading__._active = _active(__threading__._active)


import sys
if sys.version_info[:2] >= (3, 4):
    # XXX: Issue 18808 breaks us on Python 3.4.
    # Thread objects now expect a callback from the interpreter itself
    # (threadmodule.c:release_sentinel). Because this never happens
    # when a greenlet exits, join() and friends will block forever.
    # The solution below involves capturing the greenlet when it is
    # started and deferring the known broken methods to it.

    class Thread(__threading__.Thread):
        _greenlet = None

        def is_alive(self):
            return bool(self._greenlet)

        isAlive = is_alive

        def _set_tstate_lock(self):
            self._greenlet = getcurrent()

        def run(self):
            try:
                super(Thread, self).run()
            finally:
                # avoid ref cycles, but keep in __dict__ so we can
                # distinguish the started/never-started case
                self._greenlet = None
                self._stop() # mark as finished

        def join(self, timeout=None):
            if '_greenlet' not in self.__dict__:
                raise RuntimeError("Cannot join an inactive thread")
            if self._greenlet is None:
                return
            self._greenlet.join(timeout=timeout)

        def _wait_for_tstate_lock(self, *args, **kwargs):
            # pylint:disable=arguments-differ
            raise NotImplementedError()

    __implements__.append('Thread')

    # The main thread is patched up with more care in monkey.py
    #t = __threading__.current_thread()
    #if isinstance(t, __threading__.Thread):
    #    t.__class__ = Thread
    #    t._greenlet = getcurrent()

if sys.version_info[:2] >= (3, 3):
    __implements__.remove('_get_ident')
    __implements__.append('get_ident')
    get_ident = _get_ident
    __implements__.remove('_sleep')

    # Python 3 changed the implementation of threading.RLock
    # Previously it was a factory function around threading._RLock
    # which in turn used _allocate_lock. Now, it wants to use
    # threading._CRLock, which is imported from _thread.RLock and as such
    # is implemented in C. So it bypasses our _allocate_lock function.
    # Fortunately they left the Python fallback in place
    assert hasattr(__threading__, '_CRLock'), "Unsupported Python version"
    _CRLock = None
    __implements__.append('_CRLock')
