#include "os/os.h"
#include "os_util.h"
#include "task.h"
#include "list/slist.h"

/* Macro used to convert os_priority to the kernel's real priority */
#define OS_KERNEL_PRIO(prio) (prio)

static slist_t s_thread_list;

static void _os_delete(struct os_thread_handle *thread_handle)
{
    os_scheduler_suspend();

    slist_remove(&s_thread_list, &thread_handle->node);
    thread_handle->thread->handle = NULL;
    if (thread_handle->flag_free)
    {
        os_free(thread_handle->thread);
    }
    os_free(thread_handle);

    os_scheduler_resume();
}

static void _os_thread_start(void *arg)
{
    struct os_thread_handle *thread_handle = arg;
    thread_handle->entry(thread_handle->arg);
    _os_delete(thread_handle);
    vTaskDelete(NULL);
}

os_state os_thread_init(os_thread_t *thread,
                        const char *name,
                        os_thread_entry_t entry,
                        void *arg,
                        void *stack_base,
                        size_t stack_size,
                        os_priority priority)
{
#if (configSUPPORT_STATIC_ALLOCATION == 1)

    OS_ASS_HDL(!os_thread_is_valid(thread), thread->handle);

    if (stack_size <= sizeof(struct os_thread_handle) + sizeof(StaticTask_t))
    {
        OS_ERR("err size: %d <= %d\r\n", stack_size, sizeof(struct os_thread_handle) + sizeof(StaticTask_t));
        return OS_FAIL;
    }

    struct os_thread_handle *thread_handle = stack_base;
    StaticTask_t *pxTaskBuffer = (StaticTask_t *)&thread_handle[1];
    StackType_t *puxStackBuffer = (StackType_t *)&pxTaskBuffer[1];

    thread_handle->thread = thread;
    thread_handle->entry = entry;
    thread_handle->arg = arg;
    thread_handle->flag_free = 0;
    thread_handle->pxCreatedTask = xTaskCreateStatic(_os_thread_start,
                                                     name,
                                                     stack_size / sizeof(StackType_t),
                                                     thread_handle,
                                                     OS_KERNEL_PRIO(priority),
                                                     puxStackBuffer,
                                                     pxTaskBuffer);
    thread->handle = thread_handle;
    return OS_OK;

#else

    OS_ERR("err configSUPPORT_STATIC_ALLOCATION != 1\r\n");
    return OS_FAIL;

#endif
}

os_state os_thread_create(os_thread_t *thread,
                          const char *name,
                          os_thread_entry_t entry,
                          void *arg,
                          size_t stack_size,
                          os_priority priority)
{
    int ret;

    if (thread)
    {
        OS_ASS_HDL(!os_thread_is_valid(thread), thread->handle);
    }

    struct os_thread_handle *thread_handle = os_malloc(sizeof(struct os_thread_handle));
    if (thread_handle == NULL)
    {
        return OS_E_NOMEM;
    }

    if (thread == NULL)
    {
        thread = os_malloc(sizeof(os_thread_t));
        if (thread == NULL)
        {
            os_free(thread_handle);
            return OS_E_NOMEM;
        }
        memset(thread, 0, sizeof(os_thread_t));
        thread_handle->flag_free = 1;
    }
    else
    {
        thread_handle->flag_free = 0;
    }
    thread->handle = thread_handle;
    thread_handle->work_q_list = NULL;
    thread_handle->thread = thread;
    thread_handle->entry = entry;
    thread_handle->arg = arg;
    slist_init_node(&thread_handle->node);

    os_scheduler_suspend();
    slist_insert_tail(&s_thread_list, &thread_handle->node);
    os_scheduler_resume();

    ret = xTaskCreatePinnedToCore(_os_thread_start,
                                  name,
                                  stack_size / sizeof(StackType_t),
                                  thread_handle,
                                  OS_KERNEL_PRIO(priority),
                                  &thread_handle->pxCreatedTask,
                                  0);
    if (ret != pdPASS)
    {
        OS_ERR("err %d\r\n", ret);

        os_scheduler_suspend();
        slist_remove(&s_thread_list, &thread_handle->node);
        os_scheduler_resume();

        if (thread_handle->flag_free == 0)
        {
            thread->handle = NULL;
        }
        if (thread_handle->thread)
        {
            os_free(thread_handle->thread);
        }
        os_free(thread_handle);
        return OS_FAIL;
    }
    return OS_OK;
}

os_state os_thread_delete(os_thread_t *thread)
{
    if (thread == NULL) /* delete self */
    {
        struct os_thread_handle *thread_handle = os_thread_get_self();
        _os_delete(thread_handle);
        vTaskDelete(NULL);
        return OS_OK;
    }
    else
    {
        OS_ASS_HDL(os_thread_is_valid(thread), thread->handle);
        struct os_thread_handle *thread_handle = thread->handle;
        vTaskDelete(thread_handle->pxCreatedTask);
        _os_delete(thread_handle);
        return OS_OK;
    }
}

void os_thread_sleep(os_time_t msec)
{
    vTaskDelay((TickType_t)os_calc_msec_to_ticks(msec));
}

void os_thread_yield(void)
{
    taskYIELD();
}

void os_thread_suspend(os_thread_t *thread)
{
    if (thread)
    {
        OS_ASS_HDL(os_thread_is_valid(thread), thread->handle);
        struct os_thread_handle *thread_handle = thread->handle;
        vTaskSuspend(thread_handle->pxCreatedTask);
    }
    else
    {
        vTaskSuspend(NULL);
    }
}

void os_thread_resume(os_thread_t *thread)
{
    OS_ASS_HDL(os_thread_is_valid(thread), thread->handle);
    struct os_thread_handle *thread_handle = thread->handle;
    vTaskResume(thread_handle->pxCreatedTask);
}

os_thread_handle_t os_thread_get_self(void)
{
    os_thread_handle_t ret = NULL;

    os_scheduler_suspend();
    TaskHandle_t xTask = xTaskGetCurrentTaskHandle();
    struct os_thread_handle *thread_handle;
    struct os_thread_handle *prev_handle = NULL;
    int cnt = 0;
    SLIST_FOR_EACH_CONTAINER(&s_thread_list, thread_handle, node)
    {
        if (thread_handle->pxCreatedTask == xTask)
        {
            ret = thread_handle->thread->handle;
            break;
        }
        prev_handle = thread_handle;
        cnt++;
    }
    if (cnt > 5)
    {
        slist_remove_next(&s_thread_list, &prev_handle->node, &thread_handle->node);
        slist_insert_font(&s_thread_list, &thread_handle->node);
    }
    os_scheduler_resume();
    SYS_ASSERT(ret, "xTask: %p", xTask);
    return ret;
}

const char *os_thread_get_name(os_thread_t *thread)
{
    struct os_thread_handle *thread_handle = thread->handle;
    return pcTaskGetName(thread_handle->pxCreatedTask);
}

size_t os_thread_stack_min(os_thread_t *thread)
{
#if INCLUDE_uxTaskGetStackHighWaterMark
    TaskHandle_t xTask;

    if (thread != NULL)
    {
        if (os_thread_is_valid(thread))
        {
            struct os_thread_handle *thread_handle = thread->handle;
            xTask = thread_handle->pxCreatedTask;
        }
        else
        {
            return 0;
        }
    }
    else
    {
        xTask = NULL;
    }

    extern UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);
    return (uxTaskGetStackHighWaterMark(xTask) * sizeof(StackType_t));
#else
    return 0;
#endif
}

bool os_thread_is_valid(os_thread_t *thread)
{
    if (thread && thread->handle)
    {
        return true;
    }
    else
    {
        return false;
    }
}
