/*
 * Copyright (C) 2019 Intel Corporation.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bh_thread.h"
#include "bh_assert.h"
#include "bh_log.h"
#include "bh_memory.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

static bool is_thread_sys_inited = false;

static korp_mutex thread_list_lock;
static pthread_key_t thread_local_storage_key[BH_MAX_TLS_NUM];

int _vm_thread_sys_init()
{
    unsigned i, j;
    int ret;

    if (is_thread_sys_inited)
        return 0;

    for (i = 0; i < BH_MAX_TLS_NUM; i++) {
        ret = pthread_key_create(&thread_local_storage_key[i], NULL);
        if (ret)
            goto fail;
    }

    ret = vm_mutex_init(&thread_list_lock);
    if (ret)
        goto fail;

    is_thread_sys_inited = true;
    return 0;

    fail: for (j = 0; j < i; j++)
        pthread_key_delete(thread_local_storage_key[j]);
    return -1;
}

void vm_thread_sys_destroy(void)
{
    if (is_thread_sys_inited) {
        unsigned i;
        for (i = 0; i < BH_MAX_TLS_NUM; i++)
            pthread_key_delete(thread_local_storage_key[i]);
        vm_mutex_destroy(&thread_list_lock);
        is_thread_sys_inited = false;
    }
}

typedef struct {
    thread_start_routine_t start;
    void* stack;
    int stack_size;
    void* arg;
} thread_wrapper_arg;

static void *vm_thread_wrapper(void *arg)
{
    thread_wrapper_arg * targ = arg;
    LOG_VERBOSE("THREAD CREATE 0x%08x\n", &targ);
    targ->stack = (void *)((uintptr_t)(&arg) & ~0xfff);
    _vm_tls_put(1, targ);
    targ->start(targ->arg);
    bh_free(targ);
    _vm_tls_put(1, NULL);
    return NULL;
}

int _vm_thread_create_with_prio(korp_tid *tid, thread_start_routine_t start,
        void *arg, unsigned int stack_size, int prio)
{
    pthread_attr_t tattr;
    thread_wrapper_arg *targ;

    bh_assert(stack_size > 0);
    bh_assert(tid);
    bh_assert(start);

    *tid = INVALID_THREAD_ID;

    pthread_attr_init(&tattr);
    pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE);
    if (pthread_attr_setstacksize(&tattr, stack_size) != 0) {
        bh_debug("Invalid thread stack size %u. Min stack size on Linux = %u",
                stack_size, PTHREAD_STACK_MIN);
        pthread_attr_destroy(&tattr);
        return BHT_ERROR;
    }

    targ = (thread_wrapper_arg*) bh_malloc(sizeof(*targ));
    if (!targ) {
        pthread_attr_destroy(&tattr);
        return BHT_ERROR;
    }

    targ->start = start;
    targ->arg = arg;
    targ->stack_size = stack_size;

    if (pthread_create(tid, &tattr, vm_thread_wrapper, targ) != 0) {
        pthread_attr_destroy(&tattr);
        bh_free(targ);
        return BHT_ERROR;
    }

    pthread_attr_destroy(&tattr);
    return BHT_OK;
}

int _vm_thread_create(korp_tid *tid, thread_start_routine_t start, void *arg,
        unsigned int stack_size)
{
    return _vm_thread_create_with_prio(tid, start, arg, stack_size,
                                       BH_THREAD_DEFAULT_PRIORITY);
}

korp_tid _vm_self_thread()
{
    return (korp_tid) pthread_self();
}

void vm_thread_exit(void * code)
{
    bh_free(_vm_tls_get(1));
    _vm_tls_put(1, NULL);
    pthread_exit(code);
}

void *_vm_tls_get(unsigned idx)
{
    bh_assert(idx < BH_MAX_TLS_NUM);
    return pthread_getspecific(thread_local_storage_key[idx]);
}

int _vm_tls_put(unsigned idx, void * tls)
{
    bh_assert(idx < BH_MAX_TLS_NUM);
    pthread_setspecific(thread_local_storage_key[idx], tls);
    return BHT_OK;
}

int _vm_mutex_init(korp_mutex *mutex)
{
    return pthread_mutex_init(mutex, NULL) == 0 ? BHT_OK : BHT_ERROR;
}

int _vm_recursive_mutex_init(korp_mutex *mutex)
{
    int ret;

    pthread_mutexattr_t mattr;

    bh_assert(mutex);
    ret = pthread_mutexattr_init(&mattr);
    if (ret)
        return BHT_ERROR;

    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE_NP);
    ret = pthread_mutex_init(mutex, &mattr);
    pthread_mutexattr_destroy(&mattr);

    return ret == 0 ? BHT_OK : BHT_ERROR;
}

int _vm_mutex_destroy(korp_mutex *mutex)
{
    int ret;

    bh_assert(mutex);
    ret = pthread_mutex_destroy(mutex);

    return ret == 0 ? BHT_OK : BHT_ERROR;
}

/* Returned error (EINVAL, EAGAIN and EDEADLK) from
 locking the mutex indicates some logic error present in
 the program somewhere.
 Don't try to recover error for an existing unknown error.*/
void vm_mutex_lock(korp_mutex *mutex)
{
    int ret;

    bh_assert(mutex);
    ret = pthread_mutex_lock(mutex);
    if (0 != ret) {
        printf("vm mutex lock failed (ret=%d)!\n", ret);
        exit(-1);
    }
}

int vm_mutex_trylock(korp_mutex *mutex)
{
    int ret;

    bh_assert(mutex);
    ret = pthread_mutex_trylock(mutex);

    return ret == 0 ? BHT_OK : BHT_ERROR;
}

/* Returned error (EINVAL, EAGAIN and EPERM) from
 unlocking the mutex indicates some logic error present
 in the program somewhere.
 Don't try to recover error for an existing unknown error.*/
void vm_mutex_unlock(korp_mutex *mutex)
{
    int ret;

    bh_assert(mutex);
    ret = pthread_mutex_unlock(mutex);
    if (0 != ret) {
        printf("vm mutex unlock failed (ret=%d)!\n", ret);
        exit(-1);
    }
}

int _vm_sem_init(korp_sem* sem, unsigned int c)
{
    int ret;

    bh_assert(sem);
    ret = sem_init(sem, 0, c);

    return ret == 0 ? BHT_OK : BHT_ERROR;
}

int _vm_sem_destroy(korp_sem *sem)
{
    int ret;

    bh_assert(sem);
    ret = sem_destroy(sem);

    return ret == 0 ? BHT_OK : BHT_ERROR;
}

int _vm_sem_wait(korp_sem *sem)
{
    int ret;

    bh_assert(sem);

    ret = sem_wait(sem);

    return ret == 0 ? BHT_OK : BHT_ERROR;
}

int _vm_sem_reltimedwait(korp_sem *sem, int mills)
{
    int ret = BHT_OK;

    struct timespec timeout;
    const int mills_per_sec = 1000;
    const int mills_to_nsec = 1E6;

    bh_assert(sem);

    if (mills == BHT_WAIT_FOREVER) {
        ret = sem_wait(sem);
    } else {

        timeout.tv_sec = mills / mills_per_sec;
        timeout.tv_nsec = (mills % mills_per_sec) * mills_to_nsec;
        timeout.tv_sec += time(NULL);

        ret = sem_timedwait(sem, &timeout);
    }

    if (ret != BHT_OK) {
        if (errno == BHT_TIMEDOUT) {
            ret = BHT_TIMEDOUT;
            errno = 0;
        } else {
            bh_debug("Faliure happens when timed wait is called");
            bh_assert(0);
        }
    }

    return ret;
}

int _vm_sem_post(korp_sem *sem)
{
    bh_assert(sem);

    return sem_post(sem) == 0 ? BHT_OK : BHT_ERROR;
}

int _vm_cond_init(korp_cond *cond)
{
    bh_assert(cond);

    if (pthread_cond_init(cond, NULL) != BHT_OK)
        return BHT_ERROR;

    return BHT_OK;
}

int _vm_cond_destroy(korp_cond *cond)
{
    bh_assert(cond);

    if (pthread_cond_destroy(cond) != BHT_OK)
        return BHT_ERROR;

    return BHT_OK;
}

int _vm_cond_wait(korp_cond *cond, korp_mutex *mutex)
{
    bh_assert(cond);
    bh_assert(mutex);

    if (pthread_cond_wait(cond, mutex) != BHT_OK)
        return BHT_ERROR;

    return BHT_OK;
}

static void msec_nsec_to_abstime(struct timespec *ts, int64 msec, int32 nsec)
{
    struct timeval tv;

    gettimeofday(&tv, NULL);

    ts->tv_sec = tv.tv_sec + msec / 1000;
    ts->tv_nsec = tv.tv_usec * 1000 + (msec % 1000) * 1000000 + nsec;

    if (ts->tv_nsec >= 1000000000L) {
        ts->tv_sec++;
        ts->tv_nsec -= 1000000000L;
    }
}

int _vm_cond_reltimedwait(korp_cond *cond, korp_mutex *mutex, int mills)
{
    int ret;
    struct timespec abstime;

    if (mills == BHT_WAIT_FOREVER)
        ret = pthread_cond_wait(cond, mutex);
    else {
        msec_nsec_to_abstime(&abstime, mills, 0);
        ret = pthread_cond_timedwait(cond, mutex, &abstime);
    }

    if (ret != BHT_OK && ret != BHT_TIMEDOUT)
        return BHT_ERROR;

    return BHT_OK;
}

int _vm_cond_signal(korp_cond *cond)
{
    bh_assert(cond);

    if (pthread_cond_signal(cond) != BHT_OK)
        return BHT_ERROR;

    return BHT_OK;
}

int _vm_cond_broadcast(korp_cond *cond)
{
    bh_assert(cond);

    if (pthread_cond_broadcast(cond) != BHT_OK)
        return BHT_ERROR;

    return BHT_OK;
}

int _vm_thread_cancel(korp_tid thread)
{
    return pthread_cancel(thread);
}

int _vm_thread_join(korp_tid thread, void **value_ptr, int mills)
{
    return pthread_join(thread, value_ptr);
}

int _vm_thread_detach(korp_tid thread)
{
    return pthread_detach(thread);
}

