#ifndef COROUTINE_H
#define COROUTINE_H

#include <stdbool.h>
#include <stdint.h>

///////////////////////////////////////////////////////////////////////////////
// Coroutine
//
// Coroutines for C, based on setjmp/longjmp.
// Thread safe - each thread has its own coroutine system
// Coroutines are cooperatively scheduled
// Coroutines have their own stack (currently 16K each)
// A coroutine can be continued, queried, or deleted on a different thread.
//
// Usage:
//   Coroutine_StartSystem();  // call once per thread before using coroutines
//   Coroutine *co = Coroutine_New(start_function);
//   void *result = Coroutine_Run(co, initial_value);
//   Coroutine_Delete(co);
//   Coroutine_StopSystem();   // call once per thread when done with coroutines
//
// Inside the coroutine function:
//   void *value = Coroutine_Yield(yield_value, on_yield, this);
//   ...
//   return return_value;
//
// To create a coroutine:
//   Coroutine *co = Coroutine_New(start_function);
// To start or continue a coroutine:
//   void *result = Coroutine_Continue(co, value, early);
//   // early=true puts the coroutine at the head of the run queue
//   // early=false puts the coroutine at the tail of the run queue
// To yield from inside a coroutine:
//   void *value = Coroutine_Yield(yield_value, on_yield, this);
//   // on_yield is called before the next coroutine is run
//   // 'this' is passed to on_yield as its parameter
//   // value is the value passed to Coroutine_Continue
// To delete a coroutine:
//   Coroutine_Delete(co);
// To get the value yielded from, or returned by a corotuine:
//   void *value = Coroutine_GetValue(co);
// To get the currently running coroutine (NULL if none):
//   Coroutine *co = Coroutine_GetActive();
// To check if a coroutine is currently running:
//   bool running = Coroutine_IsRunning(co);
//
// Notes:
// Coroutine is not expected to be used directly, but as a foundation for
// higher level constructs such as Generators, Async, etc.
//
///////////////////////////////////////////////////////////////////////////////


// The stack is used as follows:
//
// Note: the stack is assumed to grow downwards through memory
//
// (low memory)
//
//   <- limit. When set this and lower memory addresses are assumed to be unavailable for stack use
//   (- lowest address usable by the stack -)
//   .                  .
//   .                  .
//   | coroutine stack  |
//   +------------------+
//   | coroutine header |  <- 'tip' coroutine (latest allocated Coroutine)
//   +------------------+
//   | coroutine stack  |
//   +------------------+
//   | coroutine header |  <- 'active' Coroutine (the one currently running - could be 'tip')
//   +------------------+
//   | coroutine stack  |
//   +------------------+
//   | coroutine header |
//   +------------------+
//   | coroutine stack  |
//   +------------------+
//   | coroutine header |
//   +------------------+
//   | startup space    |  <- set aside by Coroutine_StartSystem
//   +------------------+
//   | caller           |  <- This calls Coroutine_StartSystem etc
//   +------------------+
//   | used stack       |
//   +------------------+  <- stack 'bottom'; highest address used by the stack
// (high memory)

// Each coroutine has this much stack:
#ifndef COROUTINE_STACK_SIZE
    #define COROUTINE_STACK_SIZE 65536
#endif

// When Coroutine is started, an amount of stack is set aside to give
// the caller of Coroutine_StartSystem a bit of room to work before calling
// Coroutine_Run(), that is this amount:
#ifndef COROUTINE_STARTUP_STACK_SIZE
    #define COROUTINE_STARTUP_STACK_SIZE 4096
#endif

// When allocating space for a coroutine stack, fill it with guard pattern
// so that lowest_headroom in the Coroutine_Report can be worked out
#ifndef COROUTINE_RECORD_LOWEST_HEADROOM
    #define COROUTINE_RECORD_LOWEST_HEADROOM 1
#endif

// Returned by Coroutine_StopSystem(), this summarises the coroutine session
typedef struct Coroutine_Report {
    unsigned coroutines_created;
    unsigned coroutines_pool_size;
    unsigned lowest_headroom;
    uintptr_t stack_per_coroutine;
} Coroutine_Report;

typedef struct Coroutine Coroutine;

typedef void (*Coroutine_YieldCallback)(void *me);
typedef void *(*Coroutine_Start)(void *);

extern void Coroutine_StartSystem(void);
extern void Coroutine_SetStackLimit(void *);
extern Coroutine_Report Coroutine_StopSystem(void);
extern Coroutine *Coroutine_New(Coroutine_Start start);
extern void Coroutine_Run_Coroutine(Coroutine *cor, void *value);
extern void *Coroutine_Run(Coroutine_Start start, void *value);
extern void Coroutine_Delete(Coroutine *cor);
extern void Coroutine_Continue(Coroutine *cor, void *value, bool early);
extern void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *me);
extern void *Coroutine_GetValue(Coroutine *cor);
extern Coroutine *Coroutine_GetActive(void);
extern intptr_t Coroutine_GetStackHeadroom(void);
extern void *Coroutine_GetStackHWM(void);
extern void Coroutine_ClearStackForHWM(void);
extern bool Coroutine_CanStartCoroutine();
extern void *Coroutine_GetCStackTop(void);
extern void *Coroutine_Chain(Coroutine_Start start, void *value);
extern bool Coroutine_IsStarted(void);
extern bool Coroutine_IsRunning(Coroutine *cor);

#endif
