/*
 * 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 "mpp_meta"

#include <string.h>

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

#include "mpp_meta_impl.h"

#define META_VAL_INVALID    (0x00000000)
#define META_VAL_VALID      (0x00000001)
#define META_VAL_READY      (0x00000002)

#define WRITE_ONCE(x, val)  ((*(volatile typeof(x) *) &(x)) = (val))
#define READ_ONCE(var)      (*((volatile typeof(var) *)(&(var))))

static MppMetaDef meta_defs[] = {
    /* categorized by type */
    /* data flow type */
    {   KEY_INPUT_FRAME,        TYPE_FRAME,     },
    {   KEY_OUTPUT_FRAME,       TYPE_FRAME,     },
    {   KEY_INPUT_PACKET,       TYPE_PACKET,    },
    {   KEY_OUTPUT_PACKET,      TYPE_PACKET,    },
    /* buffer for motion detection */
    {   KEY_MOTION_INFO,        TYPE_BUFFER,    },
    /* buffer storing the HDR information for current frame*/
    {   KEY_HDR_INFO,           TYPE_BUFFER,    },
    /* the offset of HDR meta data in frame buffer */
    {   KEY_HDR_META_OFFSET,    TYPE_S32,       },
    {   KEY_HDR_META_SIZE,      TYPE_S32,       },

    {   KEY_OUTPUT_INTRA,       TYPE_S32,       },
    {   KEY_INPUT_BLOCK,        TYPE_S32,       },
    {   KEY_OUTPUT_BLOCK,       TYPE_S32,       },

    /* extra information for tsvc */
    {   KEY_TEMPORAL_ID,        TYPE_S32,       },
    {   KEY_LONG_REF_IDX,       TYPE_S32,       },
    {   KEY_ENC_AVERAGE_QP,     TYPE_S32,       },

    {   KEY_ROI_DATA,           TYPE_PTR,       },
    {   KEY_ROI_DATA2,          TYPE_PTR,       },
    {   KEY_OSD_DATA,           TYPE_PTR,       },
    {   KEY_OSD_DATA2,          TYPE_PTR,       },
    {   KEY_USER_DATA,          TYPE_PTR,       },
    {   KEY_USER_DATAS,         TYPE_PTR,       },
    {   KEY_QPMAP0,             TYPE_BUFFER,    },
    {   KEY_MV_LIST,            TYPE_PTR,       },

    {   KEY_ENC_MARK_LTR,       TYPE_S32,       },
    {   KEY_ENC_USE_LTR,        TYPE_S32,       },
    {   KEY_ENC_FRAME_QP,       TYPE_S32,       },
    {   KEY_ENC_BASE_LAYER_PID, TYPE_S32,       },

    {   KEY_DEC_TBN_EN,         TYPE_S32,       },
    {   KEY_DEC_TBN_Y_OFFSET,   TYPE_S32,       },
    {   KEY_DEC_TBN_UV_OFFSET,  TYPE_S32,       },
};

class MppMetaService
{
private:
    // avoid any unwanted function
    MppMetaService();
    ~MppMetaService();
    MppMetaService(const MppMetaService &);
    MppMetaService &operator=(const MppMetaService &);

    spinlock_t          mLock;
    struct list_head    mlist_meta;

    RK_U32              meta_id;
    RK_S32              meta_count;
    RK_U32              finished;

public:
    static MppMetaService *get_inst() {
        static MppMetaService instance;
        return &instance;
    }

    /*
     * get_index_of_key does two things:
     * 1. Check the key / type pair is correct or not.
     *    If failed on check return negative value
     * 2. Compare all exsisting meta data defines to find the non-negative index
     */
    RK_S32 get_index_of_key(MppMetaKey key, MppMetaType type);

    MppMetaImpl  *get_meta(const char *tag, const char *caller);
    void          put_meta(MppMetaImpl *meta);
};

MppMetaService::MppMetaService()
    : meta_id(0),
      meta_count(0),
      finished(0)
{
    mpp_spinlock_init(&mLock);
    INIT_LIST_HEAD(&mlist_meta);
}

MppMetaService::~MppMetaService()
{
    if (!list_empty(&mlist_meta)) {
        MppMetaImpl *pos, *n;

        mpp_log_f("cleaning leaked metadata\n");

        list_for_each_entry_safe(pos, n, &mlist_meta, MppMetaImpl, list_meta) {
            put_meta(pos);
        }
    }

    mpp_assert(meta_count == 0);
    finished = 1;
}

RK_S32 MppMetaService::get_index_of_key(MppMetaKey key, MppMetaType type)
{
    RK_S32 i = 0;
    RK_S32 num = MPP_ARRAY_ELEMS(meta_defs);

    for (i = 0; i < num; i++) {
        if ((meta_defs[i].key == key) && (meta_defs[i].type == type))
            break;
    }

    return (i < num) ? (i) : (-1);
}

MppMetaImpl *MppMetaService::get_meta(const char *tag, const char *caller)
{
    MppMetaImpl *impl = mpp_malloc_size(MppMetaImpl, sizeof(MppMetaImpl) +
                                        sizeof(MppMetaVal) * MPP_ARRAY_ELEMS(meta_defs));
    if (impl) {
        const char *tag_src = (tag) ? (tag) : (MODULE_TAG);
        RK_U32 i;

        strncpy(impl->tag, tag_src, sizeof(impl->tag));
        impl->caller = caller;
        impl->meta_id = MPP_FETCH_ADD(&meta_id, 1);
        INIT_LIST_HEAD(&impl->list_meta);
        impl->ref_count = 1;
        impl->node_count = 0;

        for (i = 0; i < MPP_ARRAY_ELEMS(meta_defs); i++)
            impl->vals[i].state = 0;

        mpp_spinlock_lock(&mLock);
        list_add_tail(&impl->list_meta, &mlist_meta);
        mpp_spinlock_unlock(&mLock);
        MPP_FETCH_ADD(&meta_count, 1);
    } else {
        mpp_err_f("failed to malloc meta data\n");
    }
    return impl;
}

void MppMetaService::put_meta(MppMetaImpl *meta)
{
    if (finished)
        return ;

    RK_S32 ref_count = MPP_SUB_FETCH(&meta->ref_count, 1);

    if (ref_count > 0)
        return;

    if (ref_count < 0) {
        mpp_err_f("invalid negative ref_count %d\n", ref_count);
        return;
    }

    mpp_spinlock_lock(&mLock);
    list_del_init(&meta->list_meta);
    mpp_spinlock_unlock(&mLock);
    MPP_FETCH_SUB(&meta_count, 1);

    mpp_free(meta);
}

MPP_RET mpp_meta_get_with_tag(MppMeta *meta, const char *tag, const char *caller)
{
    if (NULL == meta) {
        mpp_err_f("found NULL input\n");
        return MPP_ERR_NULL_PTR;
    }

    MppMetaService *service = MppMetaService::get_inst();
    MppMetaImpl *impl = service->get_meta(tag, caller);
    *meta = (MppMeta) impl;
    return (impl) ? (MPP_OK) : (MPP_NOK);
}

MPP_RET mpp_meta_put(MppMeta meta)
{
    if (NULL == meta) {
        mpp_err_f("found NULL input\n");
        return MPP_ERR_NULL_PTR;
    }

    MppMetaService *service = MppMetaService::get_inst();
    MppMetaImpl *impl = (MppMetaImpl *)meta;

    service->put_meta(impl);
    return MPP_OK;
}

MPP_RET mpp_meta_inc_ref(MppMeta meta)
{
    if (NULL == meta) {
        mpp_err_f("found NULL input\n");
        return MPP_ERR_NULL_PTR;
    }

    MppMetaImpl *impl = (MppMetaImpl *)meta;

    MPP_FETCH_ADD(&impl->ref_count, 1);
    return MPP_OK;
}

RK_S32 mpp_meta_size(MppMeta meta)
{
    if (NULL == meta) {
        mpp_err_f("found NULL input\n");
        return -1;
    }

    MppMetaImpl *impl = (MppMetaImpl *)meta;

    return MPP_FETCH_ADD(&impl->node_count, 0);
}

MPP_RET mpp_meta_dump(MppMeta meta)
{
    if (NULL == meta) {
        mpp_err_f("found NULL input\n");
        return MPP_ERR_NULL_PTR;
    }

    MppMetaImpl *impl = (MppMetaImpl *)meta;
    RK_U32 i;

    mpp_log("dumping meta %d node count %d\n", impl->meta_id, impl->node_count);

    for (i = 0; i < MPP_ARRAY_ELEMS(meta_defs); i++) {
        if (!impl->vals[i].state)
            continue;

        const char *key = (const char *)&meta_defs[i].key;
        const char *type = (const char *)&meta_defs[i].type;

        mpp_log("key %c%c%c%c type %c%c%c%c\n",
                key[3], key[2], key[1], key[0],
                type[3], type[2], type[1], type[0]);
    }

    return MPP_OK;
}

#define MPP_META_ACCESSOR(func_type, arg_type, key_type, key_field)  \
    MPP_RET mpp_meta_set_##func_type(MppMeta meta, MppMetaKey key, arg_type val) \
    { \
        if (NULL == meta) { \
            mpp_err_f("found NULL input\n"); \
            return MPP_ERR_NULL_PTR; \
        } \
        MppMetaService *service = MppMetaService::get_inst(); \
        RK_S32 index = service->get_index_of_key(key, key_type); \
        if (index < 0) \
            return MPP_NOK; \
        MppMetaImpl *impl = (MppMetaImpl *)meta; \
        MppMetaVal *meta_val = &impl->vals[index]; \
        if (MPP_BOOL_CAS(&meta_val->state, META_VAL_INVALID, META_VAL_VALID)) \
            MPP_FETCH_ADD(&impl->node_count, 1); \
        meta_val->key_field = val; \
        MPP_FETCH_OR(&meta_val->state, META_VAL_READY); \
        return MPP_OK; \
    } \
    MPP_RET mpp_meta_get_##func_type(MppMeta meta, MppMetaKey key, arg_type *val) \
    { \
        if (NULL == meta) { \
            mpp_err_f("found NULL input\n"); \
            return MPP_ERR_NULL_PTR; \
        } \
        MppMetaService *service = MppMetaService::get_inst(); \
        RK_S32 index = service->get_index_of_key(key, key_type); \
        if (index < 0) \
            return MPP_NOK; \
        MppMetaImpl *impl = (MppMetaImpl *)meta; \
        MppMetaVal *meta_val = &impl->vals[index]; \
        MPP_RET ret = MPP_NOK; \
        if (MPP_BOOL_CAS(&meta_val->state, META_VAL_VALID | META_VAL_READY, META_VAL_INVALID)) { \
            *val = meta_val->key_field; \
            MPP_FETCH_SUB(&impl->node_count, 1); \
            ret = MPP_OK; \
        } \
        return ret; \
    } \
    MPP_RET mpp_meta_get_##func_type##_d(MppMeta meta, MppMetaKey key, arg_type *val, arg_type def) \
    { \
        if (NULL == meta) { \
            mpp_err_f("found NULL input\n"); \
            return MPP_ERR_NULL_PTR; \
        } \
        MppMetaService *service = MppMetaService::get_inst(); \
        RK_S32 index = service->get_index_of_key(key, key_type); \
        if (index < 0) \
            return MPP_NOK; \
        MppMetaImpl *impl = (MppMetaImpl *)meta; \
        MppMetaVal *meta_val = &impl->vals[index]; \
        MPP_RET ret = MPP_NOK; \
        if (MPP_BOOL_CAS(&meta_val->state, META_VAL_VALID | META_VAL_READY, META_VAL_INVALID)) { \
            *val = meta_val->key_field; \
            MPP_FETCH_SUB(&impl->node_count, 1); \
            ret = MPP_OK; \
        } else { \
            *val = def; \
        } \
        return ret; \
    }

MPP_META_ACCESSOR(s32, RK_S32, TYPE_S32, val_s32)
MPP_META_ACCESSOR(s64, RK_S64, TYPE_S64, val_s64)
MPP_META_ACCESSOR(ptr, void *, TYPE_PTR, val_ptr)
MPP_META_ACCESSOR(frame, MppFrame, TYPE_FRAME, frame)
MPP_META_ACCESSOR(packet, MppPacket, TYPE_PACKET, packet)
MPP_META_ACCESSOR(buffer, MppBuffer, TYPE_BUFFER, buffer)
