/*
 * Copyright 2015 Rockchip Electronics Co. LTD
 *
 * 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.
 */

#define MODULE_TAG "hal_task"

#include <string.h>

#include "mpp_mem.h"
#include "mpp_list.h"
#include "mpp_lock.h"
#include "mpp_debug.h"
#include "mpp_common.h"

#include "hal_task.h"

typedef struct HalTaskImpl_t        HalTaskImpl;
typedef struct HalTaskGroupImpl_t   HalTaskGroupImpl;

struct HalTaskImpl_t {
    struct list_head    list;
    HalTaskGroupImpl    *group;
    RK_S32              index;
    RK_S32              status;
    void                *data;
};

struct HalTaskGroupImpl_t {
    RK_S32              stage_count;
    RK_S32              task_count;

    spinlock_t          lock;

    RK_S32              size;
    RK_S32              aligned_size;

    struct list_head    *list;
    RK_U32              *count;
    HalTaskImpl         *tasks;
};

MPP_RET hal_task_group_init(HalTaskGroup *group, RK_S32 stage_cnt, RK_S32 task_cnt, RK_S32 task_size)
{
    if (NULL == group || stage_cnt < 0 || task_cnt < 0 || task_size < 0) {
        mpp_err_f("found invalid input group %p stage %d task %d size %d\n",
                  group, stage_cnt, task_cnt, task_size);
        return MPP_ERR_UNKNOW;
    }

    HalTaskGroupImpl *p = NULL;
    RK_S32 aligned_size = MPP_ALIGN(task_size, sizeof(void *));

    do {
        RK_U8 *buf = NULL;
        RK_S32 i;

        p = mpp_calloc_size(HalTaskGroupImpl, sizeof(HalTaskGroupImpl) +
                            (sizeof(HalTaskImpl) + aligned_size) * task_cnt +
                            (sizeof(struct list_head)) * stage_cnt +
                            (sizeof(RK_U32)) * stage_cnt);
        if (NULL == p) {
            mpp_err_f("malloc group failed\n");
            break;
        }

        p->stage_count = stage_cnt;
        p->task_count = task_cnt;
        p->size = task_size;
        p->aligned_size = aligned_size;
        p->list = (struct list_head *)((HalTaskImpl *)(p + 1));
        p->count = (RK_U32 *)(p->list + stage_cnt);
        p->tasks = (HalTaskImpl *)(p->count + stage_cnt);

        mpp_spinlock_init(&p->lock);

        for (i = 0; i < stage_cnt; i++)
            INIT_LIST_HEAD(&p->list[i]);

        buf = (RK_U8 *)(p->tasks + task_cnt);

        for (i = 0; i < task_cnt; i++) {
            HalTaskImpl *task = &p->tasks[i];

            INIT_LIST_HEAD(&task->list);
            task->index  = i;
            task->group  = p;
            task->status = TASK_IDLE;
            task->data   = buf + i * aligned_size;
            list_add_tail(&task->list, &p->list[TASK_IDLE]);
            p->count[TASK_IDLE]++;
        }
        *group = p;
        return MPP_OK;
    } while (0);

    MPP_FREE(p);

    *group = NULL;
    return MPP_NOK;
}

MPP_RET hal_task_group_deinit(HalTaskGroup group)
{
    MPP_FREE(group);
    return MPP_OK;
}

MPP_RET hal_task_get_hnd(HalTaskGroup group, RK_S32 status, HalTaskHnd *hnd)
{
    if (NULL == group || status >= TASK_BUTT || NULL == hnd) {
        mpp_err_f("found invaid input group %p status %d hnd %p\n", group, status, hnd);
        return MPP_ERR_UNKNOW;
    }

    *hnd = NULL;
    HalTaskGroupImpl *p = (HalTaskGroupImpl *)group;
    struct list_head *list = &p->list[status];

    mpp_spinlock_lock(&p->lock);
    if (list_empty(list)) {
        mpp_spinlock_unlock(&p->lock);
        return MPP_NOK;
    }

    HalTaskImpl *task = list_entry(list->next, HalTaskImpl, list);
    mpp_assert(task->status == status);
    *hnd = task;
    mpp_spinlock_unlock(&p->lock);
    return MPP_OK;
}

MPP_RET hal_task_check_empty(HalTaskGroup group, RK_S32 status)
{
    if (NULL == group || status >= TASK_BUTT) {
        mpp_err_f("found invaid input group %p status %d \n", group, status);
        return MPP_ERR_UNKNOW;
    }

    HalTaskGroupImpl *p = (HalTaskGroupImpl *)group;
    struct list_head *list = &p->list[status];
    MPP_RET ret;

    mpp_spinlock_lock(&p->lock);
    ret = list_empty(list) ? MPP_OK : MPP_NOK;
    mpp_spinlock_unlock(&p->lock);

    return ret;
}

RK_S32 hal_task_get_count(HalTaskGroup group, RK_S32 status)
{
    if (NULL == group || status >= TASK_BUTT) {
        mpp_err_f("found invaid input group %p status %d\n", group, status);
        return -1;
    }

    HalTaskGroupImpl *p = (HalTaskGroupImpl *)group;
    RK_S32 count;

    mpp_spinlock_lock(&p->lock);
    count = p->count[status];
    mpp_spinlock_unlock(&p->lock);

    return count;
}

MPP_RET hal_task_hnd_set_status(HalTaskHnd hnd, RK_S32 status)
{
    if (NULL == hnd || status >= TASK_BUTT) {
        mpp_err_f("found invaid input hnd %p status %d\n", hnd, status);
        return MPP_ERR_UNKNOW;
    }

    HalTaskImpl *impl = (HalTaskImpl *)hnd;
    HalTaskGroupImpl *group = impl->group;

    mpp_assert(group);
    mpp_assert(impl->index < group->task_count);

    mpp_spinlock_lock(&group->lock);
    list_del_init(&impl->list);
    list_add_tail(&impl->list, &group->list[status]);
    group->count[impl->status]--;
    group->count[status]++;
    impl->status = status;
    mpp_spinlock_unlock(&group->lock);

    return MPP_OK;
}

MPP_RET hal_task_hnd_set_info(HalTaskHnd hnd, void *info)
{
    if (NULL == hnd || NULL == info) {
        mpp_err_f("found invaid input hnd %p info %p\n", hnd, info);
        return MPP_ERR_UNKNOW;
    }

    HalTaskImpl *impl = (HalTaskImpl *)hnd;
    HalTaskGroupImpl *group = impl->group;

    mpp_assert(impl->index < group->task_count);

    mpp_spinlock_lock(&group->lock);
    memcpy(impl->data, info, group->size);
    mpp_spinlock_unlock(&group->lock);

    return MPP_OK;
}

MPP_RET hal_task_hnd_get_info(HalTaskHnd hnd, void *info)
{
    if (NULL == hnd || NULL == info) {
        mpp_err_f("found invaid input hnd %p info %p\n", hnd, info);
        return MPP_ERR_UNKNOW;
    }

    HalTaskImpl *impl = (HalTaskImpl *)hnd;
    HalTaskGroupImpl *group = impl->group;

    mpp_assert(impl->index < group->task_count);

    mpp_spinlock_lock(&group->lock);
    memcpy(info, impl->data, group->size);
    mpp_spinlock_unlock(&group->lock);

    return MPP_OK;
}

void *hal_task_hnd_get_data(HalTaskHnd hnd)
{
    if (NULL == hnd) {
        mpp_err_f("found invaid input hnd %p\n", hnd);
        return NULL;
    }

    HalTaskImpl *impl = (HalTaskImpl *)hnd;
    HalTaskGroupImpl *group = impl->group;

    mpp_assert(impl->index < group->task_count);

    return impl->data;
}
