#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;
}


static void
Task_ctor_(
    Task *tsk,
    Coroutine *cor
){
    assert(Coroutine_NS(IsStarted)());
    Future_ctor(&tsk->base);
    tsk->base.vfptrs = &Task_vfptrs;
    tsk->cor = cor;
    tsk->cor_owned = false;
    tsk->awaiting_future = NULL;
    tsk->canceled = false;
    tsk->cancel_value = NULL;
    if (current_task){
        Coroutine_NS(Continue)(tsk->cor, tsk, false);
    }
}


void
Task_ctor(
    Task *tsk,
    size_t min_stack,
    size_t min_stack_headroom,
    Task_Entry entry,
    void *param
){
    assert(Coroutine_NS(IsStarted)());
    Task_ctor_(tsk, Coroutine_NS(New)(min_stack, min_stack_headroom, Task_entry));
    tsk->cor_owned = true;
    tsk->entry = entry;
    tsk->param = param;
    if (current_task){
        Coroutine_NS(Continue)(tsk->cor, tsk, false);
    }
}


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


Task *
Task_New(
    size_t min_stack,
    size_t min_stack_headroom,
    Task_Entry entry,
    void *param
){
    Task *tsk = malloc(sizeof(Task));
    Task_ctor(tsk, min_stack, min_stack_headroom, 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 min_stack;
    size_t min_stack_headroom;
    Task_Entry start;
    void *value;
    void **res;
};

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

    Task roottsk;
    Task_ctor_(&roottsk, root);
    current_task = &roottsk;
    Task childtask;
    Task_ctor(&childtask, params->min_stack, params->min_stack_headroom, params->start, params->value);
    Coroutine_Err err = Task_Await(&childtask, params->res);
    Task_dtor(&childtask);
    current_task = NULL;
    Task_dtor(&roottsk);
    return err;
}

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