/*
 * Copyright 2020 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.
 */

#include <string.h>

#include "mpp_mem.h"
#include "mpp_2str.h"
#include "mpp_debug.h"
#include "mpp_common.h"

#include "hal_info.h"

typedef struct HalInfoImpl_t {
    MppCtxType      type;
    MppCodingType   coding;

    RK_U32          updated;
    RK_U32          elem_nb;
    /* info data for output */
    MppDevInfoCfg   *elems;
} HalInfoImpl;

MPP_RET hal_info_init(HalInfo *ctx, MppCtxType type, MppCodingType coding)
{
    if (NULL == ctx) {
        mpp_err_f("found NULL input ctx\n");
        return MPP_ERR_NULL_PTR;
    }

    MPP_RET ret = MPP_NOK;
    RK_U32 elem_nb = (type == MPP_CTX_DEC) ? (ENC_INFO_BUTT) : (DEC_INFO_BUTT);
    HalInfoImpl *impl = mpp_calloc_size(HalInfoImpl, sizeof(HalInfoImpl) +
                                        sizeof(MppDevInfoCfg) * elem_nb);
    if (impl) {
        impl->type = type;
        impl->coding = coding;
        impl->elem_nb = elem_nb;
        impl->elems = (MppDevInfoCfg *)(impl + 1);
        ret = MPP_OK;
    }

    *ctx = impl;

    return ret;
}

MPP_RET hal_info_deinit(HalInfo ctx)
{
    MPP_FREE(ctx);
    return MPP_OK;
}

MPP_RET hal_info_set(HalInfo ctx, RK_U32 type, RK_U32 flag, RK_U64 data)
{
    if (NULL == ctx) {
        mpp_err_f("found NULL input ctx\n");
        return MPP_ERR_NULL_PTR;
    }

    if (flag <= CODEC_INFO_FLAG_NULL || flag >= CODEC_INFO_FLAG_BUTT) {
        mpp_err_f("found invalid flag %d\n", flag);
        return MPP_ERR_VALUE;
    }

    HalInfoImpl *info = (HalInfoImpl *)ctx;
    MppDevInfoCfg *elems = NULL;

    switch (info->type) {
    case MPP_CTX_DEC : {
        if (type <= DEC_INFO_BASE || type >= DEC_INFO_BUTT) {
            mpp_err_f("found invalid dec info type %d [%d:%d]\n",
                      type, DEC_INFO_BASE, DEC_INFO_BUTT);
            return MPP_ERR_VALUE;
        }

        /* shift enum base */
        type -= DEC_INFO_BASE;
    } break;
    case MPP_CTX_ENC : {
        if (type <= ENC_INFO_BASE || type >= ENC_INFO_BUTT) {
            mpp_err_f("found invalid enc info type %d [%d:%d]\n",
                      type, ENC_INFO_BASE, ENC_INFO_BUTT);
            return MPP_ERR_VALUE;
        }

        /* shift enum base */
        type -= ENC_INFO_BASE;
    } break;
    default : {
        mpp_err_f("found invalid ctx type %d\n", info->type);
        return MPP_ERR_VALUE;
    } break;
    }

    elems = &info->elems[type];

    if (elems->type != type || elems->flag != flag || elems->data != data) {
        /* set enc info */
        elems->type = type;
        elems->flag = flag;
        elems->data = data;
        info->updated |= (1 << type);
    }

    return MPP_OK;
}

MPP_RET hal_info_get(HalInfo ctx, MppDevInfoCfg *data, RK_S32 *size)
{
    if (NULL == ctx) {
        mpp_err_f("found NULL input ctx\n");
        return MPP_ERR_NULL_PTR;
    }

    if (NULL == data || NULL == size || *size == 0) {
        mpp_err_f("found invalid output cfg data %p size %p\n", data, size);
        return MPP_ERR_NULL_PTR;
    }

    HalInfoImpl *info = (HalInfoImpl *)ctx;
    if (!info->updated) {
        *size = 0;
        return MPP_OK;
    }

    RK_S32 max_size = *size;
    RK_S32 elem_size = sizeof(info->elems[0]);
    RK_S32 out_size = 0;
    RK_S32 type_max = 0;
    RK_S32 i;

    switch (info->type) {
    case MPP_CTX_DEC : {
        type_max = DEC_INFO_BUTT - DEC_INFO_BASE;
    } break;
    case MPP_CTX_ENC : {
        type_max = ENC_INFO_BUTT - ENC_INFO_BASE;
    } break;
    default : {
        mpp_err_f("found invalid ctx type %d\n", info->type);
        return MPP_ERR_VALUE;
    } break;
    }

    for (i = 0; i < type_max; i++) {
        if (!(info->updated & (1 << i)))
            continue;

        if (out_size + elem_size > max_size) {
            mpp_err_f("out data size %d is too small for %d\n",
                      max_size, out_size + elem_size);
            break;
        }

        memcpy(data, &info->elems[i], elem_size);
        data++;
        out_size += elem_size;
        info->updated &= ~(1 << i);
    }

    *size = out_size;
    return MPP_OK;
}

RK_U64 hal_info_to_string(HalInfo ctx, RK_U32 type, void *val)
{
    RK_U64 ret = 0;

    if (NULL == ctx || NULL == val) {
        mpp_err_f("found NULL input ctx %p val %p\n", ctx, val);
        return ret;
    }

    HalInfoImpl *info = (HalInfoImpl *)ctx;
    const char *str = NULL;

    switch (info->type) {
    case MPP_CTX_DEC : {
        switch (type) {
        case DEC_INFO_FORMAT : {
            MppCodingType coding = *((MppCodingType *)val);

            mpp_assert(coding == info->coding);
            str = strof_coding_type(coding);
        } break;
        default : {
        } break;
        }
    } break;
    case MPP_CTX_ENC : {
        switch (type) {
        case ENC_INFO_FORMAT : {
            MppCodingType coding = *((MppCodingType *)val);

            mpp_assert(coding == info->coding);
            str = strof_coding_type(coding);
        } break;
        case ENC_INFO_RC_MODE : {
            MppEncRcMode rc_mode = *((MppEncRcMode *)val);

            str = strof_rc_mode(rc_mode);
        } break;
        case ENC_INFO_PROFILE : {
            RK_U32 profile = *((RK_U32 *)val);

            str = strof_profle(info->coding, profile);
        } break;
        default : {
        } break;
        }
    } break;
    default : {
        mpp_err_f("found invalid ctx type %d\n", info->type);
        return MPP_ERR_VALUE;
    } break;
    }

    if (str)
        snprintf((void *)&ret, sizeof(ret) - 1, "%s", str);

    return ret;
}

RK_U64 hal_info_to_float(RK_S32 num, RK_S32 denom)
{
    RK_U64 ret = 0;

    if (!denom)
        snprintf((void *)&ret, sizeof(ret) - 1, "%d", num);
    else
        snprintf((void *)&ret, sizeof(ret) - 1, "%.2f", (float)num / denom);

    return ret;
}

MPP_RET hal_info_from_enc_cfg(HalInfo ctx, MppEncCfgSet *cfg)
{
    MppEncRcCfg *rc = &cfg->rc;
    MppEncPrepCfg *prep = &cfg->prep;
    HalInfoImpl *info = (HalInfoImpl *)ctx;
    RK_U32 profile = 0;
    RK_U64 val = 0;

    hal_info_set(ctx, ENC_INFO_WIDTH, CODEC_INFO_FLAG_NUMBER, prep->width);
    hal_info_set(ctx, ENC_INFO_HEIGHT, CODEC_INFO_FLAG_NUMBER, prep->height);

    val = hal_info_to_string(ctx, ENC_INFO_FORMAT, &info->coding);

    hal_info_set(ctx, ENC_INFO_FORMAT, CODEC_INFO_FLAG_STRING, val);
    hal_info_set(ctx, ENC_INFO_FPS_IN, CODEC_INFO_FLAG_NUMBER,
                 rc->fps_in_num / rc->fps_in_denom);
    hal_info_set(ctx, ENC_INFO_FPS_OUT, CODEC_INFO_FLAG_NUMBER,
                 rc->fps_out_num / rc->fps_out_denom);

    val = hal_info_to_string(ctx, ENC_INFO_RC_MODE, &rc->rc_mode);
    hal_info_set(ctx, ENC_INFO_RC_MODE, CODEC_INFO_FLAG_STRING, val);

    hal_info_set(ctx, ENC_INFO_BITRATE, CODEC_INFO_FLAG_NUMBER, rc->bps_target);
    hal_info_set(ctx, ENC_INFO_GOP_SIZE, CODEC_INFO_FLAG_NUMBER, rc->gop);

    switch (info->coding) {
    case MPP_VIDEO_CodingAVC : {
        profile = cfg->h264.profile;
    } break;
    case MPP_VIDEO_CodingHEVC : {
        profile = cfg->h265.profile;
    } break;
    case MPP_VIDEO_CodingMJPEG :
    case MPP_VIDEO_CodingVP8 :
    default : {
    } break;
    }
    val = hal_info_to_string(ctx, ENC_INFO_PROFILE, &profile);
    hal_info_set(ctx, ENC_INFO_PROFILE, CODEC_INFO_FLAG_STRING, val);

    return MPP_OK;
}
