// 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.

#pragma once

#include "host-common/GoldfishMediaDefs.h"
#include "host-common/MediaH264Decoder.h"

#include <stddef.h>
#include <vector>

namespace android {
namespace emulation {

class MediaH264DecoderDefault;

// this is an interface for platform specific implementations
// such as VideoToolBox, CUVID, etc
class MediaH264DecoderPlugin {
public:
    using PixelFormat = MediaH264Decoder::PixelFormat;
    using Err = MediaH264Decoder::Err;

    virtual void initH264Context(void* ptr) = 0;
    virtual void reset(void* ptr) = 0;
    virtual MediaH264DecoderPlugin* clone() = 0;
    virtual void destroyH264Context() = 0;
    virtual void decodeFrame(void* ptr) = 0;
    virtual void flush(void* ptr) = 0;
    virtual void getImage(void* ptr) = 0;
    virtual void sendMetadata(void* ptr) = 0;

    virtual void save(base::Stream* stream) const {};
    virtual bool load(base::Stream* stream) {return true;};

    MediaH264DecoderPlugin() = default;
    virtual ~MediaH264DecoderPlugin() = default;

protected:
    // solely for snapshot save load purpose
    enum {
        PLUGIN_TYPE_NONE = 0,
        PLUGIN_TYPE_FFMPEG = 1,
        PLUGIN_TYPE_CUVID = 2,
        PLUGIN_TYPE_VIDEO_TOOL_BOX = 3,
        PLUGIN_TYPE_VIDEO_TOOL_BOX_PROXY = 4,
        PLUGIN_TYPE_GENERIC = 5,

    };

    // this is required by save/load
    virtual int type() const { return PLUGIN_TYPE_NONE; }

    friend MediaH264DecoderDefault;

public:
    struct ColorAspects {
        unsigned int primaries;
        unsigned int range;
        unsigned int transfer;
        unsigned int space;
    };

    struct PacketInfo {
        std::vector<uint8_t> data;
        uint64_t pts;
    };

    struct FrameInfo {
        std::vector<uint8_t> data;
        int width;
        int height;
        ColorAspects color;
        uint64_t pts;
    };

    struct SnapshotState {
        std::vector<uint8_t> sps;  // sps NALU
        std::vector<uint8_t> pps;  // pps NALU
        std::vector<PacketInfo> savedPackets;
        FrameInfo savedDecodedFrame;  // only one or nothing
        std::vector<FrameInfo> savedFrames;

        void saveSps(std::vector<uint8_t> xsps) { sps = std::move(xsps); }

        void savePps(std::vector<uint8_t> xpps) { pps = std::move(xpps); }
        bool savePacket(std::vector<uint8_t> data, uint64_t pts = 0) {
            if (pts > 0 && savedPackets.size() > 0 &&
                pts == savedPackets.back().pts) {
                return false;
            }
            PacketInfo pkt{data, pts};
            savedPackets.push_back(std::move(pkt));
            return true;
        }
        bool savePacket(const uint8_t* frame, size_t size, uint64_t pts = 0) {
            if (pts > 0 && savedPackets.size() > 0 &&
                pts == savedPackets.back().pts) {
                return false;
            }
            std::vector<uint8_t> vec;
            vec.assign(frame, frame+size);
            PacketInfo pkt{vec, pts};
            savedPackets.push_back(std::move(pkt));
            return true;
        }

        void saveDecodedFrame(std::vector<uint8_t> data,
                              int width = 0,
                              int height = 0,
                              ColorAspects xcolor = ColorAspects{},
                              uint64_t pts = 0) {
            FrameInfo frame{data, width, height, xcolor, pts};
            savedDecodedFrame = std::move(frame);
            savedFrames.push_back(savedDecodedFrame);
        }

        void saveVec(base::Stream* stream,
                     const std::vector<uint8_t>& vec) const {
            stream->putBe32(vec.size());
            if (!vec.empty()) {
                stream->write(vec.data(), vec.size());
            }
        }

        void saveFrameInfo(base::Stream* stream, const FrameInfo& frame) const {
            saveVec(stream, frame.data);
            stream->putBe32(frame.width);
            stream->putBe32(frame.height);
            saveColor(stream, frame.color);
            stream->putBe64(frame.pts);
        }

        void loadFrameInfo(base::Stream* stream, FrameInfo& frame) {
            loadVec(stream, frame.data);
            frame.width = stream->getBe32();
            frame.height = stream->getBe32();
            loadColor(stream, frame.color);
            frame.pts = stream->getBe64();
        }

        void savePacketInfo(base::Stream* stream, const PacketInfo& pkt) const {
            saveVec(stream, pkt.data);
            stream->putBe64(pkt.pts);
        }

        void loadPacketInfo(base::Stream* stream, PacketInfo& pkt) {
            loadVec(stream, pkt.data);
            pkt.pts = stream->getBe64();
        }

        void saveColor(base::Stream* stream, const ColorAspects& color) const {
            stream->putBe32(color.primaries);
            stream->putBe32(color.range);
            stream->putBe32(color.transfer);
            stream->putBe32(color.space);
        }

        void loadColor(base::Stream* stream, ColorAspects& color) const {
            color.primaries = stream->getBe32();
            color.range = stream->getBe32();
            color.transfer = stream->getBe32();
            color.space = stream->getBe32();
        }

        void save(base::Stream* stream) const {
            saveVec(stream, sps);
            saveVec(stream, pps);
            stream->putBe32(savedPackets.size());
            for (size_t i = 0; i < savedPackets.size(); ++i) {
                savePacketInfo(stream, savedPackets[i]);
            }
            saveFrameInfo(stream, savedDecodedFrame);
        }

        void loadVec(base::Stream* stream, std::vector<uint8_t>& vec) {
            int size = stream->getBe32();
            vec.resize(size);
            if (size > 0) {
                stream->read(vec.data(), size);
            }
        }
        void load(base::Stream* stream) {
            loadVec(stream, sps);
            loadVec(stream, pps);
            int count = stream->getBe32();
            savedPackets.resize(count);
            for (int i = 0; i < count; ++i) {
                loadPacketInfo(stream, savedPackets[i]);
            }
            loadFrameInfo(stream, savedDecodedFrame);
        }
    };
};

}  // namespace emulation
}  // namespace android
