#pragma once

/**
 * FFmpeg Version Compatibility Header
 * This file provides compatibility macros and functions for different FFmpeg versions
 * Supports FFmpeg 3.x, 4.x, 5.x, 6.x, and 7.x
 */

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
#include <libavutil/pixfmt.h>
#include <libavutil/time.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}

// FFmpeg version detection
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
    #define FFMPEG_VERSION_MAJOR 3
#elif LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(59, 0, 100)
    #define FFMPEG_VERSION_MAJOR 4
#elif LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 0, 100)
    #define FFMPEG_VERSION_MAJOR 5
#elif LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(61, 0, 100)
    #define FFMPEG_VERSION_MAJOR 6
#else
    #define FFMPEG_VERSION_MAJOR 7
#endif

// Compatibility functions for deprecated functions
inline void ffmpeg_register_all() {
#if FFMPEG_VERSION_MAJOR < 4
    av_register_all();
#endif
    // No-op in FFmpeg 4.0+
}

inline void ffmpeg_register_devices() {
#if FFMPEG_VERSION_MAJOR < 4
    avdevice_register_all();
#endif
    // No-op in FFmpeg 4.0+
}

// Compatibility for AVInputFormat/AVOutputFormat
#if FFMPEG_VERSION_MAJOR >= 4
    using FFmpegInputFormat = const AVInputFormat;
    using FFmpegOutputFormat = const AVOutputFormat;
#else
    using FFmpegInputFormat = AVInputFormat;
    using FFmpegOutputFormat = AVOutputFormat;
#endif

// Compatibility for AVCodec pointer
#if FFMPEG_VERSION_MAJOR >= 7
    using FFmpegCodec = const AVCodec;
#else
    using FFmpegCodec = AVCodec;
#endif

// Compatibility for av_err2str macro (C++ safe version)
#ifdef __cplusplus
#undef av_err2str
#ifdef _MSC_VER
#include <malloc.h>
#define av_err2str(errnum) av_make_error_string((char*)_alloca(AV_ERROR_MAX_STRING_SIZE), AV_ERROR_MAX_STRING_SIZE, errnum)
#else
#define av_err2str(errnum) av_make_error_string((char*)__builtin_alloca(AV_ERROR_MAX_STRING_SIZE), AV_ERROR_MAX_STRING_SIZE, errnum)
#endif
#endif

// Compatibility for bitstream filter context
#if FFMPEG_VERSION_MAJOR >= 4
    using AVBitStreamFilterContext = AVBSFContext;
#else
    // For FFmpeg 3.x, AVBitStreamFilterContext is already defined
    // No need to redefine it
#endif

// Compatibility functions for codec context access
inline AVMediaType ffmpeg_get_codec_type(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->codec_type;
#else
    return stream->codec->codec_type;
#endif
}

inline AVCodecID ffmpeg_get_codec_id(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->codec_id;
#else
    return stream->codec->codec_id;
#endif
}

inline int ffmpeg_get_codec_width(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->width;
#else
    return stream->codec->width;
#endif
}

inline int ffmpeg_get_codec_height(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->height;
#else
    return stream->codec->height;
#endif
}

inline AVPixelFormat ffmpeg_get_codec_pix_fmt(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return static_cast<AVPixelFormat>(stream->codecpar->format);
#else
    return stream->codec->pix_fmt;
#endif
}

inline int ffmpeg_get_codec_sample_rate(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->sample_rate;
#else
    return stream->codec->sample_rate;
#endif
}

inline int ffmpeg_get_codec_channels(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 7
    return stream->codecpar->ch_layout.nb_channels;
#elif FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->channels;
#else
    return stream->codec->channels;
#endif
}

inline uint64_t ffmpeg_get_codec_channel_layout(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 7
    // In FFmpeg 7, use ch_layout.u.mask directly if it's a mask layout
    if (stream->codecpar->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) {
        return stream->codecpar->ch_layout.u.mask;
    } else {
        // For non-mask layouts, return a default stereo layout
        return AV_CH_LAYOUT_STEREO;
    }
#elif FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->channel_layout;
#else
    return stream->codec->channel_layout;
#endif
}

inline AVSampleFormat ffmpeg_get_codec_sample_fmt(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return static_cast<AVSampleFormat>(stream->codecpar->format);
#else
    return stream->codec->sample_fmt;
#endif
}

// Compatibility for channel layout functions
inline uint64_t ffmpeg_get_default_channel_layout(int channels) {
#if FFMPEG_VERSION_MAJOR >= 7
    AVChannelLayout ch_layout;
    av_channel_layout_default(&ch_layout, channels);
    if (ch_layout.order == AV_CHANNEL_ORDER_NATIVE) {
        return ch_layout.u.mask;
    } else {
        // Fallback for common channel counts
        switch (channels) {
            case 1: return AV_CH_LAYOUT_MONO;
            case 2: return AV_CH_LAYOUT_STEREO;
            case 6: return AV_CH_LAYOUT_5POINT1;
            case 8: return AV_CH_LAYOUT_7POINT1;
            default: return AV_CH_LAYOUT_STEREO;
        }
    }
#else
    return av_get_default_channel_layout(channels);
#endif
}

// Compatibility for filter registration
inline void ffmpeg_register_filters() {
#if FFMPEG_VERSION_MAJOR < 4
    avfilter_register_all();
#endif
    // No-op in FFmpeg 4.0+
}

// Compatibility for channel layout string functions
inline int ffmpeg_get_channel_layout_string(char *buf, int buf_size, int nb_channels, uint64_t channel_layout) {
#if FFMPEG_VERSION_MAJOR >= 7
    (void)nb_channels; // Suppress unused parameter warning
    AVChannelLayout ch_layout;
    av_channel_layout_from_mask(&ch_layout, channel_layout);
    return av_channel_layout_describe(&ch_layout, buf, buf_size);
#else
    av_get_channel_layout_string(buf, buf_size, nb_channels, channel_layout);
    return strlen(buf);
#endif
}

// Compatibility for AVFrame channels access
inline int ffmpeg_get_frame_channels(AVFrame* frame) {
#if FFMPEG_VERSION_MAJOR >= 7
    return frame->ch_layout.nb_channels;
#else
    return frame->channels;
#endif
}

// Compatibility for AVFrame channel_layout access
inline uint64_t ffmpeg_get_frame_channel_layout(AVFrame* frame) {
#if FFMPEG_VERSION_MAJOR >= 7
    if (frame->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) {
        return frame->ch_layout.u.mask;
    } else {
        return AV_CH_LAYOUT_STEREO;
    }
#else
    return frame->channel_layout;
#endif
}

// Compatibility for av_samples_get_buffer_size
inline int ffmpeg_get_buffer_size(enum AVSampleFormat sample_fmt, int nb_channels, int nb_samples, int align) {
#if FFMPEG_VERSION_MAJOR >= 7
    return av_samples_get_buffer_size(nullptr, nb_channels, nb_samples, sample_fmt, align);
#else
    return av_samples_get_buffer_size(nullptr, nb_channels, nb_samples, sample_fmt, align);
#endif
}

// Compatibility for codec context creation from stream
inline AVCodecContext* ffmpeg_get_codec_context(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
    if (!codec) return nullptr;
    
    AVCodecContext* ctx = avcodec_alloc_context3(codec);
    if (!ctx) return nullptr;
    
    if (avcodec_parameters_to_context(ctx, stream->codecpar) < 0) {
        avcodec_free_context(&ctx);
        return nullptr;
    }
    
    return ctx;
#else
    return stream->codec;
#endif
}

// Compatibility for setting stream codec parameters
inline void ffmpeg_set_stream_codec_id(AVStream* stream, enum AVCodecID codec_id) {
#if FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->codec_id = codec_id;
#else
    stream->codec->codec_id = codec_id;
#endif
}

inline void ffmpeg_set_stream_codec_type(AVStream* stream, enum AVMediaType codec_type) {
#if FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->codec_type = codec_type;
#else
    stream->codec->codec_type = codec_type;
#endif
}

inline void ffmpeg_set_stream_bit_rate(AVStream* stream, int64_t bit_rate) {
#if FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->bit_rate = bit_rate;
#else
    stream->codec->bit_rate = bit_rate;
#endif
}

inline void ffmpeg_set_stream_pix_fmt(AVStream* stream, enum AVPixelFormat pix_fmt) {
#if FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->format = pix_fmt;
#else
    stream->codec->pix_fmt = pix_fmt;
#endif
}

inline void ffmpeg_set_stream_dimensions(AVStream* stream, int width, int height) {
#if FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->width = width;
    stream->codecpar->height = height;
#else
    stream->codec->width = width;
    stream->codec->height = height;
#endif
}

// Compatibility for stream codec parameters
inline int ffmpeg_copy_codec_params_to_stream(AVStream* stream, AVCodecContext* codec_ctx) {
#if FFMPEG_VERSION_MAJOR >= 4
    return avcodec_parameters_from_context(stream->codecpar, codec_ctx);
#else
    return 0;  // No-op for older versions
#endif
}

// Compatibility for extradata access
inline uint8_t* ffmpeg_get_extradata(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->extradata;
#else
    return stream->codec->extradata;
#endif
}

inline int ffmpeg_get_extradata_size(AVStream* stream) {
#if FFMPEG_VERSION_MAJOR >= 4
    return stream->codecpar->extradata_size;
#else
    return stream->codec->extradata_size;
#endif
}

inline void ffmpeg_set_stream_extradata(AVStream* stream, uint8_t* data, int size) {
#if FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->extradata = data;
    stream->codecpar->extradata_size = size;
#else
    stream->codec->extradata = data;
    stream->codec->extradata_size = size;
#endif
}

// Compatibility for AVCodecContext channels and channel_layout
inline void ffmpeg_set_codec_channels(AVCodecContext* ctx, int channels) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_default(&ctx->ch_layout, channels);
#else
    ctx->channels = channels;
#endif
}

inline void ffmpeg_set_codec_channel_layout(AVCodecContext* ctx, uint64_t channel_layout) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_from_mask(&ctx->ch_layout, channel_layout);
#else
    ctx->channel_layout = channel_layout;
#endif
}

inline int ffmpeg_get_codec_context_channels(AVCodecContext* ctx) {
#if FFMPEG_VERSION_MAJOR >= 7
    return ctx->ch_layout.nb_channels;
#else
    return ctx->channels;
#endif
}

// Compatibility for swr_alloc_set_opts
inline SwrContext* ffmpeg_swr_alloc_set_opts(SwrContext *s,
                                           int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                           int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                           int log_offset, void *log_ctx) {
#if FFMPEG_VERSION_MAJOR >= 7
    SwrContext *swr_ctx = swr_alloc();
    if (!swr_ctx) return NULL;
    
    AVChannelLayout out_ch_layout_new, in_ch_layout_new;
    av_channel_layout_from_mask(&out_ch_layout_new, out_ch_layout);
    av_channel_layout_from_mask(&in_ch_layout_new, in_ch_layout);
    
    av_opt_set_chlayout(swr_ctx, "ochl", &out_ch_layout_new, 0);
    av_opt_set_int(swr_ctx, "osf", out_sample_fmt, 0);
    av_opt_set_int(swr_ctx, "osr", out_sample_rate, 0);
    av_opt_set_chlayout(swr_ctx, "ichl", &in_ch_layout_new, 0);
    av_opt_set_int(swr_ctx, "isf", in_sample_fmt, 0);
    av_opt_set_int(swr_ctx, "isr", in_sample_rate, 0);
    
    av_channel_layout_uninit(&out_ch_layout_new);
    av_channel_layout_uninit(&in_ch_layout_new);
    
    return swr_ctx;
#else
    return swr_alloc_set_opts(s, out_ch_layout, out_sample_fmt, out_sample_rate,
                             in_ch_layout, in_sample_fmt, in_sample_rate,
                             log_offset, log_ctx);
#endif
}

// Compatibility for codec parameters setting
inline int ffmpeg_av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
                                   int nb_samples, enum AVSampleFormat sample_fmt, int align) {
#if FFMPEG_VERSION_MAJOR >= 7
    return av_samples_alloc_array_and_samples(&audio_data, linesize, nb_channels, nb_samples, sample_fmt, align);
#else
    return av_samples_alloc(audio_data, linesize, nb_channels, nb_samples, sample_fmt, align);
#endif
}

inline void ffmpeg_set_frame_channel_layout(AVFrame* frame, AVCodecContext* codec_ctx) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_copy(&frame->ch_layout, &codec_ctx->ch_layout);
#else
    frame->channel_layout = codec_ctx->channel_layout;
#endif
}

inline void ffmpeg_set_stream_codec_params(AVStream* stream, AVCodecContext* codec_ctx) {
#if FFMPEG_VERSION_MAJOR >= 7
    stream->codecpar->codec_id = codec_ctx->codec_id;
    stream->codecpar->bit_rate = codec_ctx->bit_rate;
    stream->codecpar->width = codec_ctx->width;
    stream->codecpar->height = codec_ctx->height;
    stream->codecpar->format = codec_ctx->pix_fmt;
    stream->codecpar->sample_rate = codec_ctx->sample_rate;
    av_channel_layout_copy(&stream->codecpar->ch_layout, &codec_ctx->ch_layout);
    stream->time_base = codec_ctx->time_base;
#elif FFMPEG_VERSION_MAJOR >= 4
    stream->codecpar->codec_id = codec_ctx->codec_id;
    stream->codecpar->bit_rate = codec_ctx->bit_rate;
    stream->codecpar->width = codec_ctx->width;
    stream->codecpar->height = codec_ctx->height;
    stream->codecpar->format = codec_ctx->pix_fmt;
    stream->codecpar->sample_rate = codec_ctx->sample_rate;
    stream->codecpar->channels = codec_ctx->channels;
    stream->codecpar->channel_layout = codec_ctx->channel_layout;
    stream->time_base = codec_ctx->time_base;
#else
    *(stream->codec) = *codec_ctx;
#endif
}

// Additional compatibility functions for missing identifiers
inline int ffmpeg_get_channels(AVCodecContext* ctx) {
#if FFMPEG_VERSION_MAJOR >= 7
    return ctx->ch_layout.nb_channels;
#else
    return ctx->channels;
#endif
}

inline void ffmpeg_set_channels(AVCodecContext* ctx, int channels) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_default(&ctx->ch_layout, channels);
#else
    ctx->channels = channels;
#endif
}

inline void ffmpeg_set_channel_layout(AVCodecContext* ctx, uint64_t channel_layout) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_from_mask(&ctx->ch_layout, channel_layout);
#else
    ctx->channel_layout = channel_layout;
#endif
}

inline void ffmpeg_set_frame_channels(AVFrame* frame, int channels) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_default(&frame->ch_layout, channels);
#else
    frame->channels = channels;
#endif
}

inline void ffmpeg_set_frame_channel_layout(AVFrame* frame, uint64_t channel_layout) {
#if FFMPEG_VERSION_MAJOR >= 7
    av_channel_layout_from_mask(&frame->ch_layout, channel_layout);
#else
    frame->channel_layout = channel_layout;
#endif
}

inline void ffmpeg_set_frame_pkt_pts(AVFrame* frame, int64_t pts) {
#if FFMPEG_VERSION_MAJOR >= 4
    frame->pts = pts;
#else
    frame->pkt_pts = pts;
#endif
}

// Compatibility for bitstream filter initialization
#if FFMPEG_VERSION_MAJOR >= 4
inline AVBitStreamFilterContext* ffmpeg_bitstream_filter_init(const char* name)
{
    const AVBitStreamFilter* bsf = av_bsf_get_by_name(name);
    if (!bsf) return nullptr;
    
    AVBSFContext* ctx = nullptr;
    if (av_bsf_alloc(bsf, &ctx) < 0) return nullptr;
    
    return ctx;
}
#else
inline AVBitStreamFilterContext* ffmpeg_bitstream_filter_init(const char* name) {
    // For FFmpeg 3.x, use the old API
    return av_bitstream_filter_init(name);
}
#endif

#if FFMPEG_VERSION_MAJOR >= 4
inline void ffmpeg_bitstream_filter_close(AVBitStreamFilterContext* ctx) {
    if (ctx) {
        av_bsf_free(&ctx);
    }
}
#else
inline void ffmpeg_bitstream_filter_close(AVBitStreamFilterContext* ctx) {
    if (ctx) {
        av_bitstream_filter_close(ctx);
    }
}
#endif

#if FFMPEG_VERSION_MAJOR >= 4
inline int ffmpeg_bitstream_filter_filter(AVBitStreamFilterContext* ctx, AVPacket* packet) {
    if (!ctx || !packet) return -1;
    
    // 对于新版本FFmpeg，需要先初始化BSF上下文
    if (ctx->par_in && ctx->par_in->codec_type == AVMEDIA_TYPE_UNKNOWN) {
        // 从packet推断参数
        ctx->par_in->codec_type = AVMEDIA_TYPE_AUDIO;
        ctx->par_in->codec_id = AV_CODEC_ID_AAC;
        
        if (avcodec_parameters_copy(ctx->par_out, ctx->par_in) < 0) {
            return -1;
        }
        
        if (av_bsf_init(ctx) < 0) {
            return -1;
        }
    }
    
    int ret = av_bsf_send_packet(ctx, packet);
    if (ret < 0) return ret;
    
    ret = av_bsf_receive_packet(ctx, packet);
    return ret;
}
#else
inline int ffmpeg_bitstream_filter_filter(AVBitStreamFilterContext* ctx, AVPacket* packet) {
    if (!ctx || !packet) return -1;
    
    uint8_t* output_data = nullptr;
    int output_size = 0;
    
    int ret = av_bitstream_filter_filter(ctx, nullptr, nullptr, &output_data, &output_size,
                                         packet->data, packet->size, packet->flags & AV_PKT_FLAG_KEY);
    
    if (ret >= 0 && output_data && output_size > 0) {
        av_packet_unref(packet);
        packet->data = output_data;
        packet->size = output_size;
        packet->buf = av_buffer_create(output_data, output_size, av_buffer_default_free, nullptr, 0);
    }
    
    return ret;
}
#endif
