/* SPDX-License-Identifier: Apache-2.0 OR MIT */
/*
 * Copyright (c) 2015 Rockchip Electronics Co., Ltd.
 */

#define  MODULE_TAG "mpp_enc"

#include <string.h>

#include "mpp_env.h"
#include "mpp_mem.h"
#include "mpp_info.h"
#include "mpp_common.h"
#include "mpp_2str.h"

#include "mpp.h"
#include "mpp_enc_debug.h"
#include "mpp_enc_cfg.h"
#include "mpp_enc_impl.h"
#include "mpp_enc_cb_param.h"

#include "kmpp_obj.h"

RK_U32 mpp_enc_debug = 0;

static void copy_enc_cfg(KmppObj dst, KmppObj src)
{
    MppEncCfgSet *_dst = (MppEncCfgSet *)kmpp_obj_to_entry(dst);
    MppEncCfgSet *_src = (MppEncCfgSet *)kmpp_obj_to_entry(src);

    memcpy(_dst, _src, sizeof(*_dst));

    /* cleanup output change flag to avoid extra change flag bit when user resend the cfg */
    {
        void *p = kmpp_obj_to_flags(dst);
        rk_s32 size = kmpp_obj_to_flags_size(dst);

        if (p && size)
            memset(p, 0, size);
    }
}

MPP_RET mpp_enc_init_v2(MppEnc *enc, MppEncInitCfg *cfg)
{
    MPP_RET ret;
    MppCodingType coding = cfg->coding;
    EncImpl impl = NULL;
    MppEncImpl *p = NULL;
    MppEncHal enc_hal = NULL;
    MppEncHalCfg enc_hal_cfg;
    EncImplCfg ctrl_cfg;

    mpp_env_get_u32("mpp_enc_debug", &mpp_enc_debug, 0);

    if (NULL == enc) {
        mpp_err_f("failed to malloc context\n");
        return MPP_ERR_NULL_PTR;
    }

    *enc = NULL;

    p = mpp_calloc(MppEncImpl, 1);
    if (NULL == p) {
        mpp_err_f("failed to malloc context\n");
        return MPP_ERR_MALLOC;
    }

    mpp_enc_cfg_get(&p->set_obj);
    mpp_enc_cfg_get(&p->cfg_obj);
    if (!p->set_obj || !p->cfg_obj) {
        mpp_err_f("failed to get cfg set\n");
        ret = MPP_ERR_MALLOC;
        goto ERR_RET;
    }

    p->set = (MppEncCfgSet *)kmpp_obj_to_entry(p->set_obj);
    p->cfg = (MppEncCfgSet *)kmpp_obj_to_entry(p->cfg_obj);

    ret = mpp_enc_refs_init(&p->refs);
    if (ret) {
        mpp_err_f("could not init enc refs\n");
        goto ERR_RET;
    }

    p->output_cb.callBack = mpp_enc_callback;
    p->output_cb.ctx = p;
    p->output_cb.cmd = ENC_CALLBACK_BASE;

    // H.264 encoder use mpp_enc_hal path
    // create hal first
    enc_hal_cfg.coding = coding;
    enc_hal_cfg.cfg = p->cfg;
    enc_hal_cfg.output_cb = &p->output_cb;
    enc_hal_cfg.task_cnt = cfg->task_cnt;
    enc_hal_cfg.type = VPU_CLIENT_BUTT;
    enc_hal_cfg.dev = NULL;
    enc_hal_cfg.cap_recn_out = 0;

    ctrl_cfg.coding = coding;
    ctrl_cfg.type = VPU_CLIENT_BUTT;
    ctrl_cfg.cfg = p->cfg;
    ctrl_cfg.refs = p->refs;

    ret = mpp_enc_hal_init(&enc_hal, &enc_hal_cfg);
    if (ret) {
        mpp_err_f("could not init enc hal\n");
        goto ERR_RET;
    }

    ctrl_cfg.type = enc_hal_cfg.type;

    ret = enc_impl_init(&impl, &ctrl_cfg);
    if (ret) {
        mpp_err_f("could not init impl\n");
        goto ERR_RET;
    }

    ret = hal_info_init(&p->hal_info, MPP_CTX_ENC, coding);
    if (ret) {
        mpp_err_f("could not init hal info\n");
        goto ERR_RET;
    }

    p->coding   = coding;
    p->impl     = impl;
    p->enc_hal  = enc_hal;
    p->dev      = enc_hal_cfg.dev;
    p->mpp      = cfg->mpp;
    p->tasks    = enc_hal_cfg.tasks;
    p->sei_mode = MPP_ENC_SEI_MODE_ONE_SEQ;
    p->version_info = get_mpp_version();
    p->version_length = strlen(p->version_info);
    p->rc_cfg_size = SZ_1K;
    p->rc_cfg_info = mpp_calloc_size(char, p->rc_cfg_size);

    if (enc_hal_cfg.cap_recn_out)
        p->support_hw_deflicker = 1;

    {
        // create header packet storage
        size_t size = SZ_4K;
        p->hdr_buf = mpp_calloc_size(void, size);

        mpp_packet_init(&p->hdr_pkt, p->hdr_buf, size);
        mpp_packet_set_length(p->hdr_pkt, 0);
    }
    {
        Mpp *mpp = (Mpp *)p->mpp;

        p->input  = mpp_task_queue_get_port(mpp->mInputTaskQueue,  MPP_PORT_OUTPUT);
        p->output = mpp_task_queue_get_port(mpp->mOutputTaskQueue, MPP_PORT_INPUT);
    }

    /* NOTE: setup configure coding for check */
    p->cfg->base.coding = coding;
    p->cfg->plt_cfg.plt = &p->cfg->plt_data;
    copy_enc_cfg(p->set_obj, p->cfg_obj);

    mpp_enc_ref_cfg_init(&p->cfg->ref_cfg);
    mpp_enc_ref_cfg_init(&p->set->ref_cfg);
    ret = mpp_enc_ref_cfg_copy(p->cfg->ref_cfg, mpp_enc_ref_default());
    ret = mpp_enc_ref_cfg_copy(p->set->ref_cfg, mpp_enc_ref_default());

    ret = mpp_enc_refs_set_cfg(p->refs, mpp_enc_ref_default());
    mpp_enc_refs_set_rc_igop(p->refs, p->cfg->rc.gop);

    sem_init(&p->enc_reset, 0, 0);
    sem_init(&p->cmd_start, 0, 0);
    sem_init(&p->cmd_done, 0, 0);
    mpp_mutex_init(&p->lock);

    *enc = p;
    return ret;
ERR_RET:
    mpp_enc_deinit_v2(p);
    return ret;
}

MPP_RET mpp_enc_deinit_v2(MppEnc ctx)
{
    MppEncImpl *enc = (MppEncImpl *)ctx;

    if (NULL == enc) {
        mpp_err_f("found NULL input\n");
        return MPP_ERR_NULL_PTR;
    }

    mpp_mutex_destroy(&enc->lock);

    if (enc->hal_info) {
        hal_info_deinit(enc->hal_info);
        enc->hal_info = NULL;
    }

    if (enc->impl) {
        enc_impl_deinit(enc->impl);
        enc->impl = NULL;
    }

    if (enc->enc_hal) {
        mpp_enc_hal_deinit(enc->enc_hal);
        enc->enc_hal = NULL;
    }

    if (enc->hdr_pkt)
        mpp_packet_deinit(&enc->hdr_pkt);

    MPP_FREE(enc->hdr_buf);

    if (enc->set->ref_cfg) {
        mpp_enc_ref_cfg_deinit(&enc->set->ref_cfg);
        enc->set->ref_cfg = NULL;
    }

    if (enc->cfg->ref_cfg) {
        mpp_enc_ref_cfg_deinit(&enc->cfg->ref_cfg);
        enc->cfg->ref_cfg = NULL;
    }

    if (enc->refs) {
        mpp_enc_refs_deinit(&enc->refs);
        enc->refs = NULL;
    }

    if (enc->set_obj) {
        kmpp_obj_put_f(enc->set_obj);
        enc->set_obj = NULL;
    }

    if (enc->cfg_obj) {
        kmpp_obj_put_f(enc->cfg_obj);
        enc->cfg_obj = NULL;
    }

    if (enc->rc_ctx) {
        rc_deinit(enc->rc_ctx);
        enc->rc_ctx = NULL;
    }

    MPP_FREE(enc->rc_cfg_info);
    enc->rc_cfg_size = 0;
    enc->rc_cfg_length = 0;

    sem_destroy(&enc->enc_reset);
    sem_destroy(&enc->cmd_start);
    sem_destroy(&enc->cmd_done);

    mpp_free(enc);
    return MPP_OK;
}

MPP_RET mpp_enc_start_v2(MppEnc ctx)
{
    MppEncImpl *enc = (MppEncImpl *)ctx;
    char name[16];

    enc_dbg_func("%p in\n", enc);

    snprintf(name, sizeof(name) - 1, "mpp_%se_%d",
             strof_coding_type(enc->coding), getpid());

    enc->thread_enc = mpp_thread_create(mpp_enc_thread, enc->mpp, name);
    mpp_thread_start(enc->thread_enc);

    enc_dbg_func("%p out\n", enc);

    return MPP_OK;
}

MPP_RET mpp_enc_start_async(MppEnc ctx)
{
    MppEncImpl *enc = (MppEncImpl *)ctx;
    char name[16];

    enc_dbg_func("%p in\n", enc);

    snprintf(name, sizeof(name) - 1, "mpp_%se_%d",
             strof_coding_type(enc->coding), getpid());

    enc->thread_enc = mpp_thread_create(mpp_enc_async_thread, enc->mpp, name);
    mpp_thread_start(enc->thread_enc);

    enc_dbg_func("%p out\n", enc);

    return MPP_OK;
}

MPP_RET mpp_enc_stop_v2(MppEnc ctx)
{
    MPP_RET ret = MPP_OK;
    MppEncImpl *enc = (MppEncImpl *)ctx;

    enc_dbg_func("%p in\n", enc);

    if (enc->thread_enc) {
        mpp_thread_stop(enc->thread_enc);
        mpp_thread_destroy(enc->thread_enc);
        enc->thread_enc = NULL;
    }

    enc_dbg_func("%p out\n", enc);
    return ret;

}

MPP_RET mpp_enc_reset_v2(MppEnc ctx)
{
    MppEncImpl *enc = (MppEncImpl *)ctx;
    MppThread *thd;

    enc_dbg_func("%p in\n", enc);

    if (NULL == enc) {
        mpp_err_f("found NULL input enc\n");
        return MPP_ERR_NULL_PTR;
    }

    thd = enc->thread_enc;

    mpp_thread_lock(thd, THREAD_CONTROL);
    enc->reset_flag = 1;
    mpp_enc_notify_v2(enc, MPP_ENC_RESET);
    mpp_thread_unlock(thd, THREAD_CONTROL);

    sem_wait(&enc->enc_reset);
    mpp_assert(enc->reset_flag == 0);

    return MPP_OK;
}

MPP_RET mpp_enc_notify_v2(MppEnc ctx, RK_U32 flag)
{
    MppEncImpl *enc = (MppEncImpl *)ctx;
    MppThread *thd  = enc->thread_enc;

    enc_dbg_func("%p in flag %08x\n", enc, flag);

    mpp_thread_lock(thd, THREAD_WORK);
    if (flag == MPP_ENC_CONTROL) {
        enc->notify_flag |= flag;
        enc_dbg_notify("%p status %08x notify control signal\n", enc,
                       enc->status_flag);
        mpp_thread_signal(thd, THREAD_WORK);
    } else {
        RK_U32 old_flag = enc->notify_flag;

        enc->notify_flag |= flag;
        if ((old_flag != enc->notify_flag) &&
            (enc->notify_flag & enc->status_flag)) {
            enc_dbg_notify("%p status %08x notify %08x signal\n", enc,
                           enc->status_flag, enc->notify_flag);
            mpp_thread_signal(thd, THREAD_WORK);
        }
    }
    mpp_thread_unlock(thd, THREAD_WORK);
    enc_dbg_func("%p out\n", enc);
    return MPP_OK;
}

/*
 * preprocess config and rate-control config is common config then they will
 * be done in mpp_enc layer
 *
 * codec related config will be set in each hal component
 */
MPP_RET mpp_enc_control_v2(MppEnc ctx, MpiCmd cmd, void *param)
{
    MppEncImpl *enc = (MppEncImpl *)ctx;
    MPP_RET ret = MPP_OK;

    if (NULL == enc) {
        mpp_err_f("found NULL enc\n");
        return MPP_ERR_NULL_PTR;
    }

    if (NULL == param && cmd != MPP_ENC_SET_IDR_FRAME && cmd != MPP_ENC_SET_REF_CFG) {
        mpp_err_f("found NULL param enc %p cmd %x\n", enc, cmd);
        return MPP_ERR_NULL_PTR;
    }

    mpp_mutex_lock(&enc->lock);

    enc_dbg_ctrl("sending cmd %d param %p\n", cmd, param);

    switch (cmd) {
    case MPP_ENC_GET_CFG : {
        enc_dbg_ctrl("get enc config start\n");
        copy_enc_cfg(param, enc->cfg_obj);
        enc_dbg_ctrl("get enc config done\n");
    } break;
    case MPP_ENC_SET_PREP_CFG :
    case MPP_ENC_GET_PREP_CFG :
    case MPP_ENC_SET_RC_CFG :
    case MPP_ENC_GET_RC_CFG :
    case MPP_ENC_SET_CODEC_CFG :
    case MPP_ENC_GET_CODEC_CFG :
    case MPP_ENC_SET_SPLIT :
    case MPP_ENC_GET_SPLIT : {
        mpp_loge("deprecated %s control use MPP_ENC_GET_CFG / MPP_ENC_SET_CFG instead\n",
                 cmd == MPP_ENC_SET_PREP_CFG ? "MPP_ENC_SET_PREP_CFG" :
                 cmd == MPP_ENC_GET_PREP_CFG ? "MPP_ENC_GET_PREP_CFG" :
                 cmd == MPP_ENC_SET_RC_CFG ? "MPP_ENC_SET_RC_CFG" :
                 cmd == MPP_ENC_GET_RC_CFG ? "MPP_ENC_GET_RC_CFG" :
                 cmd == MPP_ENC_SET_CODEC_CFG ? "MPP_ENC_SET_CODEC_CFG" :
                 cmd == MPP_ENC_GET_CODEC_CFG ? "MPP_ENC_GET_CODEC_CFG" :
                 cmd == MPP_ENC_SET_SPLIT ? "MPP_ENC_SET_SPLIT" :
                 cmd == MPP_ENC_GET_SPLIT ? "MPP_ENC_GET_SPLIT" : "unknown");
        ret = MPP_NOK;
    } break;
    case MPP_ENC_GET_HEADER_MODE : {
        enc_dbg_ctrl("get header mode\n");
        memcpy(param, &enc->hdr_mode, sizeof(enc->hdr_mode));
    } break;
    case MPP_ENC_GET_OSD_PLT_CFG : {
        enc_dbg_ctrl("get osd plt cfg\n");
        memcpy(param, &enc->cfg->plt_cfg, sizeof(enc->cfg->plt_cfg));
    } break;
    default : {
        // Cmd which is not get configure will handle by enc_impl
        if (cmd == MPP_ENC_SET_CFG) {
            KmppObj obj = (KmppObj)param;

            if (kmpp_obj_is_kobj(obj)) {
                mpp_loge("can not MPP_ENC_SET_CFG by kobj %p\n", obj);
                ret = MPP_NOK;
                break;
            }
        }

        enc->cmd = cmd;
        enc->param = param;
        enc->cmd_ret = &ret;
        enc->cmd_send++;
        mpp_enc_notify_v2(ctx, MPP_ENC_CONTROL);
        sem_post(&enc->cmd_start);
        sem_wait(&enc->cmd_done);

        /* check the command is processed */
        mpp_assert(!enc->cmd);
        mpp_assert(!enc->param);
    } break;
    }

    mpp_mutex_unlock(&enc->lock);

    enc_dbg_ctrl("sending cmd %d done\n", cmd);

    return ret;
}
