use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use std::path::Path;
use std::ptr;
use fitsio::FitsFile;
use fitsio::headers::ReadsKey;
use fitsio::sys;
use crate::error::{AtfitsError, Result, ffcall};
use crate::path::utf8;
const KEY_NO_EXIST: i32 = sys::KEY_NO_EXIST as i32;
pub(crate) fn cstr(s: &str) -> Result<CString> {
CString::new(s).map_err(|e| AtfitsError::Other(format!("invalid C string {s:?}: {e}")))
}
fn is_missing_key(e: &AtfitsError) -> bool {
matches!(e, AtfitsError::Fits(fitsio::errors::Error::Fits(f)) if f.status == KEY_NO_EXIST)
}
pub(crate) fn optional<T>(r: fitsio::errors::Result<T>) -> Result<Option<T>> {
match r.map_err(AtfitsError::from) {
Ok(v) => Ok(Some(v)),
Err(e) if is_missing_key(&e) => Ok(None),
Err(e) => Err(e),
}
}
fn round_trip_digits(value: f64) -> c_int {
(15..17)
.find(|&d| format!("{value:.*e}", d as usize - 1).parse::<f64>() == Ok(value))
.unwrap_or(17)
}
pub fn update_key_i64(fptr: &mut FitsFile, name: &str, value: i64) -> Result<()> {
let name = cstr(name)?;
ffcall(|s| unsafe { sys::ffukyj(fptr.as_raw(), name.as_ptr(), value, ptr::null_mut(), s) })
}
pub fn update_key_f64(fptr: &mut FitsFile, name: &str, value: f64) -> Result<()> {
let name = cstr(name)?;
let decimals = -round_trip_digits(value);
ffcall(|s| unsafe {
sys::ffukyd(
fptr.as_raw(),
name.as_ptr(),
value,
decimals,
ptr::null_mut(),
s,
)
})
}
pub fn update_key_str(fptr: &mut FitsFile, name: &str, value: &str) -> Result<()> {
let name = cstr(name)?;
let value = cstr(value)?;
ffcall(|s| unsafe {
sys::ffukys(
fptr.as_raw(),
name.as_ptr(),
value.as_ptr(),
ptr::null_mut(),
s,
)
})
}
pub fn update_key_logical(fptr: &mut FitsFile, name: &str, value: bool) -> Result<()> {
let name = cstr(name)?;
ffcall(|s| unsafe {
sys::ffukyl(
fptr.as_raw(),
name.as_ptr(),
value.into(),
ptr::null_mut(),
s,
)
})
}
pub fn delete_key(fptr: &mut FitsFile, name: &str) -> Result<()> {
let name = cstr(name)?;
match ffcall(|s| unsafe { sys::ffdkey(fptr.as_raw(), name.as_ptr(), s) }) {
Err(e) if is_missing_key(&e) => Ok(()),
r => r,
}
}
pub fn write_comment(fptr: &mut FitsFile, comment: &str) -> Result<()> {
let comment = cstr(comment)?;
ffcall(|s| unsafe { sys::ffpcom(fptr.as_raw(), comment.as_ptr(), s) })
}
pub fn key_i64(fptr: &mut FitsFile, name: &str) -> Result<Option<i64>> {
optional(i64::read_key(fptr, name))
}
pub fn key_f64(fptr: &mut FitsFile, name: &str) -> Result<Option<f64>> {
optional(f64::read_key(fptr, name))
}
pub fn key_bool(fptr: &mut FitsFile, name: &str) -> Result<Option<bool>> {
let c_name = cstr(name)?;
let mut value = [0 as c_char; 71];
let read = ffcall(|s| unsafe {
sys::ffgkey(
fptr.as_raw(),
c_name.as_ptr(),
value.as_mut_ptr(),
ptr::null_mut(),
s,
)
});
match read {
Err(e) if is_missing_key(&e) => return Ok(None),
r => r?,
}
let raw = unsafe { CStr::from_ptr(value.as_ptr()) }.to_string_lossy();
let text = raw.trim().trim_matches('\'').trim().to_ascii_uppercase();
match text.as_str() {
"T" | "TRUE" => Ok(Some(true)),
"F" | "FALSE" => Ok(Some(false)),
_ => Err(AtfitsError::Other(format!(
"{name} = {} is not a logical",
raw.trim()
))),
}
}
pub fn key_string(fptr: &mut FitsFile, name: &str) -> Result<Option<String>> {
optional(String::read_key(fptr, name))
}
pub fn key_exists(fptr: &mut FitsFile, name: &str) -> Result<bool> {
let name = cstr(name)?;
let mut card = [0; 81];
match ffcall(|s| unsafe { sys::ffgcrd(fptr.as_raw(), name.as_ptr(), card.as_mut_ptr(), s) }) {
Ok(()) => Ok(true),
Err(e) if is_missing_key(&e) => Ok(false),
Err(e) => Err(e),
}
}
fn open_primary(path: &Path) -> Result<FitsFile> {
let mut fptr = FitsFile::open(utf8(path)?)?;
fptr.primary_hdu()?;
Ok(fptr)
}
pub fn read_key_f64(path: &Path, key: &str) -> Result<Option<f64>> {
key_f64(&mut open_primary(path)?, key)
}
pub fn read_key_string(path: &Path, key: &str) -> Result<Option<String>> {
key_string(&mut open_primary(path)?, key)
}
pub fn has_key(path: &Path, key: &str) -> Result<bool> {
key_exists(&mut open_primary(path)?, key)
}