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

#include <string.h>

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

#include "jpege_debug.h"
#include "jpege_api_v2.h"
#include "jpege_syntax.h"
#include "mpp_enc_cfg_impl.h"
#include "mpp_bitwrite.h"

typedef struct {
    MppEncCfgSet    *cfg;
    JpegeSyntax     syntax;
} JpegeCtx;

#define QUANTIZE_TABLE_SIZE 64

/*
 *  from RFC435 spec.
 */
static const RK_U8 jpege_luma_quantizer[QUANTIZE_TABLE_SIZE] = {
    16, 11, 10, 16, 24, 40, 51, 61,
    12, 12, 14, 19, 26, 58, 60, 55,
    14, 13, 16, 24, 40, 57, 69, 56,
    14, 17, 22, 29, 51, 87, 80, 62,
    18, 22, 37, 56, 68, 109, 103, 77,
    24, 35, 55, 64, 81, 104, 113, 92,
    49, 64, 78, 87, 103, 121, 120, 101,
    72, 92, 95, 98, 112, 100, 103, 99
};

static const RK_U8 jpege_chroma_quantizer[QUANTIZE_TABLE_SIZE] = {
    17, 18, 24, 47, 99, 99, 99, 99,
    18, 21, 26, 66, 99, 99, 99, 99,
    24, 26, 56, 99, 99, 99, 99, 99,
    47, 66, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99
};

RK_U32 jpege_debug = 0;

static MPP_RET jpege_init_v2(void *ctx, EncImplCfg *cfg)
{
    JpegeCtx *p = (JpegeCtx *)ctx;

    mpp_env_get_u32("jpege_debug", &jpege_debug, 0);
    jpege_dbg_func("enter ctx %p\n", ctx);

    p->cfg = cfg->cfg;

    mpp_assert(cfg->coding = MPP_VIDEO_CodingMJPEG);

    {
        /* init default rc config */
        MppEncRcCfg *rc = &p->cfg->rc;
        MppEncJpegCfg *jpeg_cfg = &p->cfg->codec.jpeg;

        rc->fps_in_flex = 0;
        rc->fps_in_num = 30;
        rc->fps_in_denorm = 1;
        rc->fps_out_flex = 0;
        rc->fps_out_num = 30;
        rc->fps_out_denorm = 1;
        rc->rc_mode = MPP_ENC_RC_MODE_VBR;
        /* init default quant */
        jpeg_cfg->quant = 10;
    }

    jpege_dbg_func("leave ctx %p\n", ctx);
    return MPP_OK;
}

static MPP_RET jpege_deinit_v2(void *ctx)
{
    JpegeCtx *p = (JpegeCtx *)ctx;
    MppEncJpegCfg *jpeg_cfg = &p->cfg->codec.jpeg;

    jpege_dbg_func("enter ctx %p\n", ctx);

    MPP_FREE(jpeg_cfg->qtable_y);
    MPP_FREE(jpeg_cfg->qtable_u);
    MPP_FREE(jpeg_cfg->qtable_v);

    jpege_dbg_func("leave ctx %p\n", ctx);
    return MPP_OK;
}

static MPP_RET jpege_proc_prep_cfg(MppEncPrepCfg *dst, MppEncPrepCfg *src)
{
    MPP_RET ret = MPP_OK;
    RK_U32 change = src->change;

    mpp_assert(change);
    if (change) {
        MppEncPrepCfg bak = *dst;
        RK_S32 mirroring;
        RK_S32 rotation;

        if (change & MPP_ENC_PREP_CFG_CHANGE_FORMAT)
            dst->format = src->format;

        if (change & MPP_ENC_PREP_CFG_CHANGE_ROTATION)
            dst->rotation_ext = src->rotation_ext;

        if (change & MPP_ENC_PREP_CFG_CHANGE_MIRRORING)
            dst->mirroring_ext = src->mirroring_ext;

        if (change & MPP_ENC_PREP_CFG_CHANGE_FLIP)
            dst->flip = src->flip;

        // parameter checking
        if (dst->rotation_ext >= MPP_ENC_ROT_BUTT || dst->rotation_ext < 0 ||
            dst->mirroring_ext < 0 || dst->flip < 0) {
            mpp_err("invalid trans: rotation %d, mirroring %d\n", dst->rotation_ext, dst->mirroring_ext);
            ret = MPP_ERR_VALUE;
        }

        rotation = dst->rotation_ext;
        mirroring = dst->mirroring_ext;

        if (dst->flip) {
            mirroring = !mirroring;
            rotation += MPP_ENC_ROT_180;
            rotation &= MPP_ENC_ROT_270;
        }

        dst->mirroring = mirroring;
        dst->rotation = rotation;

        /* jpeg encoder do not have denoise/sharpen feature */

        if ((change & MPP_ENC_PREP_CFG_CHANGE_INPUT) ||
            (change & MPP_ENC_PREP_CFG_CHANGE_ROTATION)) {
            if (dst->rotation == MPP_ENC_ROT_90 || dst->rotation == MPP_ENC_ROT_270) {
                dst->width = src->height;
                dst->height = src->width;
            } else {
                dst->width = src->width;
                dst->height = src->height;
            }
            dst->hor_stride = src->hor_stride;
            dst->ver_stride = src->ver_stride;
        }

        if (dst->width < 16 && dst->width > 8192) {
            mpp_err_f("invalid width %d is not in range [16..8192]\n", dst->width);
            ret = MPP_NOK;
        }

        if (dst->height < 16 && dst->height > 8192) {
            mpp_err_f("invalid height %d is not in range [16..8192]\n", dst->height);
            ret = MPP_NOK;
        }

        dst->change |= change;

        // parameter checking
        if (dst->rotation == MPP_ENC_ROT_90 || dst->rotation == MPP_ENC_ROT_270) {
            if (dst->height > dst->hor_stride || dst->width > dst->ver_stride) {
                mpp_err("invalid size w:h [%d:%d] stride [%d:%d]\n",
                        dst->width, dst->height, dst->hor_stride, dst->ver_stride);
                ret = MPP_ERR_VALUE;
            }
        } else {
            if (dst->width > dst->hor_stride || dst->height > dst->ver_stride) {
                mpp_err("invalid size w:h [%d:%d] stride [%d:%d]\n",
                        dst->width, dst->height, dst->hor_stride, dst->ver_stride);
                ret = MPP_ERR_VALUE;
            }
        }

        if (ret) {
            mpp_err_f("failed to accept new prep config\n");
            *dst = bak;
        } else {
            jpege_dbg_ctrl("MPP_ENC_SET_PREP_CFG w:h [%d:%d] stride [%d:%d]\n",
                           dst->width, dst->height,
                           dst->hor_stride, dst->ver_stride);
        }
    }

    return ret;
}

/* gen quantizer table by q_factor according to RFC2435 spec. */
static MPP_RET jpege_gen_qt_by_qfactor(MppEncJpegCfg *cfg, RK_S32 *factor)
{
    MPP_RET ret = MPP_OK;
    RK_U32 q, qfactor = *factor;
    RK_U32 i;
    RK_U8 *qtable_y = NULL;
    RK_U8 *qtable_c = NULL;

    if (!cfg->qtable_y)
        cfg->qtable_y = mpp_malloc(RK_U8, QUANTIZE_TABLE_SIZE);
    if (!cfg->qtable_u)
        cfg->qtable_u = mpp_malloc(RK_U8, QUANTIZE_TABLE_SIZE);

    if (NULL == cfg->qtable_y || NULL == cfg->qtable_u) {
        mpp_err_f("qtable is null, malloc err");
        return MPP_ERR_MALLOC;
    }
    qtable_y = cfg->qtable_y;
    qtable_c = cfg->qtable_u;

    if (qfactor < 50)
        q = 5000 / qfactor;
    else
        q = 200 - (qfactor << 1);

    for (i = 0; i < QUANTIZE_TABLE_SIZE; i++) {
        RK_S16 lq = (jpege_luma_quantizer[i] * q + 50) / 100;
        RK_S16 cq = (jpege_chroma_quantizer[i] * q + 50) / 100;

        /* Limit the quantizers to 1 <= q <= 255 */
        qtable_y[i] = MPP_CLIP3(1, 255, lq);
        qtable_c[i] = MPP_CLIP3(1, 255, cq);
    }
    return ret;
}

static MPP_RET jpege_proc_jpeg_cfg(MppEncJpegCfg *dst, MppEncJpegCfg *src, MppEncRcCfg *rc)
{
    MPP_RET ret = MPP_OK;
    RK_U32 change = src->change;

    if (change) {
        MppEncJpegCfg bak = *dst;

        if (change & MPP_ENC_JPEG_CFG_CHANGE_QP) {
            dst->quant = src->quant;
            if (dst->quant < 0 || dst->quant > 10) {
                mpp_err_f("invalid quality level %d is not in range [0..10] set to default 8\n");
                dst->quant = 8;
            }

            if (rc->rc_mode != MPP_ENC_RC_MODE_FIXQP) {
                mpp_log("setup quant %d change mode %s fixqp", dst->quant,
                        strof_rc_mode(rc->rc_mode));
                rc->rc_mode = MPP_ENC_RC_MODE_FIXQP;
            }
        } else if (change & MPP_ENC_JPEG_CFG_CHANGE_QFACTOR) {
            if (src->q_factor < 1 || src->q_factor > 99) {
                mpp_err_f("q_factor out of range, default set 80\n");
                src->q_factor = 80;
            }
            if (dst->q_factor != src->q_factor)
                ret = jpege_gen_qt_by_qfactor(dst, &src->q_factor);

            dst->q_factor = src->q_factor;
            if (src->qf_min < 1 || src->qf_min > 99) {
                mpp_err_f("qf_min out of range, default set 1\n");
                src->qf_min = 1;
            }
            dst->qf_min = src->qf_min;
            if (src->qf_max < 1 || src->qf_max > 99) {
                mpp_err_f("qf_max out of range, default set 99\n");
                src->qf_max = 99;
            }
            dst->qf_max = src->qf_max;
            jpege_dbg_input("q_factor %d, qf_min %d, qf_max %d\n",
                            dst->q_factor, dst->qf_min, dst->qf_max);
        } else if (change & MPP_ENC_JPEG_CFG_CHANGE_QTABLE) {
            if (src->qtable_y && src->qtable_u && src->qtable_v) {
                if (NULL == dst->qtable_y)
                    dst->qtable_y = mpp_malloc(RK_U8, QUANTIZE_TABLE_SIZE);
                if (NULL == dst->qtable_u)
                    dst->qtable_u = mpp_malloc(RK_U8, QUANTIZE_TABLE_SIZE);

                if (NULL == dst->qtable_y || NULL == dst->qtable_u) {
                    mpp_err_f("qtable is null, malloc err\n");
                    return MPP_ERR_MALLOC;
                }
                /* check table value */
                if (src->qtable_u != src->qtable_v) {
                    RK_U32 i;

                    for (i = 0; i < QUANTIZE_TABLE_SIZE; i++) {
                        if (src->qtable_u[i] != src->qtable_v[i]) {
                            RK_U32 j;

                            jpege_dbg_input("qtable_u and qtable_v are different, use qtable_u\n");
                            for (j = 0; j < QUANTIZE_TABLE_SIZE; j++)
                                jpege_dbg_input("qtable_u[%d] %d qtable_v[%d] %d\n",
                                                j, src->qtable_u[j], j, src->qtable_v[j]);
                            break;
                        }
                    }
                }
                /* default use one chroma qtable, select qtable_u */
                memcpy(dst->qtable_y, src->qtable_y, QUANTIZE_TABLE_SIZE);
                memcpy(dst->qtable_u, src->qtable_u, QUANTIZE_TABLE_SIZE);

                if (rc->rc_mode != MPP_ENC_RC_MODE_FIXQP) {
                    mpp_log("setup qtable will change mode %s fixqp",
                            strof_rc_mode(rc->rc_mode));
                    rc->rc_mode = MPP_ENC_RC_MODE_FIXQP;
                }
            } else {
                mpp_err_f("invalid qtable y %p u %p v %p\n",
                          src->qtable_y, src->qtable_u, src->qtable_v);
                ret = MPP_ERR_NULL_PTR;
            }
        }

        if (ret) {
            mpp_err_f("failed to accept new rc config\n");
            *dst = bak;
        } else {
            jpege_dbg_ctrl("MPP_ENC_SET_CODEC_CFG change 0x%x jpeg quant %d q_factor %d\n",
                           change, dst->quant, dst->q_factor);
            dst->change = src->change;
        }

        dst->change = src->change;
    }

    return ret;
}

static MPP_RET jpege_proc_split_cfg(MppEncSliceSplit *dst, MppEncSliceSplit *src)
{
    MPP_RET ret = MPP_OK;
    RK_U32 change = src->change;

    if (change & MPP_ENC_SPLIT_CFG_CHANGE_MODE) {
        dst->split_mode = src->split_mode;
        dst->split_arg = src->split_arg;
    }

    if (change & MPP_ENC_SPLIT_CFG_CHANGE_ARG)
        dst->split_arg = src->split_arg;

    dst->change |= change;

    return ret;
}

static MPP_RET jpege_proc_cfg(void *ctx, MpiCmd cmd, void *param)
{
    JpegeCtx *p = (JpegeCtx *)ctx;
    MppEncCfgSet *cfg = p->cfg;
    MPP_RET ret = MPP_OK;

    jpege_dbg_func("enter ctx %p cmd %x param %p\n", ctx, cmd, param);

    switch (cmd) {
    case MPP_ENC_SET_CFG : {
        MppEncCfgImpl *impl = (MppEncCfgImpl *)param;
        MppEncCfgSet *src = &impl->cfg;

        if (src->prep.change) {
            ret |= jpege_proc_prep_cfg(&cfg->prep, &src->prep);
            src->prep.change = 0;
        }
        if (src->codec.jpeg.change) {
            ret |= jpege_proc_jpeg_cfg(&cfg->codec.jpeg, &src->codec.jpeg, &cfg->rc);
            src->codec.jpeg.change = 0;
        }
        if (src->split.change) {
            ret |= jpege_proc_split_cfg(&cfg->split, &src->split);
            src->split.change = 0;
        }
    } break;
    case MPP_ENC_SET_PREP_CFG : {
        ret = jpege_proc_prep_cfg(&cfg->prep, param);
    } break;
    case MPP_ENC_SET_CODEC_CFG : {
        MppEncCodecCfg *codec = (MppEncCodecCfg *)param;
        ret = jpege_proc_jpeg_cfg(&cfg->codec.jpeg, &codec->jpeg, &cfg->rc);
    } break;
    case MPP_ENC_SET_IDR_FRAME :
    case MPP_ENC_SET_OSD_PLT_CFG :
    case MPP_ENC_SET_OSD_DATA_CFG :
    case MPP_ENC_GET_SEI_DATA :
    case MPP_ENC_SET_SEI_CFG : {
    } break;
    default:
        mpp_err_f("No correspond cmd(%08x) found, and can not config!", cmd);
        ret = MPP_NOK;
        break;
    }

    jpege_dbg_func("leave ret %d\n", ret);
    return ret;
}

static MPP_RET jpege_start(void *ctx, HalEncTask *task)
{
    JpegeCtx *p = (JpegeCtx *)ctx;
    JpegeSyntax syntax = p->syntax;
    MppPacket pkt = task->packet;
    RK_U8 *ptr = mpp_packet_get_pos(pkt);
    size_t buf_size = mpp_packet_get_size(pkt);
    RK_S32 size = 0;
    MppWriteCtx bit_ctx;
    MppWriteCtx *bits = &bit_ctx;

    mpp_writer_init(bits, ptr, buf_size);

    /* add SOI and APP0 data */
    /* SOI */
    mpp_writer_put_raw_bits(bits, 0xFFD8, 16);
    /* APP0 */
    mpp_writer_put_raw_bits(bits, 0xFFE0, 16);
    /* length */
    mpp_writer_put_raw_bits(bits, 0x0010, 16);
    /* "JFIF" ID */
    /* Ident1 */
    mpp_writer_put_raw_bits(bits, 0x4A46, 16);
    /* Ident2 */
    mpp_writer_put_raw_bits(bits, 0x4946, 16);
    /* Ident3 */
    mpp_writer_put_raw_bits(bits, 0x00, 8);
    /* Version */
    mpp_writer_put_raw_bits(bits, 0x0102, 16);

    if (syntax.density_x && syntax.density_y) {
        /* Units */
        mpp_writer_put_raw_bits(bits, syntax.units_type, 8);
        /* Xdensity */
        mpp_writer_put_raw_bits(bits, syntax.density_x, 16);
        /* Ydensity */
        mpp_writer_put_raw_bits(bits, syntax.density_y, 16);
    } else {
        /* Units */
        mpp_writer_put_raw_bits(bits, 0, 8);
        /* Xdensity */
        mpp_writer_put_raw_bits(bits, 0, 8);
        mpp_writer_put_raw_bits(bits, 1, 8);
        /* Ydensity */
        mpp_writer_put_raw_bits(bits, 1, 16);
    }
    /* XThumbnail */
    mpp_writer_put_raw_bits(bits, 0x00, 8);
    /* YThumbnail */
    mpp_writer_put_raw_bits(bits, 0x00, 8);
    /* Do NOT write thumbnail */
    size = mpp_writer_bytes(bits);
    mpp_packet_set_length(pkt, size);
    task->length += size;

    return MPP_OK;
}

static MPP_RET jpege_proc_hal(void *ctx, HalEncTask *task)
{
    JpegeCtx *p = (JpegeCtx *)ctx;
    MppFrame frame = task->frame;
    JpegeSyntax *syntax = &p->syntax;
    MppEncCfgSet *cfg = p->cfg;
    MppEncPrepCfg *prep = &cfg->prep;
    MppEncCodecCfg *codec = &cfg->codec;
    MppEncSliceSplit *split = &cfg->split;

    jpege_dbg_func("enter ctx %p\n", ctx);

    syntax->width       = prep->width;
    syntax->height      = prep->height;
    syntax->hor_stride  = prep->hor_stride;
    syntax->ver_stride  = prep->ver_stride;
    syntax->mcu_w       = MPP_ALIGN(prep->width, 16) / 16;
    syntax->mcu_h       = MPP_ALIGN(prep->height, 16) / 16;
    syntax->format      = prep->format;
    syntax->color       = prep->color;
    syntax->rotation    = prep->rotation;
    syntax->mirroring   = prep->mirroring;
    syntax->offset_x    = mpp_frame_get_offset_x(frame);
    syntax->offset_y    = mpp_frame_get_offset_y(frame);
    syntax->quality     = codec->jpeg.quant;
    syntax->q_factor    = codec->jpeg.q_factor;
    syntax->qf_min      = codec->jpeg.qf_min;
    syntax->qf_max      = codec->jpeg.qf_max;
    syntax->qtable_y    = codec->jpeg.qtable_y;
    syntax->qtable_c    = codec->jpeg.qtable_u;
    syntax->part_rows   = 0;
    syntax->restart_ri  = 0;
    syntax->low_delay   = 0;

    if (split->split_mode) {
        RK_U32 mb_h = MPP_ALIGN(prep->height, 16) / 16;
        RK_U32 part_rows = 0;

        if (split->split_mode == MPP_ENC_SPLIT_BY_CTU) {
            RK_U32 part_mbs = split->split_arg;
            RK_U32 mb_w = MPP_ALIGN(prep->width, 16) / 16;
            RK_U32 mb_all = mb_w * mb_h;

            if (part_mbs > 0 && part_mbs <= mb_all) {
                part_rows = (part_mbs + mb_w - 1) / mb_w;
                if (part_rows >= mb_h)
                    part_rows = 0;
            } else {
                mpp_err_f("warning: invalid split arg %d > max %d\n",
                          part_mbs, mb_all);
            }
        } else {
            mpp_err_f("warning: only mcu split is supported\n");
        }

        if (part_rows) {
            syntax->part_rows   = part_rows;
            syntax->restart_ri  = syntax->mcu_w * part_rows;
            syntax->low_delay   = cfg->base.low_delay && part_rows;
        }
    }

    task->valid = 1;
    task->syntax.data = syntax;
    task->syntax.number = 1;

    jpege_dbg_func("leave ctx %p\n", ctx);
    return MPP_OK;
}

static MPP_RET jpege_add_Prefix(MppPacket pkt, RK_S32 *len, RK_U8 uuid[16],
                                const void *data, RK_S32 size)
{
    RK_U8 *ptr = mpp_packet_get_pos(pkt);
    size_t length = mpp_packet_get_length(pkt);
    size_t buf_size = mpp_packet_get_size(pkt);
    MppWriteCtx bit_ctx;
    MppWriteCtx *bits = &bit_ctx;
    const RK_U8 *user_data = data;
    RK_S32 i = 0, app_size = 0;

    mpp_writer_init(bits, ptr + length, buf_size - length);

    if ((size > 8) && user_data[0] == 0xFF && user_data[1] == 0xE1 &&
        user_data[4] == 0x45 && user_data[5] == 0x78 &&
        user_data[6] == 0x69 && user_data[7] == 0x66) {
        jpege_dbg_func("write EXIF data, total length %d\n", size);
    } else if ((size > 8) && user_data[0] == 0xFF && user_data[1] == 0xE2 &&
               user_data[4] == 0x4D && user_data[5] == 0x50 &&
               user_data[6] == 0x46 && user_data[7] == 0x00) {
        jpege_dbg_func("write MPF data, total length %d\n", size);
    } else {
        /* add user data to APP7 */
        mpp_writer_put_raw_bits(bits, 0xFFE7, 16);
        /* length */
        mpp_writer_put_raw_bits(bits, size + 2, 16);
    }

    /* data */
    for (i = 0; i < size; i++)
        mpp_writer_put_raw_bits(bits, user_data[i], 8);

    app_size = mpp_writer_bytes(bits);
    *len = app_size;
    length += app_size;
    mpp_packet_set_length(pkt, length);
    (void)uuid;
    return MPP_OK;
}

const EncImplApi api_jpege = {
    .name       = "jpege_control",
    .coding     = MPP_VIDEO_CodingMJPEG,
    .ctx_size   = sizeof(JpegeCtx),
    .flag       = 0,
    .init       = jpege_init_v2,
    .deinit     = jpege_deinit_v2,
    .proc_cfg   = jpege_proc_cfg,
    .gen_hdr    = NULL,
    .start      = jpege_start,
    .proc_dpb   = NULL,
    .proc_hal   = jpege_proc_hal,
    .add_prefix = jpege_add_Prefix,
    .sw_enc     = NULL,
};
