use crate::sys::*;
use anyhow::Result;
use cyberex::{void::*, xself::*};
use std::{ffi::CStr, marker::PhantomPinned, os::raw::*, pin::Pin};
use th_rs::{define::TH_ESStreamInfo, helper::packet::packet::Packet};

unsafe extern "C" fn com_cb(data: *const u8, len: c_int, info_ptr: *const c_void, user: *mut c_void) {
    let p = opacue_to_mut::<Codec>(user as _);
    let slice_data = std::slice::from_raw_parts(data, len as _);
    let info = &*(info_ptr as *const TH_ESStreamInfo);
    let packet = Packet::new(slice_data, info);
    let _ = p.packet.insert(packet);
}
pub struct Codec {
    p: HyVoid<()>,
    packet: Option<Packet>,
    err_buf: Vec<u8>,
    _pin: PhantomPinned,
}
impl Codec {
    pub fn new_with_config(select_str: &str, config_str: &str) -> Result<Pin<Box<Self>>> {
        let pin_obj = Box::pin(Self {
            p: HyVoid::from_ptr(unsafe { CodecCreate() }),
            packet: None,
            err_buf: vec![0; 128],
            _pin: PhantomPinned,
        });
        let this = self_from_pinbox(&pin_obj);
        let mut err_buf = vec![0; 128];
        let ctx = this.p.as_ptr();
        if unsafe { CodecConfig(ctx, config_str.as_ptr().cast(), config_str.len()) } != 0 {
            let err_msg = CStr::from_bytes_until_nul(&err_buf)?.to_str()?;
            anyhow::bail!("Init codec fail, error: {}", err_msg)
        }

        if unsafe {
            CodecInit(
                ctx,
                select_str.as_ptr().cast(),
                select_str.len() as _,
                Some(com_cb),
                HyVoidConst::from_ref(this).as_ptr() as _,
                err_buf.as_mut_ptr().cast(),
                err_buf.len() as _,
            )
        } != 0
        {
            let err_msg = CStr::from_bytes_until_nul(&err_buf)?.to_str()?;
            anyhow::bail!("Init codec fail, error: {}", err_msg)
        }
        Ok(pin_obj)
    }
    pub fn new(select_str: &str) -> Result<Pin<Box<Self>>> {
        Self::new_with_config(select_str, "")
    }
    pub fn write<'a>(self: &'a mut Pin<Box<Self>>, data: &[u8], info: &TH_ESStreamInfo) -> Result<Option<&'a Packet>> {
        let this = unsafe { self_mut_from_pinbox(self) };

        let err_buf = &mut this.err_buf;
        // reset state
        this.packet.take();
        if unsafe {
            CodecInputData(
                this.p.as_ptr(),
                data.as_ptr().cast(),
                data.len() as _,
                info as *const _ as *const _,
                err_buf.as_mut_ptr().cast(),
                err_buf.len() as _,
            )
        } != 0
        {
            let err_msg = CStr::from_bytes_until_nul(&err_buf)?.to_str()?;
            anyhow::bail!("Input data codec fail, error: {}", err_msg)
        }
        Ok(self.packet.as_ref())
    }
}
impl Drop for Codec {
    fn drop(&mut self) {
        unsafe {
            CodecDestory(self.p.as_ptr());
        }
    }
}