/*
 * liboapv encoder
 * Advanced Professional Video codec library
 *
 * Copyright (C) 2025 Dawid Kozinski <d.kozinski@samsung.com>
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdint.h>
#include <stdlib.h>

#include <oapv/oapv.h>

#include "libavutil/avassert.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/imgutils.h"
#include "libavutil/internal.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/pixfmt.h"
#include "libavutil/mastering_display_metadata.h"

#include "avcodec.h"
#include "apv.h"
#include "codec_internal.h"
#include "encode.h"
#include "profiles.h"

#define MAX_BS_BUF   (128 * 1024 * 1024)
#define MAX_NUM_FRMS (1)           // supports only 1-frame in an access unit
#define FRM_IDX      (0)           // supports only 1-frame in an access unit
#define MAX_NUM_CC   (OAPV_MAX_CC) // Max number of color components (upto 4:4:4:4)

#define MAX_METADATA_PAYLOADS (8)

static inline int64_t rescale_rational(AVRational a, int b)
{
    return av_rescale(a.num, b, a.den);
}

/**
 * The structure stores all the states associated with the instance of APV encoder
 */
typedef struct ApvEncContext {
    const AVClass *class;

    oapve_t id;             // APV instance identifier
    oapvm_t mid;
    oapve_cdesc_t   cdsc;   // coding parameters i.e profile, width & height of input frame, num of therads, frame rate ...
    oapv_bitb_t     bitb;   // bitstream buffer (output)
    oapve_stat_t    stat;   // encoding status (output)

    oapv_frms_t ifrms;      // frames for input

    int preset_id;          // preset of apv ( fastest, fast, medium, slow, placebo)

    int qp;                 // quantization parameter (QP) [0,63]

    oapvm_payload_mdcv_t mdcv;
    oapvm_payload_cll_t cll;

    oapvm_payload_t *payloads;
    unsigned nb_payloads;

    AVDictionary *oapv_params;
} ApvEncContext;

static int apv_imgb_release(oapv_imgb_t *imgb)
{
    int refcnt = --imgb->refcnt;
    if (refcnt == 0) {
        for (int i = 0; i < imgb->np; i++)
            av_freep(&imgb->baddr[i]);
        av_free(imgb);
    }

    return refcnt;
}

static int apv_imgb_addref(oapv_imgb_t * imgb)
{
    int refcnt = ++imgb->refcnt;
    return refcnt;
}

static int apv_imgb_getref(oapv_imgb_t * imgb)
{
    return imgb->refcnt;
}

/**
 * Convert FFmpeg pixel format (AVPixelFormat) into APV pre-defined color format
 *
 * @return APV pre-defined color format (@see oapv.h) on success, OAPV_CF_UNKNOWN on failure
 */
static inline int get_color_format(enum AVPixelFormat pix_fmt)
{
    switch (pix_fmt) {
    default:
        av_unreachable("Already checked via CODEC_PIXFMTS");
    case AV_PIX_FMT_GRAY10:
        return OAPV_CF_YCBCR400;
    case AV_PIX_FMT_YUV422P10:
        return OAPV_CF_YCBCR422;
    case AV_PIX_FMT_YUV422P12:
        return OAPV_CF_YCBCR422;
    case AV_PIX_FMT_YUV444P10:
        return OAPV_CF_YCBCR444;
    case AV_PIX_FMT_YUV444P12:
        return OAPV_CF_YCBCR444;
    case AV_PIX_FMT_YUVA444P10:
        return OAPV_CF_YCBCR4444;
    case AV_PIX_FMT_YUVA444P12:
        return OAPV_CF_YCBCR4444;
    }
}

static inline int get_chroma_format_idc(enum AVPixelFormat pix_fmt)
{
    switch (pix_fmt) {
    default:
        av_unreachable("Already checked via CODEC_PIXFMTS");
    case AV_PIX_FMT_GRAY10:
        return APV_CHROMA_FORMAT_400;
    case AV_PIX_FMT_YUV422P10:
    case AV_PIX_FMT_YUV422P12:
        return APV_CHROMA_FORMAT_422;
    case AV_PIX_FMT_YUV444P10:
    case AV_PIX_FMT_YUV444P12:
        return APV_CHROMA_FORMAT_444;
    case AV_PIX_FMT_YUVA444P10:
    case AV_PIX_FMT_YUVA444P12:
        return APV_CHROMA_FORMAT_4444;
    }
}

static inline int get_min_profile(enum AVPixelFormat pix_fmt)
{
    switch (pix_fmt) {
    default:
        av_unreachable("Already checked via CODEC_PIXFMTS");
    case AV_PIX_FMT_GRAY10:
        return AV_PROFILE_APV_400_10;
    case AV_PIX_FMT_YUV422P10:
        return AV_PROFILE_APV_422_10;
    case AV_PIX_FMT_YUV422P12:
        return AV_PROFILE_APV_422_12;
    case AV_PIX_FMT_YUV444P10:
        return AV_PROFILE_APV_444_10;
    case AV_PIX_FMT_YUV444P12:
        return AV_PROFILE_APV_444_12;
    case AV_PIX_FMT_YUVA444P10:
        return AV_PROFILE_APV_4444_10;
    case AV_PIX_FMT_YUVA444P12:
        return AV_PROFILE_APV_4444_12;
    }
}

static int profile_is_compatible(enum AVPixelFormat pix_fmt, int profile)
{
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
    const int chroma_format_idc = get_chroma_format_idc(pix_fmt);
    const int bit_depth = desc->comp[0].depth;

    av_assert0(desc);

    switch (profile) {
    case AV_PROFILE_APV_422_10:
        return chroma_format_idc == APV_CHROMA_FORMAT_422 && bit_depth == 10;
    case AV_PROFILE_APV_422_12:
        return chroma_format_idc == APV_CHROMA_FORMAT_422 &&
               bit_depth >= 10 && bit_depth <= 12;
    case AV_PROFILE_APV_444_10:
        return chroma_format_idc >= APV_CHROMA_FORMAT_422 &&
               chroma_format_idc <= APV_CHROMA_FORMAT_444 &&
               bit_depth == 10;
    case AV_PROFILE_APV_444_12:
        return chroma_format_idc >= APV_CHROMA_FORMAT_422 &&
               chroma_format_idc <= APV_CHROMA_FORMAT_444 &&
               bit_depth >= 10 && bit_depth <= 12;
    case AV_PROFILE_APV_4444_10:
        return chroma_format_idc >= APV_CHROMA_FORMAT_422 &&
               chroma_format_idc <= APV_CHROMA_FORMAT_4444 &&
               bit_depth == 10;
    case AV_PROFILE_APV_4444_12:
        return chroma_format_idc >= APV_CHROMA_FORMAT_422 &&
               chroma_format_idc <= APV_CHROMA_FORMAT_4444 &&
               bit_depth >= 10 && bit_depth <= 12;
    case AV_PROFILE_APV_400_10:
        return chroma_format_idc == APV_CHROMA_FORMAT_400 && bit_depth == 10;
    default:
        return 0;
    }
}

static int validate_profile(AVCodecContext *avctx, int profile)
{
    const int minimum = get_min_profile(avctx->pix_fmt);
    const char *profile_name = av_get_profile_name(avctx->codec, profile);
    const char *minimum_name = av_get_profile_name(avctx->codec, minimum);

    if (!profile_is_compatible(avctx->pix_fmt, profile)) {
        av_log(avctx, AV_LOG_ERROR,
               "Profile %s (%d) is incompatible with pixel format %s; minimum compatible profile is %s (%d)\n",
               profile_name ? profile_name : "unknown", profile,
               av_get_pix_fmt_name(avctx->pix_fmt),
               minimum_name ? minimum_name : "unknown", minimum);
        return AVERROR(EINVAL);
    }

    return 0;
}

static oapv_imgb_t *apv_imgb_create(AVCodecContext *avctx)
{
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(avctx->pix_fmt);
    oapv_imgb_t *imgb;
    int input_depth;
    int cfmt;  // color format
    int cs;

    av_assert0(desc);

    imgb = av_mallocz(sizeof(oapv_imgb_t));
    if (!imgb)
        goto fail;

    input_depth = desc->comp[0].depth;
    cfmt = get_color_format(avctx->pix_fmt);
    cs = OAPV_CS_SET(cfmt, input_depth, AV_HAVE_BIGENDIAN);

    imgb->np = desc->nb_components;

    for (int i = 0; i < imgb->np; i++) {
        imgb->w[i]  = avctx->width >> ((i == 1 || i == 2) ? desc->log2_chroma_w : 0);
        imgb->h[i]  = avctx->height;
        imgb->aw[i] = FFALIGN(imgb->w[i], OAPV_MB_W);
        imgb->ah[i] = FFALIGN(imgb->h[i], OAPV_MB_H);
        imgb->s[i]  = imgb->aw[i] * OAPV_CS_GET_BYTE_DEPTH(cs);

        imgb->bsize[i] = imgb->e[i] = imgb->s[i] * imgb->ah[i];
        imgb->a[i] = imgb->baddr[i] = av_mallocz(imgb->bsize[i]);
        if (imgb->a[i] == NULL)
            goto fail;
    }

    imgb->cs = cs;
    imgb->addref = apv_imgb_addref;
    imgb->getref = apv_imgb_getref;
    imgb->release = apv_imgb_release;
    imgb->refcnt = 1;

    return imgb;
fail:
    av_log(avctx, AV_LOG_ERROR, "cannot create image buffer\n");
    if (imgb) {
        for (int i = 0; i < imgb->np; i++)
            av_freep(&imgb->a[i]);
        av_freep(&imgb);
    }
    return NULL;
}

static int apv_metadata_add_payload(const AVCodecContext *avctx, ApvEncContext *apv,
                                    uint32_t group_id, uint32_t type, const void *data, uint32_t size)
{
    oapvm_payload_t *tmp;

    if (apv->nb_payloads >= MAX_METADATA_PAYLOADS || !data || size == 0) {
        return AVERROR_INVALIDDATA;
    }

    tmp = av_realloc_array(apv->payloads, apv->nb_payloads + 1, sizeof(oapvm_payload_t));
    if (!tmp) {
        return AVERROR(ENOMEM);
    }
    apv->payloads = tmp;

    // Copying data into internal buffer
    void *new_data = av_malloc(size);
    if (!new_data) {
        return AVERROR(ENOMEM);
    }
    memcpy(new_data, data, size);

    apv->payloads[apv->nb_payloads].group_id = group_id;
    apv->payloads[apv->nb_payloads].type = type;
    apv->payloads[apv->nb_payloads].size = size;
    apv->payloads[apv->nb_payloads].data = new_data;
    apv->nb_payloads++;

    return 0;
}

/**
 * Populate the liboapv configuration from AVCodecContext and encoder options.
 *
 * AVCodecContext fields are applied first, followed by liboapv private options
 * and finally oapv-params. The APV profile defaults to the minimum profile
 * implied by pix_fmt, and later overrides must remain compatible with that
 * pixel format.
 *
 * @param[in] avctx codec context (AVCodecContext)
 * @param[out] cdsc contains all APV encoder encoder parameters that should be initialized before the encoder is use
 *
 * @return 0 on success, negative error code on failure
 */
static int get_conf(AVCodecContext *avctx, oapve_cdesc_t *cdsc)
{
    ApvEncContext *apv = avctx->priv_data;

    /* initialize apv_param struct with default values */
    int ret = oapve_param_default(&cdsc->param[FRM_IDX]);
    if (OAPV_FAILED(ret)) {
        av_log(avctx, AV_LOG_ERROR, "Cannot set default parameter\n");
        return AVERROR_EXTERNAL;
    }

    /* read options from AVCodecContext */
    cdsc->param[FRM_IDX].w = avctx->width;
    cdsc->param[FRM_IDX].h = avctx->height;

    if (avctx->framerate.num > 0) {
        cdsc->param[FRM_IDX].fps_num = avctx->framerate.num;
        cdsc->param[FRM_IDX].fps_den = avctx->framerate.den;
    } else if (avctx->time_base.num > 0) {
        cdsc->param[FRM_IDX].fps_num = avctx->time_base.den;
        cdsc->param[FRM_IDX].fps_den = avctx->time_base.num;
    }

    cdsc->param[FRM_IDX].profile_idc = get_min_profile(avctx->pix_fmt);
    if (avctx->profile != AV_PROFILE_UNKNOWN) {
        ret = validate_profile(avctx, avctx->profile);
        if (ret < 0)
            return ret;
        cdsc->param[FRM_IDX].profile_idc = avctx->profile;
    }
    cdsc->param[FRM_IDX].preset = apv->preset_id;
    cdsc->param[FRM_IDX].qp = apv->qp;
    if (avctx->bit_rate / 1000 > INT_MAX || avctx->rc_max_rate / 1000 > INT_MAX) {
        av_log(avctx, AV_LOG_ERROR, "bit_rate and rc_max_rate > %d000 is not supported\n", INT_MAX);
        return AVERROR(EINVAL);
    }
    cdsc->param[FRM_IDX].bitrate = (int)(avctx->bit_rate / 1000);
    if (cdsc->param[FRM_IDX].bitrate) {
        if (cdsc->param[FRM_IDX].qp) {
            av_log(avctx, AV_LOG_WARNING, "You cannot set both the bitrate and the QP parameter at the same time.\n"
                                          "If the bitrate is set, the rate control type is set to ABR, which means that the QP value is ignored.\n");
        }
        cdsc->param[FRM_IDX].rc_type = OAPV_RC_ABR;
    }

    cdsc->threads = avctx->thread_count;

    if (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED) {
        cdsc->param[FRM_IDX].color_primaries = avctx->color_primaries;
        cdsc->param[FRM_IDX].color_description_present_flag = 1;
    }

    if (avctx->color_trc != AVCOL_TRC_UNSPECIFIED) {
        cdsc->param[FRM_IDX].transfer_characteristics = avctx->color_trc;
        cdsc->param[FRM_IDX].color_description_present_flag = 1;
    }

    if (avctx->colorspace != AVCOL_SPC_UNSPECIFIED) {
        cdsc->param[FRM_IDX].matrix_coefficients = avctx->colorspace;
        cdsc->param[FRM_IDX].color_description_present_flag = 1;
    }

    if (avctx->color_range != AVCOL_RANGE_UNSPECIFIED) {
        cdsc->param[FRM_IDX].full_range_flag = (avctx->color_range == AVCOL_RANGE_JPEG);
        cdsc->param[FRM_IDX].color_description_present_flag = 1;
    }

    cdsc->max_bs_buf_size = MAX_BS_BUF; /* maximum bitstream buffer size */
    cdsc->max_num_frms = MAX_NUM_FRMS;

    const AVDictionaryEntry *en = NULL;
    while ((en = av_dict_iterate(apv->oapv_params, en))) {
        ret = oapve_param_parse(&cdsc->param[FRM_IDX], en->key, en->value);
        if (ret < 0) {
            av_log(avctx, AV_LOG_WARNING, "Error parsing option '%s = %s'.\n", en->key, en->value);
        }
    }

    ret = validate_profile(avctx, cdsc->param[FRM_IDX].profile_idc);
    if (ret < 0)
        return ret;

    avctx->profile = cdsc->param[FRM_IDX].profile_idc;


    return 0;
}

static int handle_side_data(AVCodecContext *avctx, ApvEncContext *apv)
{
    int size = 0;
    uint8_t payload[64];

    const AVFrameSideData *cll_sd =
        av_frame_side_data_get(avctx->decoded_side_data,
            avctx->nb_decoded_side_data, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
    const AVFrameSideData *mdcv_sd =
        av_frame_side_data_get(avctx->decoded_side_data,
            avctx->nb_decoded_side_data,
            AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);

    if (cll_sd) {
        const AVContentLightMetadata *cll = (AVContentLightMetadata *)cll_sd->data;

        apv->cll.max_cll  = cll->MaxCLL;
        apv->cll.max_fall = cll->MaxFALL;

        oapvm_write_cll(&apv->cll, payload, &size);

        int ret = apv_metadata_add_payload(avctx, apv, 1, OAPV_METADATA_CLL, payload, size);
        if (ret < 0) {
            av_log(avctx, AV_LOG_WARNING, "Error adding content light metadata\n");
            return ret;
        }
    }

    if (mdcv_sd) {
        AVMasteringDisplayMetadata *mdcv = (AVMasteringDisplayMetadata *)mdcv_sd->data;

        // According to APV specification, i = 0, 1, 2 specifies Red, Green, Blue respectively
        apv->mdcv.primary_chromaticity_x[0] = rescale_rational(mdcv->display_primaries[0][0], 50000); // Red X
        apv->mdcv.primary_chromaticity_y[0] = rescale_rational(mdcv->display_primaries[0][1], 50000); // Red Y
        apv->mdcv.primary_chromaticity_x[1] = rescale_rational(mdcv->display_primaries[1][0], 50000); // Green X
        apv->mdcv.primary_chromaticity_y[1] = rescale_rational(mdcv->display_primaries[1][1], 50000); // Green Y
        apv->mdcv.primary_chromaticity_x[2] = rescale_rational(mdcv->display_primaries[2][0], 50000); // Blue X
        apv->mdcv.primary_chromaticity_y[2] = rescale_rational(mdcv->display_primaries[2][1], 50000); // Blue Y

        apv->mdcv.white_point_chromaticity_x = rescale_rational(mdcv->white_point[0], 50000);
        apv->mdcv.white_point_chromaticity_y = rescale_rational(mdcv->white_point[1], 50000);

        apv->mdcv.max_mastering_luminance = rescale_rational(mdcv->max_luminance, 10000);
        apv->mdcv.min_mastering_luminance = rescale_rational(mdcv->min_luminance, 10000);

        oapvm_write_mdcv(&apv->mdcv, payload, &size);

        int ret = apv_metadata_add_payload(avctx, apv, 1, OAPV_METADATA_MDCV, payload, size);
        if (ret < 0) {
            av_log(avctx, AV_LOG_WARNING, "Error adding master display metadata\n");
            return ret;
        }
    }

    return 0;
}

/**
 * @brief Initialize APV codec
 * Create an encoder instance and allocate all the needed resources
 *
 * @param avctx codec context
 * @return 0 on success, negative error code on failure
 */
static av_cold int liboapve_init(AVCodecContext *avctx)
{
    ApvEncContext *apv = avctx->priv_data;
    oapve_cdesc_t *cdsc = &apv->cdsc;
    unsigned char *bs_buf;
    int ret;

    apv->nb_payloads = 0;
    apv->payloads = NULL;

    /* allocate bitstream buffer */
    bs_buf = (unsigned char *)av_malloc(MAX_BS_BUF);
    if (bs_buf == NULL) {
        av_log(avctx, AV_LOG_ERROR, "Cannot allocate bitstream buffer, size=%d\n", MAX_BS_BUF);
        return AVERROR(ENOMEM);
    }
    apv->bitb.addr = bs_buf;
    apv->bitb.bsize = MAX_BS_BUF;

    /* read configurations and set values for created descriptor (APV_CDSC) */
    ret = get_conf(avctx, cdsc);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Cannot get OAPV configuration\n");
        return ret;
    }

    /* create encoder */
    apv->id = oapve_create(cdsc, &ret);
    if (apv->id == NULL) {
        av_log(avctx, AV_LOG_ERROR, "Cannot create OAPV encoder\n");
        if (ret == OAPV_ERR_INVALID_LEVEL)
            av_log(avctx, AV_LOG_ERROR, "Invalid level idc: %d\n", cdsc->param[0].level_idc);
        return AVERROR_EXTERNAL;
    }

    /* create metadata handler */
    apv->mid = oapvm_create(&ret);
    if (apv->mid == NULL || OAPV_FAILED(ret)) {
        av_log(avctx, AV_LOG_ERROR, "cannot create OAPV metadata handler\n");
        return AVERROR_EXTERNAL;
    }

    ret = handle_side_data(avctx, apv);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Failed handling side data! (%s)\n",
               av_err2str(ret));
        return ret;
    }

    if (apv->nb_payloads > 0) {
        ret = oapvm_set_all(apv->mid, apv->payloads, apv->nb_payloads);
        if (OAPV_FAILED(ret)) {
            av_log(avctx, AV_LOG_ERROR, "cannot set metadata\n");
            return AVERROR_EXTERNAL;
        }
    }

    int value = OAPV_CFG_VAL_AU_BS_FMT_NONE;
    int size = 4;
    ret = oapve_config(apv->id, OAPV_CFG_SET_AU_BS_FMT, &value, &size);
    if (OAPV_FAILED(ret)) {
        av_log(avctx, AV_LOG_ERROR, "Failed to set config for using encoder output format\n");
        return AVERROR_EXTERNAL;
    }

    apv->ifrms.frm[FRM_IDX].imgb = apv_imgb_create(avctx);
    if (apv->ifrms.frm[FRM_IDX].imgb == NULL)
        return AVERROR(ENOMEM);
    apv->ifrms.num_frms++;

    /* color description values */
    if (cdsc->param[FRM_IDX].color_description_present_flag) {
        avctx->color_primaries = cdsc->param[FRM_IDX].color_primaries;
        avctx->color_trc = cdsc->param[FRM_IDX].transfer_characteristics;
        avctx->colorspace = cdsc->param[FRM_IDX].matrix_coefficients;
        avctx->color_range = (cdsc->param[FRM_IDX].full_range_flag) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
    }

    return 0;
}

/**
  * Encode raw data frame into APV packet
  *
  * @param[in]  avctx codec context
  * @param[out] avpkt output AVPacket containing encoded data
  * @param[in]  frame AVFrame containing the raw data to be encoded
  * @param[out] got_packet encoder sets to 0 or 1 to indicate that a
  *                         non-empty packet was returned in pkt
  *
  * @return 0 on success, negative error code on failure
  */
static int liboapve_encode(AVCodecContext *avctx, AVPacket *avpkt,
                          const AVFrame *frame, int *got_packet)
{
    ApvEncContext *apv =  avctx->priv_data;
    const oapve_cdesc_t *cdsc = &apv->cdsc;
    oapv_frm_t *frm = &apv->ifrms.frm[FRM_IDX];
    oapv_imgb_t *imgb = frm->imgb;
    int ret;

    av_image_copy2((uint8_t **)imgb->a, imgb->s, frame->data, frame->linesize,
                   frame->format, frame->width, frame->height);

    imgb->ts[0] = frame->pts;

    frm->group_id = 1; // @todo FIX-ME : need to set properly in case of multi-frame
    frm->pbu_type = OAPV_PBU_TYPE_PRIMARY_FRAME;

    ret = oapve_encode(apv->id, &apv->ifrms, apv->mid, &apv->bitb, &apv->stat, NULL);
    if (OAPV_FAILED(ret)) {
        av_log(avctx, AV_LOG_ERROR, "oapve_encode() failed\n");
        return AVERROR_EXTERNAL;
    }

    /* store bitstream */
    if (OAPV_SUCCEEDED(ret) && apv->stat.write > 0) {
        uint8_t *data = apv->bitb.addr;
        int size = apv->stat.write;

        // The encoder may return a "Raw bitstream" formatted AU, including au_size.
        // Discard it as we only need the access_unit() structure.
        if (size > 4 && AV_RB32(data) != APV_SIGNATURE) {
            data += 4;
            size -= 4;
        }

        ret = ff_get_encode_buffer(avctx, avpkt, size, 0);
        if (ret < 0)
            return ret;

        memcpy(avpkt->data, data, size);
        avpkt->pts = avpkt->dts = frame->pts;
        avpkt->flags |= AV_PKT_FLAG_KEY;

        if (cdsc->param[FRM_IDX].qp)
            ff_encode_add_stats_side_data(avpkt, cdsc->param[FRM_IDX].qp * FF_QP2LAMBDA, NULL, 0, AV_PICTURE_TYPE_I);

        *got_packet = 1;
    }

    return 0;
}

/**
 * Destroy the encoder and release all the allocated resources
 *
 * @param avctx codec context
 * @return 0 on success, negative error code on failure
 */
static av_cold int liboapve_close(AVCodecContext *avctx)
{
    ApvEncContext *apv = avctx->priv_data;

    for (unsigned int i = 0; i < apv->nb_payloads; i++) {
        av_freep(&apv->payloads[i].data);
    }
    av_freep(&apv->payloads);

    for (int i = 0; i < apv->ifrms.num_frms; i++) {
        if (apv->ifrms.frm[i].imgb != NULL)
            apv->ifrms.frm[i].imgb->release(apv->ifrms.frm[i].imgb);
        apv->ifrms.frm[i].imgb = NULL;
    }

    if (apv->mid) {
        oapvm_rem_all(apv->mid);
    }

    if (apv->id) {
        oapve_delete(apv->id);
        apv->id = NULL;
    }

    if (apv->mid) {
        oapvm_delete(apv->mid);
        apv->mid = NULL;
    }

    av_freep(&apv->bitb.addr); /* release bitstream buffer */

    return 0;
}

#define OFFSET(x) offsetof(ApvEncContext, x)
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM

static const AVOption liboapv_options[] = {
    { "preset", "Encoding preset for setting encoding speed (optimization level control)", OFFSET(preset_id), AV_OPT_TYPE_INT, { .i64 = OAPV_PRESET_DEFAULT }, OAPV_PRESET_FASTEST, OAPV_PRESET_PLACEBO, VE, .unit = "preset" },
    { "fastest", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_FASTEST }, 0, 0, VE, .unit = "preset" },
    { "fast",    NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_FAST },    0, 0, VE, .unit = "preset" },
    { "medium",  NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_MEDIUM },  0, 0, VE, .unit = "preset" },
    { "slow",    NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_SLOW },    0, 0, VE, .unit = "preset" },
    { "placebo", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_PLACEBO }, 0, 0, VE, .unit = "preset" },
    { "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = OAPV_PRESET_DEFAULT }, 0, 0, VE, .unit = "preset" },

    { "qp", "Quantization parameter value for CQP rate control mode", OFFSET(qp), AV_OPT_TYPE_INT, { .i64 = 32 }, 0, 63, VE, .unit = NULL },
    { "oapv-params",  "Override the apv configuration using a :-separated list of key=value parameters", OFFSET(oapv_params), AV_OPT_TYPE_DICT, { 0 }, 0, 0, VE, .unit = NULL },

    { NULL }
};

static const AVClass liboapve_class = {
    .class_name = "liboapv",
    .item_name  = av_default_item_name,
    .option     = liboapv_options,
    .version    = LIBAVUTIL_VERSION_INT,
};

static const FFCodecDefault liboapve_defaults[] = {
    { "b", "0" },       // bitrate in terms of kilo-bits per second (support for bit-rates from a few hundred Mbps to a few Gbps for 2K, 4K and 8K resolution content)
    { NULL },
};

const FFCodec ff_liboapv_encoder = {
    .p.name             = "liboapv",
    .p.long_name        = NULL_IF_CONFIG_SMALL("liboapv APV"),
    .p.type             = AVMEDIA_TYPE_VIDEO,
    .p.id               = AV_CODEC_ID_APV,
    .init               = liboapve_init,
    FF_CODEC_ENCODE_CB(liboapve_encode),
    .close              = liboapve_close,
    .priv_data_size     = sizeof(ApvEncContext),
    .p.priv_class       = &liboapve_class,
    .defaults           = liboapve_defaults,
    .p.capabilities     = AV_CODEC_CAP_OTHER_THREADS | AV_CODEC_CAP_DR1,
    .p.wrapper_name     = "liboapv",
    .p.profiles         = NULL_IF_CONFIG_SMALL(ff_apv_profiles),
    .caps_internal      = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_NOT_INIT_THREADSAFE,
    CODEC_PIXFMTS(AV_PIX_FMT_GRAY10,
                  AV_PIX_FMT_YUV422P10,  AV_PIX_FMT_YUV422P12,
                  AV_PIX_FMT_YUV444P10,  AV_PIX_FMT_YUV444P12,
                  AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12),
};
