#include "task.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <math.h>
#include <string.h>
#include "timespec_utils.h"


_Cor_thread_local Task *current_task;


static void _Task_SetResult(
    Future *fut,
    bool canceled,
    void *res
){
    (void)fut;
    (void)canceled;
    (void)res;
    // only the task can set it's future's result
    assert(false);
}


Future_vfptrs_t Task_vfptrs = {
    (void(*)(Future *fut))&Task_dtor,
    &_Future_Await,
    &_Task_SetResult,
};


static void *Task_entry(void *me){
    Task *tsk = (Task *)me;
    void *res = NULL;
    assert(current_task == NULL);
    current_task = tsk;
    bool canceled = tsk->entry(tsk->param, &res);
    current_task = NULL;
    _Future_SetResult(&tsk->base, canceled, res);
    return res;
}


void Task_ctor(
    Task *tsk,
    size_t stack_size,
    Task_Entry entry,
    void *param
){
    assert(Coroutine_NS(IsStarted)());
    Future_ctor(&tsk->base);
    tsk->base.vfptrs = &Task_vfptrs;
    tsk->entry = entry;
    tsk->param = param;
    tsk->cor = Coroutine_NS(New)(stack_size, Task_entry);
    tsk->awaiting_future = NULL;
    tsk->canceled = false;
    tsk->cancel_value = NULL;
    if (current_task){
        Coroutine_NS(Continue)(tsk->cor, tsk, false);
    }
}


void Task_dtor(
    Task *tsk
){
    Coroutine_NS(Delete)(tsk->cor);
    Future_dtor(&tsk->base);
}


Task *Task_New(
    size_t stack_size,
    Task_Entry entry,
    void *param
){
    Task *tsk = malloc(sizeof(Task));
    Task_ctor(tsk, stack_size, entry, param);
    return tsk;
}


void Task_Delete(
    Task *tsk
){
    Future_Delete(&tsk->base);
}


void Task_Cancel(
    Task *tsk,
    void *cancel_value
){
    if (!tsk->canceled){
        tsk->canceled = true;
        tsk->cancel_value = cancel_value;
        Future *awaiting_future = tsk->awaiting_future;
        if (awaiting_future){
            Future_SetResult(awaiting_future, true, NULL);
        }
    }
}


struct Task_Run_Params {
    size_t stack_size;
    Task_Entry start;
    void *value;
    void **res;
};

static Coroutine_Err Task_Runner(void *_params)
{
    struct Task_Run_Params *params = (struct Task_Run_Params *)_params;

    Task tsk;
    Task_ctor(&tsk, params->stack_size, params->start, params->value);
    Coroutine_Err err = Coroutine_NS(Run_Coroutine)(tsk.cor, &tsk);
    if (!err){
        err = Future_GetResult(&tsk.base, params->res);
    }
    Task_dtor(&tsk);
    return err;
}

bool Task_Run(size_t stack_size, Task_Entry start, void *value, void **res){
    if (current_task){
        return Coroutine_Err_SystemRunning;
    }
    struct Task_Run_Params params = {stack_size, start, value, res};
    return Coroutine_NS(RunSystem)(Task_Runner, &params);
}
