#define RTE_MEM 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <inttypes.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <ctx.h>
#include <stack.h>
#include "lthread_api.h"
#include "lthread.h"
#include "lthread_timer.h"
#include "lthread_tls.h"
#include "lthread_objcache.h"
#include "lthread_diag.h"
void _lthread_exit_handler(struct lthread *lt)
{
    lt->state |= BIT(ST_LT_EXITED);
    if (!(lt->state & BIT(ST_LT_DETACH))) {
        
        lthread_exit(NULL);
    }
    
    _reschedule();
}
void _lthread_free(struct lthread *lt)
{
    DIAG_EVENT(lt, LT_DIAG_LTHREAD_FREE, lt, 0);
    
    _lthread_tls_destroy(lt);
    
    if (sizeof(void *) < (uint64_t)RTE_PER_LTHREAD_SECTION_SIZE)
        _lthread_objcache_free(lt->tls->root_sched->per_lthread_cache,
                    lt->per_lthread_data);
    
    _lthread_objcache_free(lt->tls->root_sched->tls_cache, lt->tls);
    
    _lthread_objcache_free(lt->stack_container->root_sched->stack_cache,
                lt->stack_container);
    
    _lthread_objcache_free(lt->root_sched->lthread_cache, lt);
}
struct lthread_stack *_stack_alloc(void)
{
    struct lthread_stack *s;
    s = _lthread_objcache_alloc((THIS_SCHED)->stack_cache);
    RTE_ASSERT(s != NULL);
    s->root_sched = THIS_SCHED;
    s->stack_size = LTHREAD_MAX_STACK_SIZE;
    return s;
}
static void _lthread_exec(void *arg)
{
    struct lthread *lt = (struct lthread *)arg;
    
    lt->fun(lt->arg);
    
    if (lt->exit_handler != NULL)
        lt->exit_handler(lt);
}
void
_lthread_init(struct lthread *lt,
    lthread_func_t fun, void *arg, lthread_exit_func exit_handler)
{
    
    lt->fun = fun;
    lt->arg = arg;
    lt->exit_handler = exit_handler;
    
    lt->birth = _sched_now();
    lt->state = BIT(ST_LT_INIT);
    lt->join = LT_JOIN_INITIAL;
}
void _lthread_set_stack(struct lthread *lt, void *stack, size_t stack_size)
{
    
    lt->stack = stack;
    lt->stack_size = stack_size;
    arch_set_stack(lt, _lthread_exec);
}
int
lthread_create(struct lthread **new_lt, int lcore_id,
        lthread_func_t fun, void *arg)
{
    if ((new_lt == NULL) || (fun == NULL))
        return POSIX_ERRNO(EINVAL);
    if (lcore_id < 0)
    else if (lcore_id > LTHREAD_MAX_LCORES)
        return POSIX_ERRNO(EINVAL);
    struct lthread *lt = NULL;
    if (THIS_SCHED == NULL) {
        THIS_SCHED = _lthread_sched_create(0);
        if (THIS_SCHED == NULL) {
            perror("Failed to create scheduler");
            return POSIX_ERRNO(EAGAIN);
        }
    }
    
    lt = _lthread_objcache_alloc((THIS_SCHED)->lthread_cache);
    if (lt == NULL)
        return POSIX_ERRNO(EAGAIN);
    bzero(lt, sizeof(struct lthread));
    lt->root_sched = THIS_SCHED;
    
    _lthread_init(lt, fun, arg, _lthread_exit_handler);
    
    *new_lt = lt;
    if (lcore_id < 0)
    DIAG_CREATE_EVENT(lt, LT_DIAG_LTHREAD_CREATE);
    _ready_queue_insert(_lthread_sched_get(lcore_id), lt);
    return 0;
}
static inline void _lthread_sched_sleep(struct lthread *lt, uint64_t nsecs)
{
    uint64_t state = lt->state;
    uint64_t clks = _ns_to_clks(nsecs);
    if (clks) {
        _timer_start(lt, clks);
        lt->state = state | BIT(ST_LT_SLEEPING);
    }
    DIAG_EVENT(lt, LT_DIAG_LTHREAD_SLEEP, clks, 0);
    _suspend();
}
int _lthread_desched_sleep(struct lthread *lt)
{
    uint64_t state = lt->state;
    if (state & BIT(ST_LT_SLEEPING)) {
        _timer_stop(lt);
        state &= (CLEARBIT(ST_LT_SLEEPING) & CLEARBIT(ST_LT_EXPIRED));
        lt->state = state | BIT(ST_LT_READY);
        return 1;
    }
    return 0;
}
void lthread_set_data(void *data)
{
    if (sizeof(void *) == RTE_PER_LTHREAD_SECTION_SIZE)
        THIS_LTHREAD->per_lthread_data = data;
}
void *lthread_get_data(void)
{
    return THIS_LTHREAD->per_lthread_data;
}
struct lthread *lthread_current(void)
{
    struct lthread_sched *sched = THIS_SCHED;
    if (sched)
        return sched->current_lthread;
    return NULL;
}
static void *
_cancel(void *arg)
{
    struct lthread *lt = (struct lthread *) arg;
    lt->state |= BIT(ST_LT_CANCELLED);
    lthread_detach();
    return NULL;
}
int lthread_cancel(struct lthread *cancel_lt)
{
    struct lthread *lt;
    if ((cancel_lt == NULL) || (cancel_lt == THIS_LTHREAD))
        return POSIX_ERRNO(EINVAL);
    DIAG_EVENT(cancel_lt, LT_DIAG_LTHREAD_CANCEL, cancel_lt, 0);
    if (cancel_lt->sched != THIS_SCHED) {
        
        lthread_create(<,
                cancel_lt->sched->lcore_id,
                _cancel,
                cancel_lt);
        return 0;
    }
    cancel_lt->state |= BIT(ST_LT_CANCELLED);
    return 0;
}
void lthread_sleep(uint64_t nsecs)
{
    struct lthread *lt = THIS_LTHREAD;
    _lthread_sched_sleep(lt, nsecs);
}
void lthread_sleep_clks(uint64_t clks)
{
    struct lthread *lt = THIS_LTHREAD;
    uint64_t state = lt->state;
    if (clks) {
        _timer_start(lt, clks);
        lt->state = state | BIT(ST_LT_SLEEPING);
    }
    DIAG_EVENT(lt, LT_DIAG_LTHREAD_SLEEP, clks, 0);
    _suspend();
}
void lthread_yield(void)
{
    struct lthread *lt = THIS_LTHREAD;
    DIAG_EVENT(lt, LT_DIAG_LTHREAD_YIELD, 0, 0);
    _ready_queue_insert(THIS_SCHED, lt);
    ctx_switch(&(THIS_SCHED)->ctx, <->ctx);
}
void lthread_exit(void *ptr)
{
    struct lthread *lt = THIS_LTHREAD;
    
    if (lt->state & BIT(ST_LT_DETACH))
        return;
    
    if ((lt->join == LT_JOIN_INITIAL)
                   LT_JOIN_EXITING)) {
        DIAG_EVENT(lt, LT_DIAG_LTHREAD_EXIT, 1, 0);
        _suspend();
        
        if ((ptr != NULL) && (lt->lt_join->lt_exit_ptr != NULL))
            *(lt->lt_join->lt_exit_ptr) = ptr;
        
        lt->join = LT_JOIN_EXIT_VAL_SET;
    } else {
        DIAG_EVENT(lt, LT_DIAG_LTHREAD_EXIT, 0, 0);
        
        if ((ptr != NULL) && (lt->lt_join->lt_exit_ptr != NULL))
            *(lt->lt_join->lt_exit_ptr) = ptr;
        
        lt->join = LT_JOIN_EXIT_VAL_SET;
        _ready_queue_insert(lt->lt_join->sched,
                    (struct lthread *)lt->lt_join);
    }
    
    while (lt->join != LT_JOIN_EXIT_VAL_READ)
        _reschedule();
    
    lt->join = LT_JOIN_INITIAL;
    
    lt->state |= (BIT(ST_LT_DETACH) | BIT(ST_LT_EXITED));
}
int lthread_join(struct lthread *lt, void **ptr)
{
    if (lt == NULL)
        return POSIX_ERRNO(EINVAL);
    struct lthread *current = THIS_LTHREAD;
    uint64_t lt_state = lt->state;
    
    if ((lt_state & BIT(ST_LT_DETACH)) || (lt->join == LT_JOIN_THREAD_SET))
        return POSIX_ERRNO(EINVAL);
    
    lt->lt_join = current;
    current->lt_exit_ptr = ptr;
    
    if ((lt->join == LT_JOIN_INITIAL)
                   LT_JOIN_THREAD_SET)) {
        DIAG_EVENT(current, LT_DIAG_LTHREAD_JOIN, lt, 1);
        _suspend();
    } else {
        DIAG_EVENT(current, LT_DIAG_LTHREAD_JOIN, lt, 0);
        _ready_queue_insert(lt->sched, lt);
    }
    
    while (lt->join != LT_JOIN_EXIT_VAL_SET)
        _reschedule();
    
    if (ptr != NULL)
        *ptr = *current->lt_exit_ptr;
    
    lt->join = LT_JOIN_EXIT_VAL_READ;
    return 0;
}
void lthread_detach(void)
{
    struct lthread *lt = THIS_LTHREAD;
    DIAG_EVENT(lt, LT_DIAG_LTHREAD_DETACH, 0, 0);
    uint64_t state = lt->state;
    lt->state = state | BIT(ST_LT_DETACH);
}
void lthread_set_funcname(const char *f)
{
    struct lthread *lt = THIS_LTHREAD;
    strncpy(lt->funcname, f, sizeof(lt->funcname));
    lt->funcname[sizeof(lt->funcname)-1] = 0;
}