#![allow(unsafe_code)]

use std::{ffi::CStr, ptr::NonNull};

use derive_more::From;

use super::{error::AndroidBackendError, ffi};

#[derive(From)]
pub(crate) struct OwnedFormat {
    raw: NonNull<ffi::AMediaFormat>,
}

impl OwnedFormat {
    pub(crate) fn get_i32(&self, key: &CStr) -> Option<i32> {
        let mut value = 0;
        unsafe { ffi::AMediaFormat_getInt32(self.raw(), key.as_ptr(), &mut value) }.then_some(value)
    }

    pub(crate) fn get_i64(&self, key: &CStr) -> Result<Option<i64>, AndroidBackendError> {
        let mut value = 0;
        Ok(
            unsafe { ffi::AMediaFormat_getInt64(self.raw(), key.as_ptr(), &mut value) }
                .then_some(value),
        )
    }

    pub(crate) fn get_string(&self, key: &CStr) -> Result<Option<String>, AndroidBackendError> {
        let mut value = std::ptr::null();
        let found = unsafe { ffi::AMediaFormat_getString(self.raw(), key.as_ptr(), &mut value) };
        if !found {
            return Ok(None);
        }
        let value = NonNull::new(value.cast_mut()).ok_or_else(|| {
            AndroidBackendError::operation("media-format-string", "string pointer was null")
        })?;
        Ok(Some(
            unsafe { CStr::from_ptr(value.as_ptr()) }
                .to_string_lossy()
                .into_owned(),
        ))
    }

    pub(crate) fn get_u16(&self, key: &CStr) -> Result<Option<u16>, AndroidBackendError> {
        self.get_uint(key, "media-format-u16")
    }

    pub(crate) fn get_u32(&self, key: &CStr) -> Result<Option<u32>, AndroidBackendError> {
        self.get_uint(key, "media-format-u32")
    }

    fn get_uint<T>(&self, key: &CStr, op: &'static str) -> Result<Option<T>, AndroidBackendError>
    where
        T: TryFrom<i32>,
    {
        self.get_i32(key)
            .map(|value| {
                T::try_from(value).map_err(|_| {
                    AndroidBackendError::operation(
                        op,
                        format!("{}={value} is out of range", key.to_string_lossy()),
                    )
                })
            })
            .transpose()
    }

    pub(crate) fn raw(&self) -> *mut ffi::AMediaFormat {
        self.raw.as_ptr()
    }

    pub(crate) fn set_i32(&mut self, key: &CStr, value: i32) -> bool {
        unsafe { ffi::AMediaFormat_setInt32(self.raw(), key.as_ptr(), value) }
    }
}

impl Drop for OwnedFormat {
    fn drop(&mut self) {
        unsafe { ffi::AMediaFormat_delete(self.raw()) };
    }
}
