use cidre::{
    arc, cm, cv, define_obj_type, dispatch, ns, objc,
    sc::{self, StreamDelegate, StreamDelegateImpl, StreamOutput, StreamOutputImpl},
};

define_obj_type!(
    pub CapturerCallbacks + StreamOutputImpl + StreamDelegateImpl, CapturerCallbacksInner, CAPTURER
);

unsafe impl Send for CapturerCallbacks {}
unsafe impl Sync for CapturerCallbacks {}

impl sc::stream::Output for CapturerCallbacks {}

#[objc::add_methods]
impl sc::stream::OutputImpl for CapturerCallbacks {
    extern "C" fn impl_stream_did_output_sample_buf(
        &mut self,
        _cmd: Option<&objc::Sel>,
        _: &sc::Stream,
        sample_buf: &mut cm::SampleBuf,
        kind: sc::OutputType,
    ) {
        if let Some(cb) = &mut self.inner_mut().did_output_sample_buf_cb {
            let sample_buf = sample_buf.retained();
            let frame = match kind {
                sc::OutputType::Screen => {
                    let Some(image_buf) = sample_buf.image_buf().map(|v| v.retained()) else {
                        return;
                    };

                    Frame::Screen(VideoFrame {
                        sample_buf,
                        image_buf,
                    })
                }
                sc::OutputType::Audio => Frame::Audio(AudioFrame { sample_buf }),
                sc::OutputType::Mic => Frame::Mic(AudioFrame { sample_buf }),
            };
            (cb)(frame);
        }
    }
}

impl sc::stream::Delegate for CapturerCallbacks {}

#[objc::add_methods]
impl sc::stream::DelegateImpl for CapturerCallbacks {
    extern "C" fn impl_stream_did_stop_with_err(
        &mut self,
        _cmd: Option<&objc::Sel>,
        stream: &sc::Stream,
        error: &ns::Error,
    ) {
        if let Some(cb) = &mut self.inner_mut().did_stop_with_err_cb {
            (cb)(stream, error)
        }
    }
}

type DidOutputSampleBufCallback = Box<dyn FnMut(Frame)>;
type StreamDidStopwithErrCallback = Box<dyn FnMut(&sc::Stream, &ns::Error)>;

#[derive(Default)]
pub struct CapturerCallbacksInner {
    did_output_sample_buf_cb: Option<DidOutputSampleBufCallback>,
    did_stop_with_err_cb: Option<StreamDidStopwithErrCallback>,
}

pub struct Capturer {
    _queue: arc::R<dispatch::Queue>,
    stream: arc::R<sc::Stream>,
    // READING THIS IS NOT THREAD SAFE, IT JUST HAS TO EXIST
    _callbacks: arc::R<CapturerCallbacks>,
}

impl Capturer {
    pub fn builder(
        target: arc::R<sc::ContentFilter>,
        config: arc::R<sc::StreamCfg>,
    ) -> CapturerBuilder {
        CapturerBuilder {
            target,
            config,
            callbacks: CapturerCallbacksInner::default(),
        }
    }

    pub async fn start(&self) -> Result<(), arc::R<ns::Error>> {
        self.stream.start().await
    }

    pub async fn stop(&self) -> Result<(), arc::R<ns::Error>> {
        self.stream.stop().await
    }
}

pub struct VideoFrame {
    sample_buf: arc::R<cm::SampleBuf>,
    image_buf: arc::R<cv::ImageBuf>,
}

unsafe impl Send for VideoFrame {}

impl VideoFrame {
    pub fn sample_buf(&self) -> &cm::SampleBuf {
        self.sample_buf.as_ref()
    }

    pub fn sample_buf_mut(&mut self) -> &mut cm::SampleBuf {
        self.sample_buf.as_mut()
    }

    pub fn image_buf(&self) -> &cv::ImageBuf {
        self.image_buf.as_ref()
    }

    pub fn image_buf_mut(&mut self) -> &mut cv::ImageBuf {
        self.image_buf.as_mut()
    }
}

pub struct AudioFrame {
    sample_buf: arc::R<cm::SampleBuf>,
}

impl AudioFrame {
    pub fn sample_buf(&self) -> &cm::SampleBuf {
        self.sample_buf.as_ref()
    }

    pub fn sample_buf_mut(&mut self) -> &mut cm::SampleBuf {
        self.sample_buf.as_mut()
    }
}

pub enum Frame {
    Screen(VideoFrame),
    Audio(AudioFrame),
    Mic(AudioFrame),
}

impl Frame {
    pub fn sample_buf(&self) -> &cm::SampleBuf {
        match self {
            Frame::Screen(frame) => frame.sample_buf(),
            Frame::Audio(frame) => frame.sample_buf(),
            Frame::Mic(frame) => frame.sample_buf(),
        }
    }

    pub fn sample_buf_mut(&mut self) -> &mut cm::SampleBuf {
        match self {
            Frame::Screen(frame) => frame.sample_buf_mut(),
            Frame::Audio(frame) => frame.sample_buf_mut(),
            Frame::Mic(frame) => frame.sample_buf_mut(),
        }
    }

    pub fn output_type(&self) -> sc::OutputType {
        match self {
            Frame::Screen(_) => sc::OutputType::Screen,
            Frame::Audio(_) => sc::OutputType::Audio,
            Frame::Mic(_) => sc::OutputType::Mic,
        }
    }
}

pub struct CapturerBuilder {
    target: arc::R<sc::ContentFilter>,
    config: arc::R<sc::StreamCfg>,
    callbacks: CapturerCallbacksInner,
}

impl CapturerBuilder {
    pub fn with_output_sample_buf_cb(mut self, cb: impl FnMut(Frame) + 'static) -> Self {
        self.callbacks.did_output_sample_buf_cb = Some(Box::new(cb));
        self
    }

    pub fn with_stop_with_err_cb(
        mut self,
        cb: impl FnMut(&sc::Stream, &ns::Error) + 'static,
    ) -> Self {
        self.callbacks.did_stop_with_err_cb = Some(Box::new(cb));
        self
    }

    pub fn build(self) -> Result<Capturer, arc::R<ns::Error>> {
        let callbacks = CapturerCallbacks::with(self.callbacks);

        let stream = sc::Stream::with_delegate(&self.target, &self.config, callbacks.as_ref());

        let queue = dispatch::Queue::serial_with_ar_pool();

        if self.config.captures_audio() {
            stream
                .add_stream_output(callbacks.as_ref(), sc::OutputType::Audio, Some(&queue))
                .map_err(|e| e.retained())?;
        }

        stream
            .add_stream_output(callbacks.as_ref(), sc::OutputType::Screen, Some(&queue))
            .map_err(|e| e.retained())?;

        Ok(Capturer {
            _queue: queue,
            stream,
            _callbacks: callbacks,
        })
    }
}
