/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <sys/time.h>
#include <openssl/err.h>
#include "util/util_memory.h"
#include "iotcs_port_time.h"
#include "iotcs_port_thread.h"
#include "iotcs_thread_private.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_PORT_THREAD
#include "log/log_template.h"

static void error_handler(int error, char* msg) {
    LOG_CRITS(msg);
    switch (error) {
        case EINVAL: LOG_CRITS(" Type - EINVAL");
            break;
        case EPERM: LOG_CRITS(" Type - EPERM");
            break;
        case EDEADLK: LOG_CRITS(" Type - EDEADLK");
            break;
        case ENOMEM: LOG_CRITS(" Type - ENOMEM");
            break;
        case EBUSY: LOG_CRITS(" Type - EBUSY");
            break;
        case EAGAIN: LOG_CRITS(" Type - EAGAIN");
            break;
        default: LOG_CRITS(" Type - UNKNOWN");
            break;
    }
}

iotcs_port_thread iotcs_port_thread_create(iotcs_port_thread_func func_ptr) {
    pthread_t* thread_id = util_malloc(sizeof (pthread_t));

    int ret = pthread_create(thread_id, NULL, func_ptr, NULL);

    if (ret != 0) {
        error_handler(ret, "thread_create failed() - pthread_create");
        return NULL;
    }

    return (iotcs_port_thread) thread_id;
}

iotcs_port_thread iotcs_port_thread_create_small_stack(iotcs_port_thread_func func_ptr) {
    return iotcs_port_thread_create(func_ptr);
}

void iotcs_port_thread_join(iotcs_port_thread thread) {
    int ret = pthread_join(*(pthread_t*) thread, NULL);

    util_free(thread);

    if (ret != 0) {
        error_handler(ret, "thread_finalize failed() - thread_finalize");
    }
}

int iotcs_port_sleep_millis(int32_t timeout_ms) {
    struct timespec wait;
    struct timespec remain;

    if (timeout_ms <= 0)
        return 0;

    IOTCS_MILLISEC_TO_TIMESPEC(timeout_ms, &wait);

    nanosleep(&wait, &remain);

    return IOTCS_TIMESPEC_TO_MILLISEC(&remain);

}

void iotcs_port_thread_cleanup(void) {
    ERR_remove_thread_state(0);
}

unsigned long iotcs_get_thread_id(void) {
    return (unsigned long)pthread_self();
}