#ifndef CORO_API_H
#define CORO_API_H

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#ifndef pTHX_
# define pTHX_
# define aTHX_
# define pTHX
# define aTHX
#endif

/* C-level coroutine struct, opaque, not used much */
struct coro;

/* used for schedule-like-function prepares */
struct coro_transfer_args
{
  struct coro *prev, *next;
};

/* this is the per-perl-coro slf frame info
 * it is treated like other "global" interpreter data
 * and unfortunately is copied around, so keep it small
 *
 * Workflow:
 *
 * 1. init function is called to parse arguments and fill out the CoroSLF frame
 * 2. loop starts by calling frame->prepare, providing transfer arguments
 * 3. transfer is called, transsferring control to another coro
 * 4. if at this point, the coro is destroyed: frame->destroy is called, then no further processing
 * 5. otherwise: eventually control is transferred back to coro
 * 6. frame->check is called
 * 7. check returns 0 => wait not finished, loop to 2
 * 8. otherwise, check puts results on stack, returns 1 => finish
 */
struct CoroSLF
{
  void (*prepare) (pTHX_ struct coro_transfer_args *ta); /* 0 means not yet initialised */
  int (*check) (pTHX_ struct CoroSLF *frame);
  void *data; /* for use by prepare/check/destroy */
  void (*destroy) (pTHX_ struct CoroSLF *frame);
};

/* needs to fill in the *frame */
typedef void (*coro_slf_cb) (pTHX_ struct CoroSLF *frame, CV *cv, SV **arg, int items);

/* called on enter/leave */
typedef void (*coro_enterleave_hook) (pTHX_ void *arg);

/* private structure, always use the provided macros below */
struct CoroAPI
{
  /* private */
  I32 ver;
  I32 rev;
#define CORO_API_VERSION 7 /* reorder CoroSLF on change */
#define CORO_API_REVISION 2

  /* Coro */
  int nready;
  SV *current;
  SV *except;
  void (*readyhook) (void);

  void (*schedule) (pTHX);
  void (*schedule_to) (pTHX_ SV *coro_sv);
  int (*cede) (pTHX);
  int (*cede_notself) (pTHX);
  int (*ready) (pTHX_ SV *coro_sv);
  int (*is_ready) (pTHX_ SV *coro_sv);

  /* Coro::State */
  void (*transfer) (pTHX_ SV *prev_sv, SV *next_sv); /* Coro::State */

  /* SLF */
  struct coro *(*sv_state) (pTHX_ SV *coro_sv);
  void (*execute_slf) (pTHX_ CV *cv, coro_slf_cb init_cb, I32 ax);

  /* public */
  /* for use as CoroSLF.prepare */
  void (*prepare_nop)          (pTHX_ struct coro_transfer_args *ta);
  void (*prepare_schedule)     (pTHX_ struct coro_transfer_args *ta);
  void (*prepare_cede)         (pTHX_ struct coro_transfer_args *ta);
  void (*prepare_cede_notself) (pTHX_ struct coro_transfer_args *ta);

  /* private */
  void (*enterleave_hook)(pTHX_ SV *coro_sv, coro_enterleave_hook enter, void *enter_arg, coro_enterleave_hook leave, void *leave_arg);
  void (*enterleave_unhook)(pTHX_ SV *coro_sv, coro_enterleave_hook enter, coro_enterleave_hook leave);
  void (*enterleave_scope_hook)(pTHX_ coro_enterleave_hook enter, void *enter_arg, coro_enterleave_hook leave, void *leave_arg); /* XS caller must LEAVE/ENTER */
};

static struct CoroAPI *GCoroAPI;

/* public API macros */
#define CORO_TRANSFER(prev,next) GCoroAPI->transfer (aTHX_ (prev), (next))

#define CORO_SV_STATE(coro)      GCoroAPI->sv_state (aTHX_ (coro))
#define CORO_EXECUTE_SLF(cv,init,ax) GCoroAPI->execute_slf (aTHX_ (cv), (init), (ax))
#define CORO_EXECUTE_SLF_XS(init) CORO_EXECUTE_SLF (cv, (init), ax)

#define CORO_SCHEDULE            GCoroAPI->schedule (aTHX)
#define CORO_CEDE                GCoroAPI->cede (aTHX)
#define CORO_CEDE_NOTSELF        GCoroAPI->cede_notself (aTHX)
#define CORO_READY(coro)         GCoroAPI->ready (aTHX_ coro)
#define CORO_IS_READY(coro)      GCoroAPI->is_ready (coro)
#define CORO_NREADY              (GCoroAPI->nready)
#define CORO_THROW               (GCoroAPI->except)
#define CORO_CURRENT             SvRV (GCoroAPI->current)
#define CORO_READYHOOK           (GCoroAPI->readyhook)

#define CORO_ENTERLEAVE_HOOK(coro,enter,enter_arg,leave,leave_arg)   GCoroAPI->enterleave_hook (aTHX_ coro, enter, enter_arg, leave, leave_arg)
#define CORO_ENTERLEAVE_UNHOOK(coro,enter,leave)                     GCoroAPI->enterleave_hook (aTHX_ coro, enter           , leave           )
#define CORO_ENTERLEAVE_SCOPE_HOOK(enter,enter_arg,leave,leave_arg)  GCoroAPI->enterleave_scope_hook (aTHX_ enter, enter_arg, leave, leave_arg)

#define I_CORO_API(YourName)                                                                       \
STMT_START {                                                                                       \
  SV *sv = perl_get_sv ("Coro::API", 0);                                                           \
  if (!sv) croak ("Coro::API not found");                                                          \
  GCoroAPI = (struct CoroAPI*) SvIV (sv);                                                          \
  if (GCoroAPI->ver != CORO_API_VERSION                                                            \
      || GCoroAPI->rev < CORO_API_REVISION)                                                        \
    croak ("Coro::API version mismatch (%d.%d vs. %d.%d) -- please recompile %s",                  \
           (int)GCoroAPI->ver, (int)GCoroAPI->rev, CORO_API_VERSION, CORO_API_REVISION, YourName); \
} STMT_END

#endif

