use crate::{define::CESStreamInfo, sys::*, util::SlicePack};
use anyhow::{bail, Result};
use cyberex::{void::*, xself::*};
use std::{marker::PhantomPinned, pin::Pin};

unsafe extern "C" fn comCb(
    data: *const ::std::os::raw::c_uchar,
    len: ::std::os::raw::c_int,
    user: *mut ::std::os::raw::c_void,
) {
    let p = opacue_to_mut::<RtpMuxer>(user.cast());

    p.buf.push(SlicePack {
        ptr: HyVoidConst::from_ptr(data.cast()),
        size: len as usize,
    });
}

pub struct RtpMuxer {
    ptr: HyVoid<()>,
    buf: Vec<SlicePack>,
    _pin: PhantomPinned,
}

impl RtpMuxer {
    pub fn new() -> Pin<Box<Self>> {
        let mut pin_obj = Box::pin(Self {
            ptr: HyVoid::from_ptr(unsafe { lcRtpMCreate() }),
            buf: Vec::new(),
            _pin: PhantomPinned,
        });

        let this = unsafe { self_mut_from_pinbox(&mut pin_obj) };

        unsafe {
            lcRtpMInit(this.ptr.as_ptr(), Some(comCb), 0, HyVoid::from_ref(this).as_ptr());
        }
        pin_obj
    }

    pub fn write<'a>(self: &'a mut Pin<Box<Self>>, data: &[u8], info: Option<CESStreamInfo>) -> Result<Vec<&'a [u8]>> {
        let this = unsafe { self_mut_from_pinbox(self) };

        this.buf.clear();

        let ok = unsafe {
            lcRtpMInputData(
                this.ptr.as_ptr(),
                data.as_ptr(),
                data.len() as _,
                if let Some(info) = &info {
                    info as *const _
                } else {
                    std::ptr::null()
                },
            )
        };
        if ok == 0 {
            bail!("Error Occur")
        }
        let mut v = Vec::new();
        for pack in &this.buf {
            v.push(unsafe { std::slice::from_raw_parts(pack.ptr.as_ptr().cast(), pack.size) });
        }
        this.buf.clear();
        Ok(v)
    }

    pub fn get_init_data(self: &Pin<Box<Self>>) -> Result<Vec<u8>> {
        let this = self_from_pinbox(self);
        let mut initData = vec![0; 1024 * 8];
        let real_len = unsafe { lcRtpMGetInitData(this.ptr.as_ptr(), initData.as_mut_ptr(), initData.len() as _) };
        initData.resize(real_len as _, 0);
        if initData.is_empty() {
            bail!("Continue input data");
        }
        Ok(initData)
    }
}
impl Drop for RtpMuxer {
    fn drop(&mut self) {
        unsafe {
            lcRtpMDestroy(self.ptr.as_ptr());
        }
    }
}