import thread
from pypy.interpreter.error import OperationError
from pypy.interpreter.baseobjspace import Wrappable
from pypy.interpreter.typedef import TypeDef, interp2app
from pypy.interpreter.typedef import GetSetProperty, descr_get_dict
from pypy.interpreter.typedef import descr_set_dict
from pypy.interpreter.gateway import ObjSpace, W_Root, Arguments

# Force the declaration of thread.start_new_thread() & co. for RPython
import pypy.module.thread.rpython.exttable


class Local(Wrappable):
    """Thread-local data"""

    def __init__(self, space, initargs):
        self.space = space
        self.initargs = initargs.normalize()
        ident = thread.get_ident()
        self.dicts = {ident: space.newdict()}

    def getdict(self):
        ident = thread.get_ident()
        try:
            w_dict = self.dicts[ident]
        except KeyError:
            # create a new dict for this thread
            space = self.space
            w_dict = self.dicts[ident] = space.newdict()
            # call __init__
            try:
                w_self = space.wrap(self)
                w_type = space.type(w_self)
                w_init = space.getattr(w_type, space.wrap("__init__"))
                space.call_args(w_init, self.initargs.prepend(w_self))
            except:
                # failed, forget w_dict and propagate the exception
                del self.dicts[ident]
                raise
            # ready
            space.threadlocals.atthreadexit(space, finish_thread, self)
        return w_dict

    def setdict(self, space, w_dict):
        if not space.is_true(space.isinstance(w_dict, space.w_dict)):
            raise OperationError(space.w_TypeError,
                                space.wrap("setting dictionary to a non-dict"))
        self.getdict()   # force a dict to exist first
        ident = thread.get_ident()
        self.dicts[ident] = w_dict

    def descr_local__new__(space, w_subtype, __args__):
        # XXX check __args__
        local = space.allocate_instance(Local, w_subtype)
        Local.__init__(local, space, __args__)
        return space.wrap(local)

Local.typedef = TypeDef("thread._local",
                        __doc__ = "Thread-local data",
                        __new__ = interp2app(Local.descr_local__new__.im_func,
                                    unwrap_spec=[ObjSpace, W_Root, Arguments]),
                        __dict__ = GetSetProperty(descr_get_dict,
                                                  descr_set_dict, cls=Local),
                        )

def getlocaltype(space):
    return space.gettypeobject(Local.typedef)


def finish_thread(w_obj):
    assert isinstance(w_obj, Local)
    ident = thread.get_ident()
    del w_obj.dicts[ident]
