#pragma once
#include <cstdint>
#include <cstddef>
#include <string_view>
#include <vector>
#include <initializer_list>

namespace ffmsep {

inline constexpr int CP_SUCCESS = 0;
inline constexpr int CP_ERROR_EOF = -1;
inline constexpr int CP_ERROR_EAGAIN = -2;
inline constexpr int CP_ERROR_NOT_OPEN = -3;
inline constexpr int CP_ERROR_INVALID_STATE = -4;
inline constexpr int CP_ERROR_INVALID_ARGUMENT = -5;

enum class CPMediaType : std::uint8_t {
    Unknow = 0,
    Data,
};

enum class CPCodecID : std::uint32_t {
    Unknow = 0,
    Tactile = 0x54514354u // 'T','Q','C','T'：触觉传感器协议标识 Tactile Quick Codec Type
};

struct CPPacket {
    std::vector<std::uint8_t> payload;
    std::int64_t pts = 0;
    std::int64_t dts = 0;
    bool end_of_stream = false;
    bool flush = false;

    CPPacket() = default;
    CPPacket(std::vector<std::uint8_t> data, std::int64_t pts_value = 0, std::int64_t dts_value = 0) noexcept
        : payload(std::move(data)), pts(pts_value), dts(dts_value) {}

    [[nodiscard]] bool empty() const noexcept {return payload.empty();}
};

struct CPFrame {
    std::vector<std::uint8_t> data;
    std::int64_t pts = 0;
    bool key_frame = false;
    bool valid = false;

    void reset() noexcept {
        data.clear();
        key_frame = false;
        valid = false;
        pts = 0;
    }
};

struct CPCodecContext;

struct CPCodec {
    using InitFn = int(*)(CPCodecContext*);
    using CloseFn = void(*)(CPCodecContext*);
    using SendPacketFn = int(*)(CPCodecContext*, const CPPacket&);
    using ReceiveFrameFn = int(*)(CPCodecContext*, CPFrame&);

    const char* name = nullptr;
    const char* long_name = nullptr;
    CPMediaType type = CPMediaType::Unknow;
    CPCodecID id = CPCodecID::Unknow;
    std::size_t priv_data_size = 0;
    InitFn init = nullptr;
    CloseFn close = nullptr;
    SendPacketFn send_packet = nullptr;
    ReceiveFrameFn receive_frame = nullptr;
};

struct CPCodecContext {
    const CPCodec* codec = nullptr;
    void* priv_data = nullptr;
    CPMediaType codec_type = CPMediaType::Unknow;
    bool is_open = false;

    void clear() noexcept {
        codec = nullptr;
        priv_data = nullptr;
        codec_type = CPMediaType::Unknow;
        is_open = false;
        priv_storage.clear();
    }

    void* ensure_priv_storage(std::size_t size);
    void release_priv_storage() noexcept;

    template<typename T>
    [[nodiscard]] T* priv_as() noexcept {
        return static_cast<T*>(priv_data);
    }

    template<typename T>
    [[nodiscard]] const T* priv_as() const noexcept {
        return static_cast<const T*>(priv_data);
    }

private:
    std::vector<std::uint8_t> priv_storage;
    friend CPCodecContext* cpcodec_alloc_context(const CPCodec*);
    friend int cpcodec_open(CPCodecContext*, const CPCodec*);
    friend int cpcodec_close(CPCodecContext*);
}; 
void cpcodec_register(const CPCodec* codec);
void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs);
const CPCodec* cpcodec_find_decoder(CPCodecID id);
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name);
std::vector<const CPCodec*> cpcodec_list_codecs();

CPCodecContext* cpcodec_alloc_context(const CPCodec* codec);
int cpcodec_open(CPCodecContext*, const CPCodec*);
int cpcodec_close(CPCodecContext*);
void cpcodec_free_context(CPCodecContext **ctx);
int cpcodec_send_packet(CPCodecContext*, const CPPacket*);
int cpcodec_receive_frame(CPCodecContext*, CPFrame*);
}