use crate::core::codec::Codec;
use crate::core::context::{FrameBox, PacketBox, Stream};
use crate::core::hwaccel::HWAccelID;
use crossbeam_channel::{Receiver, Sender};
use ffmpeg_sys_next::{
    AVCodec, AVCodecDescriptor, AVCodecParameters, AVHWDeviceType, AVMediaType, AVPixelFormat,
    AVRational, AVStream,
};
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;

/// fftools: `InputStream` + `DemuxStream` (ffmpeg.h / ffmpeg_demux.c).
#[derive(Clone)]
pub(crate) struct DecoderStream {
    pub(crate) stream_index: usize,
    pub(crate) stream: Stream,
    pub(crate) codec_parameters: *mut AVCodecParameters,
    pub(crate) codec_type: AVMediaType,
    pub(crate) codec: Codec,
    pub(crate) codec_desc: *const AVCodecDescriptor,
    /// User decoder options for this stream's media type
    /// (`Input::set_video_codec_opt` and friends), applied at decoder open.
    pub(crate) codec_opts: Option<HashMap<CString, CString>>,
    pub(crate) duration: i64,
    pub(crate) time_base: AVRational,
    pub(crate) avg_framerate: AVRational,
    /// Input::set_framerate forces a fixed framerate: the decoder stamps
    /// frames on the CFR grid instead of trusting container timestamps
    /// (fftools DECODER_FLAG_FRAMERATE_FORCED, ffmpeg_demux.c:924-925).
    pub(crate) framerate_forced: bool,
    pub(crate) have_sub2video: bool,

    pub(crate) hwaccel_id: HWAccelID,
    pub(crate) hwaccel_device_type: AVHWDeviceType,
    pub(crate) hwaccel_device: Option<String>,
    pub(crate) hwaccel_output_format: AVPixelFormat,

    /// Applied to `AVCodecContext.log_level_offset` when the decoder opens.
    pub(crate) log_level_offset: i32,

    src: Option<Receiver<PacketBox>>,
    dsts: Vec<(Sender<FrameBox>, usize, Arc<[AtomicBool]>)>,
}

impl DecoderStream {
    pub(crate) fn new(
        stream_index: usize,
        stream: *mut AVStream,
        codec_parameters: *mut AVCodecParameters,
        codec_type: AVMediaType,
        codec: *const AVCodec,
        codec_desc: *const AVCodecDescriptor,
        codec_opts: Option<HashMap<CString, CString>>,
        duration: i64,
        time_base: AVRational,
        avg_framerate: AVRational,
        framerate_forced: bool,
        hwaccel_id: HWAccelID,
        hwaccel_device_type: AVHWDeviceType,
        hwaccel_device: Option<String>,
        hwaccel_output_format: AVPixelFormat,
        log_level_offset: i32,
    ) -> Self {
        Self {
            stream_index,
            stream: Stream { inner: stream },
            codec_parameters,
            codec_type,
            codec: Codec::new(codec),
            codec_desc,
            codec_opts,
            duration,
            time_base,
            avg_framerate,
            framerate_forced,
            have_sub2video: false,
            hwaccel_id,
            hwaccel_device_type,
            hwaccel_device,
            hwaccel_output_format,
            log_level_offset,
            src: None,
            dsts: vec![],
        }
    }

    pub(crate) fn is_used(&self) -> bool {
        self.src.is_some()
    }

    pub(crate) fn set_src(&mut self, src: Receiver<PacketBox>) {
        self.src = Some(src);
    }

    pub(crate) fn add_dst(&mut self, frame_dst: Sender<FrameBox>) {
        self.add_fg_dst(frame_dst, usize::MAX, Arc::new([]));
    }

    pub(crate) fn add_fg_dst(
        &mut self,
        frame_dst: Sender<FrameBox>,
        fg_input_index: usize,
        finished_flag_list: Arc<[AtomicBool]>,
    ) {
        self.dsts
            .push((frame_dst, fg_input_index, finished_flag_list));
    }

    pub(crate) fn take_src(&mut self) -> Option<Receiver<PacketBox>> {
        self.src.take()
    }

    pub(crate) fn take_dsts(&mut self) -> Vec<(Sender<FrameBox>, usize, Arc<[AtomicBool]>)> {
        std::mem::take(&mut self.dsts)
    }

    pub fn replace_dsts(
        &mut self,
        new_dsts: Sender<FrameBox>,
        fg_input_index: usize,
        finished_flag_list: Arc<[AtomicBool]>,
    ) -> Vec<(Sender<FrameBox>, usize, Arc<[AtomicBool]>)> {
        std::mem::replace(
            &mut self.dsts,
            vec![(new_dsts, fg_input_index, finished_flag_list)],
        )
    }
}
