use std::{
	borrow::Cow,
	ffi,
	io,
	os::raw::c_char,
	ptr::null,
};

pub unsafe fn from_cstr(s: *const c_char) -> io::Result<&'static str> {
	ffi::CStr::from_ptr(s)
		.to_str()
		.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}

#[derive(Clone, Debug)]
pub struct CStr<'a>(Cow<'a, ffi::CStr>);

impl<'a> CStr<'a> {
	pub fn from<T>(s: &'a T) -> Result<Self, ffi::NulError>
	where
		Self: CStrFrom<'a, T>,
	{
		CStrFrom::cstr_from(s)
	}

	pub fn as_ptr(&self) -> *const c_char {
		self.0.as_ptr()
	}
}

#[derive(Clone, Debug)]
pub struct NullableCStr<'a>(Option<Cow<'a, ffi::CStr>>);

impl<'a> NullableCStr<'a> {
	pub fn from<T>(s: &'a T) -> Result<Self, ffi::NulError>
	where
		Self: CStrFrom<'a, T>,
	{
		CStrFrom::cstr_from(s)
	}

	pub fn as_ptr(&self) -> *const c_char {
		match self.0 {
			Some(ref s) => s.as_ptr(),
			None => null(),
		}
	}
}

pub trait CStrFrom<'a, T>: Sized {
	fn cstr_from(_: &'a T) -> Result<Self, ffi::NulError>;
}

impl<'a, T: AsRef<str>> CStrFrom<'a, T> for CStr<'a> {
	fn cstr_from(s: &'a T) -> Result<Self, ffi::NulError> {
		Ok(CStr(Cow::Owned(ffi::CString::new(s.as_ref())?)))
	}
}

impl<'a, T: AsRef<str>> CStrFrom<'a, Option<T>> for NullableCStr<'a> {
	fn cstr_from(s: &'a Option<T>) -> Result<Self, ffi::NulError> {
		match *s {
			Some(ref s) => Ok(NullableCStr(Some(Cow::Owned(
				ffi::CString::new(s.as_ref())?,
			)))),
			None => Ok(NullableCStr(None)),
		}
	}
}
