from pypy.interpreter.error import OperationError
from pypy.interpreter.typedef import TypeDef
from pypy.interpreter.gateway import interp2app, ObjSpace, W_Root
from pypy.module._stackless.interp_coroutine import AppCoroutine, AppCoState
from pypy.module._stackless.interp_coroutine import makeStaticMethod
from pypy.module._stackless.rcoroutine import AbstractThunk
from pypy.module._stackless.rclonable import InterpClonableMixin


class AppClonableCoroutine(AppCoroutine, InterpClonableMixin):

    def newsubctx(self):
        self.hello_local_pool()
        AppCoroutine.newsubctx(self)
        self.goodbye_local_pool()

    def hello(self):
        self.hello_local_pool()
        AppCoroutine.hello(self)

    def goodbye(self):
        AppCoroutine.goodbye(self)
        self.goodbye_local_pool()

    def descr_method__new__(space, w_subtype):
        co = space.allocate_instance(AppClonableCoroutine, w_subtype)
        costate = AppClonableCoroutine._get_state(space)
        AppClonableCoroutine.__init__(co, space, state=costate)
        return space.wrap(co)

    def _get_state(space):
        return space.fromcache(AppClonableCoState)
    _get_state = staticmethod(_get_state)

    def w_getcurrent(space):
        return space.wrap(AppClonableCoroutine._get_state(space).current)
    w_getcurrent = staticmethod(w_getcurrent)

    def w_clone(self):
        space = self.space
        costate = self.costate
        if costate.current is self:
            raise OperationError(space.w_RuntimeError,
                                 space.wrap("clone() cannot clone the "
                                            "current coroutine"
                                            "; use fork() instead"))
        copy = AppClonableCoroutine(space, state=costate)
        copy.subctx = self.clone_into(copy, self.subctx)
        return space.wrap(copy)

    def descr__reduce__(self, space):
        raise OperationError(space.w_TypeError,
                             space.wrap("_stackless.clonable instances are "
                                        "not picklable"))


AppClonableCoroutine.typedef = TypeDef("clonable", AppCoroutine.typedef,
    __new__    = interp2app(AppClonableCoroutine.descr_method__new__.im_func),
    getcurrent = interp2app(AppClonableCoroutine.w_getcurrent),
    clone      = interp2app(AppClonableCoroutine.w_clone),
    __reduce__ = interp2app(AppClonableCoroutine.descr__reduce__,
                            unwrap_spec=['self', ObjSpace]),
)

class AppClonableCoState(AppCoState):
    def post_install(self):
        self.current = self.main = AppClonableCoroutine(self.space, state=self)
        self.main.subctx.clear_framestack()      # wack

def post_install(module):
    makeStaticMethod(module, 'clonable', 'getcurrent')
    space = module.space
    AppClonableCoroutine._get_state(space).post_install()

# ____________________________________________________________

class ForkThunk(AbstractThunk):
    def __init__(self, coroutine):
        self.coroutine = coroutine
        self.newcoroutine = None
    def call(self):
        oldcoro = self.coroutine
        self.coroutine = None
        newcoro = AppClonableCoroutine(oldcoro.space, state=oldcoro.costate)
        newcoro.subctx = oldcoro.clone_into(newcoro, oldcoro.subctx)
        newcoro.parent = oldcoro
        self.newcoroutine = newcoro

def fork(space):
    """Fork, as in the Unix fork(): the call returns twice, and the return
    value of the call is either the new 'child' coroutine object (if returning
    into the parent), or None (if returning into the child).  This returns
    into the parent first, which can switch to the child later.
    """
    costate = AppClonableCoroutine._get_state(space)
    current = costate.current
    if current is costate.main:
        raise OperationError(space.w_RuntimeError,
                             space.wrap("cannot fork() in the main "
                                        "clonable coroutine"))
    thunk = ForkThunk(current)
    coro_fork = AppClonableCoroutine(space, state=costate)
    coro_fork.bind(thunk)
    coro_fork.switch()
    # we resume here twice.  The following would need explanations about
    # why it returns the correct thing in both the parent and the child...
    return space.wrap(thunk.newcoroutine)
fork.unwrap_spec = [ObjSpace]

