/*
 * Copyright (C) 2015-2017 Alibaba Group Holding Limited
 */

#include <k_api.h>

#if (RHINO_CONFIG_WORKQUEUE > 0)
static kstat_t workqueue_is_exist(kworkqueue_t *workqueue)
{
    CPSR_ALLOC();
    kworkqueue_t *pos;

    RHINO_CRITICAL_ENTER();

    for (pos = krhino_list_entry(g_workqueue_list_head.next, kworkqueue_t, workqueue_node);
        &pos->workqueue_node != &g_workqueue_list_head;
         pos = krhino_list_entry(pos->workqueue_node.next, kworkqueue_t, workqueue_node)) {
        if (pos == workqueue) {
            RHINO_CRITICAL_EXIT();
            return RHINO_WORKQUEUE_EXIST;
        }
    }

    RHINO_CRITICAL_EXIT();
    return RHINO_WORKQUEUE_NOT_EXIST;
}

static void worker_task(void *arg)
{
    CPSR_ALLOC();

    kstat_t       ret;
    kwork_t      *work = NULL;
    kworkqueue_t *queue = (kworkqueue_t *)arg;

    while (1) {

        ret = krhino_sem_take(&(queue->sem), RHINO_WAIT_FOREVER);
        if (ret != RHINO_SUCCESS) {
            k_err_proc(ret);
        }

        RHINO_CRITICAL_ENTER();

        /* have work to do. */
        work = krhino_list_entry(queue->work_list.next, kwork_t, work_node);
        klist_rm_init(&(work->work_node));
        queue->work_current = work;
        work->work_exit = 0;
        RHINO_CRITICAL_EXIT();

        /* do work */
        work->handle(work->arg);
        RHINO_CRITICAL_ENTER();
        /* clean current work */
        queue->work_current = NULL;

        RHINO_CRITICAL_EXIT();
    }
}

kstat_t krhino_workqueue_create(kworkqueue_t *workqueue, const name_t *name,
                                uint8_t pri, cpu_stack_t *stack_buf, size_t stack_size)
{
    CPSR_ALLOC();

    kstat_t ret;

    NULL_PARA_CHK(workqueue);
    NULL_PARA_CHK(name);
    NULL_PARA_CHK(stack_buf);

    if (pri >= RHINO_CONFIG_PRI_MAX) {
        return RHINO_BEYOND_MAX_PRI;
    }

    if (stack_size == 0u) {
        return RHINO_TASK_INV_STACK_SIZE;
    }

    ret = workqueue_is_exist(workqueue);
    if (ret == RHINO_WORKQUEUE_EXIST) {
        return RHINO_WORKQUEUE_EXIST;
    }

    klist_init(&(workqueue->workqueue_node));
    klist_init(&(workqueue->work_list));
    workqueue->work_current = NULL;
    workqueue->name      = name;

    ret = krhino_sem_create(&(workqueue->sem), "WORKQUEUE-SEM", 0);
    if (ret != RHINO_SUCCESS) {
        return ret;
    }

    RHINO_CRITICAL_ENTER();
    klist_insert(&g_workqueue_list_head, &(workqueue->workqueue_node));
    RHINO_CRITICAL_EXIT();

    ret = krhino_task_create(&(workqueue->worker), name, (void *)workqueue, pri,
                             0, stack_buf, stack_size, worker_task, 1);
    if (ret != RHINO_SUCCESS) {
        RHINO_CRITICAL_ENTER();
        klist_rm_init(&(workqueue->workqueue_node));
        RHINO_CRITICAL_EXIT();
        krhino_sem_del(&(workqueue->sem));
        return ret;
    }

    TRACE_WORKQUEUE_CREATE(krhino_cur_task_get(), workqueue);

    return RHINO_SUCCESS;
}

kstat_t krhino_workqueue_del(kworkqueue_t *workqueue)
{
    CPSR_ALLOC();

    kstat_t ret;

    NULL_PARA_CHK(workqueue);

    ret = workqueue_is_exist(workqueue);
    if (ret == RHINO_WORKQUEUE_NOT_EXIST) {
        return RHINO_WORKQUEUE_NOT_EXIST;
    }

    RHINO_CRITICAL_ENTER();

    if (!is_klist_empty(&(workqueue->work_list))) {
        RHINO_CRITICAL_EXIT();
        return RHINO_WORKQUEUE_BUSY;
    }

    if (workqueue->work_current != NULL) {
        RHINO_CRITICAL_EXIT();
        return RHINO_WORKQUEUE_BUSY;
    }

    RHINO_CRITICAL_EXIT();

    ret = krhino_task_del(&(workqueue->worker));
    if (ret != RHINO_SUCCESS) {
        return ret;
    }

    ret = krhino_sem_del(&(workqueue->sem));
    if (ret != RHINO_SUCCESS) {
        return ret;
    }

    RHINO_CRITICAL_ENTER();
    klist_rm_init(&(workqueue->workqueue_node));
    TRACE_WORKQUEUE_DEL(g_active_task[cpu_cur_get()], workqueue);
    RHINO_CRITICAL_EXIT();

    return RHINO_SUCCESS;
}

static void work_timer_cb(void *timer, void *arg)
{
    CPSR_ALLOC();

    kstat_t       ret;
    kwork_t      *work = ((ktimer_t *)timer)->priv;

    kworkqueue_t *wq   = (kworkqueue_t *)arg;

    RHINO_CRITICAL_ENTER();
    if (wq->work_current == work) {
        RHINO_CRITICAL_EXIT();
        return;
    }

    if (work->work_exit == 1) {
        RHINO_CRITICAL_EXIT();
        return;
    }

    /* NOTE: the work MUST be initialized firstly */
    klist_rm_init(&(work->work_node));
    klist_insert(&(wq->work_list), &(work->work_node));

    work->wq = wq;
    work->work_exit = 1;
    RHINO_CRITICAL_EXIT();

    ret = krhino_sem_give(&(wq->sem));
    if (ret != RHINO_SUCCESS) {
        return;
    }

}

kstat_t krhino_work_init(kwork_t *work, work_handle_t handle, void *arg,
                         tick_t dly)
{
    kstat_t ret;

    if (work == NULL) {
        return RHINO_NULL_PTR;
    }

    if (handle == NULL) {
        return RHINO_NULL_PTR;
    }

    NULL_PARA_CHK(work);
    NULL_PARA_CHK(handle);

    memset(work, 0, sizeof(kwork_t));

    klist_init(&(work->work_node));
    work->handle  = handle;
    work->arg     = arg;
    work->dly     = dly;
    work->wq      = NULL;

    if (dly > 0) {
        ret = krhino_timer_dyn_create((ktimer_t **)(&work->timer), "WORK-TIMER", work_timer_cb,
                                       work->dly, 0, (void *)work, 0);
        if (ret != RHINO_SUCCESS) {
            return ret;
        }
    }

    TRACE_WORK_INIT(krhino_cur_task_get(), work);

    return RHINO_SUCCESS;
}

kstat_t krhino_work_run(kworkqueue_t *workqueue, kwork_t *work)
{
    CPSR_ALLOC();

    kstat_t ret;

    NULL_PARA_CHK(workqueue);
    NULL_PARA_CHK(work);

    if (work->dly == 0) {
        RHINO_CRITICAL_ENTER();
        if (workqueue->work_current == work) {
            RHINO_CRITICAL_EXIT();
            return RHINO_WORKQUEUE_WORK_RUNNING;
        }

        if (work->work_exit == 1) {
            RHINO_CRITICAL_EXIT();
            return RHINO_WORKQUEUE_WORK_EXIST;
        }

        /* NOTE: the work MUST be initialized firstly */
        klist_rm_init(&(work->work_node));
        klist_insert(&(workqueue->work_list), &(work->work_node));

        work->wq = workqueue;
        work->work_exit = 1;

        RHINO_CRITICAL_EXIT();
        ret = krhino_sem_give(&(workqueue->sem));
        if (ret != RHINO_SUCCESS) {
            return ret;
        }

    } else {
        RHINO_CRITICAL_ENTER();
        work->timer->priv = work;
        RHINO_CRITICAL_EXIT();
        ret = krhino_timer_arg_change_auto(work->timer, (void *)workqueue);
        if (ret != RHINO_SUCCESS) {
            return ret;
        }
    }

    return RHINO_SUCCESS;
}

kstat_t krhino_work_sched(kwork_t *work)
{
    return krhino_work_run(&g_workqueue_default, work);
}

kstat_t krhino_work_cancel(kwork_t *work)
{
    CPSR_ALLOC();
    kworkqueue_t *wq;

    NULL_PARA_CHK(work);

    wq = (kworkqueue_t *)work->wq;

    if (wq == NULL) {
        if (work->dly > 0) {
            krhino_timer_stop(work->timer);
        }
        return RHINO_SUCCESS;
    }

    RHINO_CRITICAL_ENTER();
    if (wq->work_current == work) {
        RHINO_CRITICAL_EXIT();
        return RHINO_WORKQUEUE_WORK_RUNNING;
    }

    if (work->work_exit == 1) {
        RHINO_CRITICAL_EXIT();
        return RHINO_WORKQUEUE_WORK_EXIST;
    }

    klist_rm_init(&(work->work_node));
    work->wq      = NULL;
    RHINO_CRITICAL_EXIT();

    return RHINO_SUCCESS;
}

void workqueue_init(void)
{
    klist_init(&g_workqueue_list_head);

    krhino_workqueue_create(&g_workqueue_default, "DEFAULT-WORKQUEUE",
                            RHINO_CONFIG_WORKQUEUE_TASK_PRIO, g_workqueue_stack,
                            RHINO_CONFIG_WORKQUEUE_STACK_SIZE);
}
#endif

