#include "generator.h"
#include "coroutine.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "cor_thread_local.h"


_Cor_thread_local Generator *g_active_generator = NULL;


static void on_yield_gen(
    void *this
){
    Generator *gen = (Generator *)this;
    Coroutine_Continue(gen->caller, Coroutine_GetValue(gen->coroutine), true);
}


static void on_yield_gen_caller(
    void *this
){
    (void)this;
}


static void *do_start(
    void *this
){
    Generator *gen = (Generator *)this;
    g_active_generator = this;
    assert(gen->state == Generator_Running || gen->state == Generator_Deleting);
    void *value;
    if (gen->state == Generator_Running){
        g_active_generator = gen;
        value = gen->start(gen->param);
        g_active_generator = NULL;
    } else {
        // we are being deleted
        value = NULL;
    }
    gen->state = Generator_Complete;
    Coroutine_Continue(gen->caller, value, true);
    return value;
}


void Generator_ctor(
    Generator *gen,
    void *(*start)(void *),
    void *param
){
    gen->start = start;
    gen->param = param;
    gen->coroutine = Coroutine_New(do_start);
    gen->state = Generator_Running;
}


Generator *Generator_New(
    void *(*start)(void *),
    void *param
){
    Generator *gen = malloc(sizeof(Generator));
    Generator_ctor(gen, start, param);
    return gen;
}


void Generator_dtor(
    Generator *gen
){
    assert(gen->state != Generator_Deleting);
    if (gen->state == Generator_Running){
        gen->state = Generator_Deleting;
        gen->caller = Coroutine_GetActive();
        Coroutine_Continue(gen->coroutine, gen, true);
        Coroutine_Yield(NULL, on_yield_gen_caller, gen);
    }
    assert(gen->state == Generator_Complete);
    Coroutine_Delete(gen->coroutine);
}

void Generator_Delete(
    Generator *gen
){
    Generator_dtor(gen);
    free(gen);
}


bool Generator_Next(
    Generator *gen,
    void **value
){
    assert(gen->state != Generator_Deleting);
    if (gen->state == Generator_Complete){
        return false;
    }
    gen->caller = Coroutine_GetActive();
    Coroutine_Continue(gen->coroutine, gen, true);
    *value = Coroutine_Yield(NULL, on_yield_gen_caller, gen);
    return Coroutine_IsRunning(gen->coroutine);
}


bool Generator_Yield(
    void *value
)
{
    assert(g_active_generator);
    Generator *gen = g_active_generator;
    g_active_generator = NULL;
    Coroutine_Yield(value, on_yield_gen, gen);
    g_active_generator = gen;
    return gen->state == Generator_Running;
}
