/*
 * 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 "config_components.h"

#include "libavutil/avassert.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"
#include "libavutil/log.h"
#include "libavutil/mem.h"
#include "decode.h"
#include "lcevcdec.h"

#if CONFIG_LIBLCEVC_DEC
static LCEVC_ColorFormat map_format(int format)
{
    switch (format) {
    case AV_PIX_FMT_YUV420P:
        return LCEVC_I420_8;
    case AV_PIX_FMT_YUV420P10:
        return LCEVC_I420_10_LE;
    case AV_PIX_FMT_NV12:
        return LCEVC_NV12_8;
    case AV_PIX_FMT_NV21:
        return LCEVC_NV21_8;
    case AV_PIX_FMT_GRAY8:
        return LCEVC_GRAY_8;
    }

    return LCEVC_ColorFormat_Unknown;
}

static int alloc_base_frame(void *logctx, FFLCEVCContext *lcevc,
                            const AVFrame *frame, LCEVC_PictureHandle *picture)
{
    LCEVC_PictureDesc desc;
    LCEVC_ColorFormat fmt = map_format(frame->format);
    LCEVC_PictureLockHandle lock;
    uint8_t *data[4] = { NULL };
    int linesizes[4] = { 0 };
    uint32_t planes;
    LCEVC_ReturnCode res;

    res = LCEVC_DefaultPictureDesc(&desc, fmt, frame->width, frame->height);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    desc.cropTop    = frame->crop_top;
    desc.cropBottom = frame->crop_bottom;
    desc.cropLeft   = frame->crop_left;
    desc.cropRight  = frame->crop_right;
    desc.sampleAspectRatioNum  = frame->sample_aspect_ratio.num;
    desc.sampleAspectRatioDen  = frame->sample_aspect_ratio.den;

    /* Allocate LCEVC Picture */
    res = LCEVC_AllocPicture(lcevc->decoder, &desc, picture);
    if (res != LCEVC_Success) {
        return AVERROR_EXTERNAL;
    }
    res = LCEVC_LockPicture(lcevc->decoder, *picture, LCEVC_Access_Write, &lock);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    res = LCEVC_GetPicturePlaneCount(lcevc->decoder, *picture, &planes);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    for (unsigned i = 0; i < planes; i++) {
        LCEVC_PicturePlaneDesc plane;

        res = LCEVC_GetPictureLockPlaneDesc(lcevc->decoder, lock, i, &plane);
        if (res != LCEVC_Success)
            return AVERROR_EXTERNAL;

        data[i] = plane.firstSample;
        linesizes[i] = plane.rowByteStride;
    }

    av_image_copy2(data, linesizes, frame->data, frame->linesize,
                   frame->format, frame->width, frame->height);

    res = LCEVC_UnlockPicture(lcevc->decoder, lock);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    return 0;
}

static int alloc_enhanced_frame(void *logctx, FFLCEVCFrame *frame_ctx,
                                LCEVC_PictureHandle *picture)
{
    FFLCEVCContext *lcevc = frame_ctx->lcevc;
    LCEVC_PictureDesc desc ;
    LCEVC_ColorFormat fmt = map_format(frame_ctx->frame->format);
    LCEVC_PicturePlaneDesc planes[4] = { 0 };
    LCEVC_ReturnCode res;

    res = LCEVC_DefaultPictureDesc(&desc, fmt, frame_ctx->frame->width, frame_ctx->frame->height);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    /* Set plane description */
    for (int i = 0; i < 4; i++) {
        planes[i].firstSample = frame_ctx->frame->data[i];
        planes[i].rowByteStride = frame_ctx->frame->linesize[i];
    }

    /* Allocate LCEVC Picture */
    res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture);
    if (res != LCEVC_Success) {
        return AVERROR_EXTERNAL;
    }
    return 0;
}

static int lcevc_send_frame(void *logctx, FFLCEVCFrame *frame_ctx, const AVFrame *in)
{
    FFLCEVCContext *lcevc = frame_ctx->lcevc;
    const AVFrameSideData *sd = av_frame_get_side_data(in, AV_FRAME_DATA_LCEVC);
    LCEVC_PictureHandle picture;
    LCEVC_ReturnCode res;
    int ret = 0;

    if (!sd)
        return 1;

    res = LCEVC_SendDecoderEnhancementData(lcevc->decoder, in->pts, 0, sd->data, sd->size);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    ret = alloc_base_frame(logctx, lcevc, in, &picture);
    if (ret < 0)
        return ret;

    res = LCEVC_SendDecoderBase(lcevc->decoder, in->pts, 0, picture, -1, NULL);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    memset(&picture, 0, sizeof(picture));
    ret = alloc_enhanced_frame(logctx, frame_ctx, &picture);
    if (ret < 0)
        return ret;

    res = LCEVC_SendDecoderPicture(lcevc->decoder, picture);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    return 0;
}

static int generate_output(void *logctx, FFLCEVCFrame *frame_ctx, AVFrame *out)
{
    FFLCEVCContext *lcevc = frame_ctx->lcevc;
    LCEVC_PictureDesc desc;
    LCEVC_DecodeInformation info;
    LCEVC_PictureHandle picture;
    LCEVC_ReturnCode res;

    res = LCEVC_ReceiveDecoderPicture(lcevc->decoder, &picture, &info);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    res = LCEVC_GetPictureDesc(lcevc->decoder, picture, &desc);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    out->crop_top = desc.cropTop;
    out->crop_bottom = desc.cropBottom;
    out->crop_left = desc.cropLeft;
    out->crop_right = desc.cropRight;
    out->sample_aspect_ratio.num = desc.sampleAspectRatioNum;
    out->sample_aspect_ratio.den = desc.sampleAspectRatioDen;

    av_frame_copy_props(frame_ctx->frame, out);
    av_frame_unref(out);
    av_frame_move_ref(out, frame_ctx->frame);

    out->width = desc.width + out->crop_left + out->crop_right;
    out->height = desc.height + out->crop_top + out->crop_bottom;

    res = LCEVC_FreePicture(lcevc->decoder, picture);
    if (res != LCEVC_Success)
        return AVERROR_EXTERNAL;

    return 0;
}

static int lcevc_receive_frame(void *logctx, FFLCEVCFrame *frame_ctx, AVFrame *out)
{
    FFLCEVCContext *lcevc = frame_ctx->lcevc;
    LCEVC_PictureHandle picture;
    LCEVC_ReturnCode res;
    int ret;

    ret = generate_output(logctx, frame_ctx, out);
    if (ret < 0)
        return ret;

    while (1) {
        res = LCEVC_ReceiveDecoderBase (lcevc->decoder, &picture);
        if (res != LCEVC_Success && res != LCEVC_Again)
            return AVERROR_EXTERNAL;

        if (res == LCEVC_Again)
            break;

        res = LCEVC_FreePicture(lcevc->decoder, picture);
        if (res != LCEVC_Success)
            return AVERROR_EXTERNAL;
    }

    return 0;
}

static void event_callback(LCEVC_DecoderHandle dec, LCEVC_Event event,
    LCEVC_PictureHandle pic, const LCEVC_DecodeInformation *info,
    const uint8_t *data, uint32_t size, void *logctx)
{
    switch (event) {
    case LCEVC_Log:
        av_log(logctx, AV_LOG_INFO, "%s\n", data);
        break;
    default:
        break;
    }
}

static void lcevc_free(FFRefStructOpaque unused, void *obj)
{
    FFLCEVCContext *lcevc = obj;
    if (lcevc->initialized)
        LCEVC_DestroyDecoder(lcevc->decoder);
    memset(lcevc, 0, sizeof(*lcevc));
}
#endif

static int lcevc_init(FFLCEVCContext *lcevc, void *logctx)
{
#if CONFIG_LIBLCEVC_DEC
    LCEVC_AccelContextHandle dummy = { 0 };
    const int32_t event = LCEVC_Log;

    if (LCEVC_CreateDecoder(&lcevc->decoder, dummy) != LCEVC_Success) {
        av_log(logctx, AV_LOG_ERROR, "Failed to create LCEVC decoder\n");
        return AVERROR_EXTERNAL;
    }

    LCEVC_ConfigureDecoderInt(lcevc->decoder, "log_level", 4);
    LCEVC_ConfigureDecoderIntArray(lcevc->decoder, "events", 1, &event);
    LCEVC_SetDecoderEventCallback(lcevc->decoder, event_callback, logctx);

    if (LCEVC_InitializeDecoder(lcevc->decoder) != LCEVC_Success) {
        av_log(logctx, AV_LOG_ERROR, "Failed to initialize LCEVC decoder\n");
        LCEVC_DestroyDecoder(lcevc->decoder);
        return AVERROR_EXTERNAL;
    }

#endif
    lcevc->initialized = 1;

    return 0;
}

int ff_lcevc_process(void *logctx, AVFrame *frame)
{
    FrameDecodeData  *fdd = (FrameDecodeData*)frame->private_ref->data;
    FFLCEVCFrame *frame_ctx = fdd->post_process_opaque;
    FFLCEVCContext *lcevc = frame_ctx->lcevc;
    int ret;

    if (!lcevc->initialized) {
        ret = lcevc_init(lcevc, logctx);
        if (ret < 0)
            return ret;
    }

#if CONFIG_LIBLCEVC_DEC
    av_assert0(frame_ctx->frame);


    ret = lcevc_send_frame(logctx, frame_ctx, frame);
    if (ret)
        return ret < 0 ? ret : 0;

    lcevc_receive_frame(logctx, frame_ctx, frame);
    if (ret < 0)
        return ret;

    av_frame_remove_side_data(frame, AV_FRAME_DATA_LCEVC);
#endif

    return 0;
}

int ff_lcevc_alloc(FFLCEVCContext **plcevc)
{
    FFLCEVCContext *lcevc = NULL;
#if CONFIG_LIBLCEVC_DEC
    lcevc = ff_refstruct_alloc_ext(sizeof(*lcevc), 0, NULL, lcevc_free);
    if (!lcevc)
        return AVERROR(ENOMEM);
#endif
    *plcevc = lcevc;
    return 0;
}

void ff_lcevc_unref(void *opaque)
{
    FFLCEVCFrame *lcevc = opaque;
    ff_refstruct_unref(&lcevc->lcevc);
    av_frame_free(&lcevc->frame);
    av_free(opaque);
}
