// Copyright (C) 2019 The Android Open Source Project
//
// 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.

#include "host-common/MediaFfmpegVideoHelper.h"
#include "host-common/YuvConverter.h"
#include "android/utils/debug.h"

#define MEDIA_FFMPEG_DEBUG 0

#if MEDIA_FFMPEG_DEBUG
#define MEDIA_DPRINT(fmt, ...)                                              \
    fprintf(stderr, "media-ffmpeg-video-helper: %s:%d " fmt "\n", __func__, \
            __LINE__, ##__VA_ARGS__);
#else
#define MEDIA_DPRINT(fmt, ...)
#endif

namespace android {
namespace emulation {

using FrameInfo = MediaSnapshotState::FrameInfo;
using ColorAspects = MediaSnapshotState::ColorAspects;

MediaFfmpegVideoHelper::MediaFfmpegVideoHelper(int type, int threads)
    : mType(type), mThreadCount(threads) {}

bool MediaFfmpegVideoHelper::init() {
    // standard ffmpeg codec stuff
    avcodec_register_all();
    bool print_all_decoder = false;
#if MEDIA_FFMPEG_DEBUG
    print_all_decoder = false;
#endif
    if (print_all_decoder) {
        AVCodec* current_codec = NULL;

        current_codec = av_codec_next(current_codec);
        while (current_codec != NULL) {
            if (av_codec_is_decoder(current_codec)) {
                MEDIA_DPRINT("codec decoder found %s long name %s",
                             current_codec->name, current_codec->long_name);
            }
            current_codec = av_codec_next(current_codec);
        }
    }

    AVCodecID codecType;
    switch (mType) {
        case 8:
            codecType = AV_CODEC_ID_VP8;
            MEDIA_DPRINT("create vp8 ffmpeg decoder%d", (int)mType);
            break;
        case 9:
            codecType = AV_CODEC_ID_VP9;
            MEDIA_DPRINT("create vp9 ffmpeg decoder%d", (int)mType);
            break;
        case 264:
            codecType = AV_CODEC_ID_H264;
            MEDIA_DPRINT("create h264 ffmpeg decoder%d", (int)mType);
            break;
        default:
            MEDIA_DPRINT("invalid codec %d", (int)mType);
            return false;
    }

    mCodec = NULL;
    mCodec = avcodec_find_decoder(codecType);
    if (!mCodec) {
        MEDIA_DPRINT("cannot find codec");
        return false;
    }

    mCodecCtx = avcodec_alloc_context3(mCodec);

    if (mThreadCount > 1) {
        mCodecCtx->thread_count = std::min(mThreadCount, 4);
        mCodecCtx->thread_type = FF_THREAD_FRAME;
        mCodecCtx->active_thread_type = FF_THREAD_FRAME;
    }
    avcodec_open2(mCodecCtx, mCodec, 0);
    mFrame = av_frame_alloc();

    MEDIA_DPRINT("Successfully created software h264 decoder context %p",
                 mCodecCtx);

    dprint("successfully created ffmpeg video decoder for %s",
           mType == 264 ? "H264" : (mType == 8 ? "VP8" : "VP9"));

    return true;
}

MediaFfmpegVideoHelper::~MediaFfmpegVideoHelper() {
    deInit();
}

void MediaFfmpegVideoHelper::deInit() {
    MEDIA_DPRINT("Destroy %p", this);
    mSavedDecodedFrames.clear();
    if (mCodecCtx) {
        avcodec_flush_buffers(mCodecCtx);
        avcodec_close(mCodecCtx);
        avcodec_free_context(&mCodecCtx);
        mCodecCtx = NULL;
        mCodec = NULL;
    }
    if (mFrame) {
        av_frame_free(&mFrame);
        mFrame = NULL;
    }
}

void MediaFfmpegVideoHelper::copyFrame() {
    int w = mFrame->width;
    int h = mFrame->height;
    mDecodedFrame.resize(w * h * 3 / 2);
    MEDIA_DPRINT("w %d h %d Y line size %d U line size %d V line size %d", w, h,
                 mFrame->linesize[0], mFrame->linesize[1], mFrame->linesize[2]);
    for (int i = 0; i < h; ++i) {
        memcpy(mDecodedFrame.data() + i * w,
               mFrame->data[0] + i * mFrame->linesize[0], w);
    }
    MEDIA_DPRINT("format is %d and NV21 is %d  NV12 is %d", mFrame->format,
                 (int)AV_PIX_FMT_NV21, (int)AV_PIX_FMT_NV12);
    if (mFrame->format == AV_PIX_FMT_NV12) {
        for (int i = 0; i < h / 2; ++i) {
            memcpy(w * h + mDecodedFrame.data() + i * w,
                   mFrame->data[1] + i * mFrame->linesize[1], w);
        }
        YuvConverter<uint8_t> convert8(w, h);
        convert8.UVInterleavedToPlanar(mDecodedFrame.data());
    } else {
        for (int i = 0; i < h / 2; ++i) {
            memcpy(w * h + mDecodedFrame.data() + i * w / 2,
                   mFrame->data[1] + i * mFrame->linesize[1], w / 2);
        }
        for (int i = 0; i < h / 2; ++i) {
            memcpy(w * h + w * h / 4 + mDecodedFrame.data() + i * w / 2,
                   mFrame->data[2] + i * mFrame->linesize[2], w / 2);
        }
    }
    MEDIA_DPRINT("copied Frame and it has presentation time at %lld",
                 (long long)(mFrame->pts));
}

void MediaFfmpegVideoHelper::flush() {
    MEDIA_DPRINT("flushing");
    avcodec_send_packet(mCodecCtx, NULL);
    fetchAllFrames();
    MEDIA_DPRINT("flushing done");
}

void MediaFfmpegVideoHelper::fetchAllFrames() {
    while (true) {
        int retframe = avcodec_receive_frame(mCodecCtx, mFrame);
        if (retframe != 0) {
            MEDIA_DPRINT("no more frames");
            char tmp[1024];
            av_strerror(retframe, tmp, sizeof(tmp));
            MEDIA_DPRINT("WARNING: some unknown error %d: %s", retframe, tmp);
            return;

            break;
        }
        if (mIgnoreDecoderOutput) {
            continue;
        }
        copyFrame();
        MEDIA_DPRINT("save frame");
        mSavedDecodedFrames.push_back(MediaSnapshotState::FrameInfo{
                std::move(mDecodedFrame), std::vector<uint32_t>{},
                (int)mFrame->width, (int)mFrame->height, (uint64_t)mFrame->pts,
                ColorAspects{mFrame->color_primaries, mFrame->color_range,
                             mFrame->color_trc, mFrame->colorspace}});
    }
}

void MediaFfmpegVideoHelper::decode(const uint8_t* data,
                                    size_t len,
                                    uint64_t pts) {
    MEDIA_DPRINT("calling with size %d", (int)len);
    av_init_packet(&mPacket);
    mPacket.data = (unsigned char*)data;
    mPacket.size = len;
    mPacket.pts = pts;
    avcodec_send_packet(mCodecCtx, &mPacket);
    fetchAllFrames();
    MEDIA_DPRINT("done with size %d", (int)len);
}

}  // namespace emulation
}  // namespace android
