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

#include <string.h>

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

#include "h263d_api.h"
#include "h263d_parser.h"

#define h263d_INIT_STREAM_SIZE      SZ_64K

typedef struct {
    // parameter interact with mpp_dec
    MppBufSlots     frame_slots;
    MppBufSlots     packet_slots;
    MppDecCfgSet    *cfg;

    RK_U8           *stream;
    size_t          stream_size;
    MppPacket       task_pkt;
    RK_S64          task_pts;
    RK_S64          task_dts;
    RK_U32          task_eos;

    // runtime parameter
    RK_U32          frame_count;

    // parser context
    H263dParser     parser;
} H263dCtx;

MPP_RET h263d_init(void *dec, ParserCfg *cfg)
{
    H263dParser parser = NULL;
    MppPacket task_pkt = NULL;
    H263dCtx *p;
    MPP_RET ret;
    RK_U8 *stream;
    size_t stream_size = h263d_INIT_STREAM_SIZE;

    if (NULL == dec) {
        mpp_err_f("found NULL intput dec %p cfg %p\n", dec, cfg);
        return MPP_ERR_NULL_PTR;
    }

    stream = mpp_malloc_size(RK_U8, stream_size);
    if (NULL == stream) {
        mpp_err_f("failed to malloc stream buffer size %d\n", stream_size);
        return MPP_ERR_MALLOC;
    }

    ret = mpp_packet_init(&task_pkt, stream, stream_size);
    if (ret) {
        mpp_err_f("failed to create mpp_packet for task\n");
        goto ERR_RET;
    }

    // reset task packet length to zero
    // NOTE: set length must after set pos
    mpp_packet_set_pos(task_pkt, stream);
    mpp_packet_set_length(task_pkt, 0);

    ret = mpp_h263_parser_init(&parser, cfg->frame_slots);
    if (ret) {
        mpp_err_f("failed to init parser\n");
        goto ERR_RET;
    }

    p = (H263dCtx *)dec;
    p->frame_slots  = cfg->frame_slots;
    p->packet_slots = cfg->packet_slots;
    p->cfg          = cfg->cfg;
    p->stream       = stream;
    p->stream_size  = stream_size;
    p->task_pkt     = task_pkt;
    p->parser       = parser;

    return MPP_OK;
ERR_RET:
    if (task_pkt) {
        mpp_packet_deinit(&task_pkt);
    }
    if (stream) {
        mpp_free(stream);
        stream = NULL;
    }
    return ret;
}

MPP_RET h263d_deinit(void *dec)
{
    H263dCtx *p;
    if (NULL == dec) {
        mpp_err_f("found NULL intput\n");
        return MPP_ERR_NULL_PTR;
    }

    p = (H263dCtx *)dec;
    if (p->parser) {
        mpp_h263_parser_deinit(p->parser);
        p->parser = NULL;
    }

    if (p->task_pkt) {
        mpp_packet_deinit(&p->task_pkt);
    }

    if (p->stream) {
        mpp_free(p->stream);
        p->stream = NULL;
    }
    return MPP_OK;
}

MPP_RET h263d_reset(void *dec)
{
    if (NULL == dec) {
        mpp_err_f("found NULL intput\n");
        return MPP_ERR_NULL_PTR;
    }

    H263dCtx *p = (H263dCtx *)dec;
    return mpp_h263_parser_reset(p->parser);
}


MPP_RET h263d_flush(void *dec)
{
    if (NULL == dec) {
        mpp_err_f("found NULL intput\n");
        return MPP_ERR_NULL_PTR;
    }

    H263dCtx *p = (H263dCtx *)dec;
    return mpp_h263_parser_flush(p->parser);
}


MPP_RET h263d_control(void *dec, MpiCmd cmd_type, void *param)
{
    if (NULL == dec) {
        mpp_err_f("found NULL intput\n");
        return MPP_ERR_NULL_PTR;
    }
    (void)cmd_type;
    (void)param;
    return MPP_OK;
}

MPP_RET h263d_prepare(void *dec, MppPacket pkt, HalDecTask *task)
{
    H263dCtx *p;
    RK_U8 *pos;
    size_t length;
    RK_U32 eos;

    if (NULL == dec || NULL == pkt || NULL == task) {
        mpp_err_f("found NULL intput dec %p pkt %p task %p\n", dec, pkt, task);
        return MPP_ERR_NULL_PTR;
    }

    p = (H263dCtx *)dec;
    pos     = mpp_packet_get_pos(pkt);
    length  = mpp_packet_get_length(pkt);
    eos     = mpp_packet_get_eos(pkt);

    if (eos && !length) {
        task->valid = 0;
        task->flags.eos = 1;
        mpp_log("h263d flush eos");
        h263d_flush(dec);
        return MPP_OK;
    }

    if (NULL == p->stream) {
        mpp_err("failed to malloc task buffer for hardware with size %d\n", length);
        return MPP_ERR_UNKNOW;
    }

    if (!p->cfg->base.split_parse) {
        /*
         * Copy packet mode:
         * Decoder's user will insure each packet is one frame for process
         * Parser will just copy packet to the beginning of stream buffer
         */
        if (length > p->stream_size) {
            // NOTE: here we double the buffer length to reduce frequency of realloc
            do {
                p->stream_size <<= 1;
            } while (length > p->stream_size);

            mpp_free(p->stream);
            p->stream = mpp_malloc_size(RK_U8, p->stream_size);
            mpp_assert(p->stream);
            mpp_packet_set_data(p->task_pkt, p->stream);
            mpp_packet_set_size(p->task_pkt, p->stream_size);
        }

        memcpy(p->stream, pos, length);
        mpp_packet_set_pos(p->task_pkt, p->stream);
        mpp_packet_set_length(p->task_pkt, length);
        // set input packet length to 0 here
        // indicate that the input packet has been all consumed
        mpp_packet_set_pos(pkt, pos + length);
        // always use latest pts for current packet
        p->task_pts = mpp_packet_get_pts(pkt);
        p->task_dts = mpp_packet_get_dts(pkt);
        p->task_eos = mpp_packet_get_eos(pkt);
        /* this step will enable the task and goto parse stage */
        task->valid = 1;
    } else {
        /*
         * Split packet mode:
         * Input packet can be any length and no need to be bound of on frame
         * Parser will do split frame operation to find the beginning and end of one frame
         */
        /*
         * NOTE: on split mode total length is the left size plus the new incoming
         *       packet length.
         */
        size_t remain_length = mpp_packet_get_length(p->task_pkt);
        size_t total_length = remain_length + length;
        if (total_length > p->stream_size) {
            RK_U8 *dst;
            do {
                p->stream_size <<= 1;
            } while (length > p->stream_size);

            // NOTE; split mode need to copy remaining stream to new buffer
            dst = mpp_malloc_size(RK_U8, p->stream_size);
            mpp_assert(dst);

            memcpy(dst, p->stream, remain_length);
            mpp_free(p->stream);
            p->stream = dst;
            mpp_packet_set_data(p->task_pkt, p->stream);
            mpp_packet_set_size(p->task_pkt, p->stream_size);
        }

        // start parser split
        if (MPP_OK == mpp_h263_parser_split(p->parser, p->task_pkt, pkt)) {
            task->valid = 1;
        }
        p->task_pts = mpp_packet_get_pts(p->task_pkt);
        p->task_dts = mpp_packet_get_dts(p->task_pkt);
        p->task_eos = mpp_packet_get_eos(p->task_pkt);
    }

    mpp_packet_set_pts(p->task_pkt, p->task_pts);
    mpp_packet_set_dts(p->task_pkt, p->task_dts);
    task->input_packet = p->task_pkt;
    task->flags.eos    = p->task_eos;

    return MPP_OK;
}

MPP_RET h263d_parse(void *dec, HalDecTask *task)
{
    MPP_RET ret;
    H263dCtx *p;

    if (NULL == dec || NULL == task) {
        mpp_err_f("found NULL intput dec %p task %p\n", dec, task);
        return MPP_ERR_NULL_PTR;
    }
    p = (H263dCtx *)dec;
    ret = mpp_h263_parser_decode(p->parser, task->input_packet);
    if (ret) {
        // found error on decoding drop this task and clear remaining length
        task->valid  = 0;
        task->output = -1;
        mpp_packet_set_length(task->input_packet, 0);
        return MPP_NOK;
    }

    mpp_h263_parser_setup_syntax(p->parser, &task->syntax);
    mpp_h263_parser_setup_hal_output(p->parser, &task->output);
    mpp_h263_parser_setup_refer(p->parser, task->refer, MAX_DEC_REF_NUM);
    mpp_h263_parser_update_dpb(p->parser);

    p->frame_count++;

    return MPP_OK;
}

MPP_RET h263d_callback(void *dec, void *err_info)
{
    (void)dec;
    (void)err_info;
    return MPP_OK;
}

const ParserApi api_h263d_parser = {
    .name = "api_h263d_parser",
    .coding = MPP_VIDEO_CodingH263,
    .ctx_size = sizeof(H263dCtx),
    .flag = 0,
    .init = h263d_init,
    .deinit = h263d_deinit,
    .prepare = h263d_prepare,
    .parse = h263d_parse,
    .reset = h263d_reset,
    .flush = h263d_flush,
    .control = h263d_control,
    .callback = h263d_callback,
};

