#include "asleep.h"
#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"


typedef struct Loop_Timer {
    struct timespec when;
    Future *who;
} Loop_Timer;

typedef struct _ASleep {
    pthread_mutex_t mutex;
    unsigned users;
    Loop_Timer *sleepers;
    int lo;
    int hi;
    int max;
    pthread_cond_t cond;
    bool sleeploop_cancel;
    pthread_t sleeploop;
    bool started;
} _ASleep;

static _ASleep asleep;

static void *ASleep_Sleeper(void *param);

static void ASleep_ctor(
    _ASleep *me
){
    int r;

    me->sleepers = NULL;
    me->lo = 0;
    me->hi = 0;
    me->max = 0;

    r = pthread_mutex_init(&me->mutex, NULL);
    assert(r == 0);
    r = pthread_cond_init(&me->cond, NULL);
    assert(r == 0);
    me->sleeploop_cancel = false;
    r = pthread_create(&me->sleeploop, NULL, ASleep_Sleeper, me);
    assert(r == 0);
    me->started = true;
}



static void ASleep_dtor(
    _ASleep *me
){
    int r;
    me->started = false;
    me->sleeploop_cancel = true;
    r = pthread_cond_signal(&me->cond);
    assert(r == 0);
    r = pthread_join(me->sleeploop, NULL);
    assert(r == 0);
    r = pthread_cond_destroy(&me->cond);
    assert(r == 0);
    pthread_mutex_destroy(&me->mutex);
    assert(r == 0);
    free(me->sleepers);
}


void ASleep_StartSystem(){
    ASleep_ctor(&asleep);
}


void ASleep_StopSystem(){
    ASleep_dtor(&asleep);
}


static void ASleep_AddSleeper(
    _ASleep *me,
    struct timespec when,
    Future *who
){
    int r = pthread_mutex_lock(&me->mutex);
    assert(r == 0);

    int nsleepers;
    if (me->lo <= me->hi){
        int nsleepers = me->hi - me->lo;
        // ensure there's always an empty slot so that lo == hi always means no sleepers
        if (nsleepers+1 >= me->max) {
            me->max = (me->max == 0) ? 4 : me->max * 2;
            me->sleepers = realloc(me->sleepers, me->max * sizeof(Loop_Timer));
            assert(me->sleepers);
        }
    } else {
        nsleepers = me->hi + me->max - me->lo;
        // ensure there's always an empty slot so that lo == hi always means no sleepers
        if (nsleepers+1 >= me->max) {
            int oldmax = me->max;
            me->max = (me->max == 0) ? 4 : me->max * 2;
            me->sleepers = realloc(me->sleepers, me->max * sizeof(Loop_Timer));
            assert(me->sleepers);
            memmove(&me->sleepers[me->lo], &me->lo + me->max - oldmax, sizeof(*me->sleepers) * oldmax - me->lo);
            me->lo += me->max - oldmax;
        }
    }

    // insertion sort - we're assuming there's not going to be many sleepers active at once
    int i;
    int nexti;
    for (i = me->hi; i != me->lo; i = nexti){
        nexti = i-1;
        if (i < 0){
            nexti += me->max;
        }
        if (timespec_lte(me->sleepers[nexti].when, when)){
            break;
        }
        me->sleepers[i] = me->sleepers[nexti];
    }
    me->sleepers[i].when = when;
    me->sleepers[i].who = who;
    me->hi += 1;
    if (me->hi >= me->max){
        me->hi -= me->max;
    }

    r = pthread_cond_signal(&me->cond);
    assert(r == 0);

    r = pthread_mutex_unlock(&me->mutex);
    assert(r == 0);
}


static void ASleep_RemoveSleeper(
    _ASleep *me,
    struct timespec when,
    Future *who
){
    int r = pthread_mutex_lock(&me->mutex);
    assert(r == 0);

    int i;
    for (i = me->lo; i != me->hi; ){
        if (timespec_eq(me->sleepers[i].when, when) && me->sleepers[i].who == who){
            int previ = i;
            i += 1;
            if (i >= me->max){
                i -= me->max;
            }
            for (; i != me->hi; ){
                me->sleepers[previ] = me->sleepers[i];
                previ = i;
                i += 1;
                if (i >= me->max){
                    i -= me->max;
                }
            }
            me->hi = previ;
            break;
        }
        i += 1;
        if (i >= me->max){
            i -= me->max;
        }
    }

    r = pthread_cond_signal(&me->cond);
    assert(r == 0);

    r = pthread_mutex_unlock(&me->mutex);
    assert(r == 0);
}


static void *ASleep_Sleeper(
    void *param
){
    _ASleep *me = (_ASleep *)param;
    int r;

    while (!me->sleeploop_cancel){
        r = pthread_mutex_lock(&me->mutex);
        assert(r == 0);
        if (me->lo != me->hi){
            struct timespec when = me->sleepers[0].when;

            r = pthread_mutex_unlock(&me->mutex);
            assert(r == 0);
            r = pthread_cond_timedwait(&me->cond, &me->mutex, &when);
            assert(r == 0 || r == ETIMEDOUT);
        } else {
            r = pthread_mutex_unlock(&me->mutex);
            assert(r == 0);
            r = pthread_cond_wait(&me->cond, &me->mutex);
            assert(r == 0);
        }
        // issue any due timers
        struct timespec now;
        r = clock_gettime(CLOCK_REALTIME, &now);
        assert(r == 0);
        while(me->lo != me->hi && timespec_lte(me->sleepers[me->lo].when, now)) {
            Future *fut = me->sleepers[me->lo].who;
            me->lo += 1;
            if (me->lo >= me->max){
                me->lo -= me->max;
            }
            r = pthread_mutex_unlock(&me->mutex);
            assert(r == 0);
            Future_SetResult(fut, false, NULL);
            r = pthread_mutex_lock(&me->mutex);
            assert(r == 0);
            r = clock_gettime(CLOCK_REALTIME, &now);
            assert(r == 0);
        }
        r = pthread_mutex_unlock(&me->mutex);
        assert(r == 0);
    }

    return NULL;
}


// value is a pointer to a float delay in seconds
bool ASleep(
    float delay,
    void **value
){
    assert(current_task && asleep.started);
    if (delay < 0){
        delay = 0;
    }
    float secs = floorf(delay);
    float nsecs = (delay - secs) * 1e9;
    struct timespec endtime;
    clock_gettime(CLOCK_REALTIME, &endtime);
    endtime.tv_nsec += (int)nsecs;
    if (endtime.tv_nsec > 1000000000){
        endtime.tv_sec += 1;
        endtime.tv_nsec -= 1000000000;
    }
    endtime.tv_sec += (int)secs;

    Future fut;
    Future_ctor(&fut);
    ASleep_AddSleeper(&asleep, endtime, &fut);
    bool res = Future_Await(&fut, value);
    ASleep_RemoveSleeper(&asleep, endtime, &fut);
    Future_dtor(&fut);

    return res;
}
