use kinavis_kernel::angle::TrueCourse;
use kinavis_kernel::position::Position;
use kinavis_kernel::units::Speed;
use kinavis_kernel::{InlineStr, KernelError};
use crate::bits::Bits;
use crate::error::AisError;
const NO_SPEED: u32 = 1023;
const NO_LONGITUDE: i32 = 181 * 600_000;
const NO_LATITUDE: i32 = 91 * 600_000;
const NO_COURSE: u32 = 3600;
const NOT_A_HEADING: u32 = 360;
pub(crate) struct Fields<'a> {
bits: &'a Bits,
}
impl<'a> Fields<'a> {
pub(crate) fn of(bits: &'a Bits, needed: usize) -> Result<Self, AisError> {
if bits.len() < needed {
return Err(AisError::TooShort {
bits: bits.len(),
needed,
});
}
Ok(Self { bits })
}
pub(crate) fn len(&self) -> usize {
self.bits.len()
}
pub(crate) fn unsigned(&self, offset: usize, width: usize) -> u32 {
self.bits.unsigned(offset, width).unwrap_or(0)
}
pub(crate) fn signed(&self, offset: usize, width: usize) -> i32 {
self.bits.signed(offset, width).unwrap_or(0)
}
pub(crate) fn bit(&self, offset: usize) -> bool {
self.bits.bit(offset).unwrap_or(false)
}
pub(crate) fn text<const N: usize>(&self, offset: usize, chars: usize) -> Option<InlineStr<N>> {
self.bits
.text::<N>(offset, chars)
.filter(|text| !text.as_str().is_empty())
}
pub(crate) fn text_at<const N: usize>(
&self,
offsets: impl Iterator<Item = usize>,
) -> Option<InlineStr<N>> {
self.bits
.text_at::<N>(offsets)
.filter(|text| !text.as_str().is_empty())
}
}
pub(crate) mod counts_per {
pub(crate) const TENTH: f64 = 10.0;
pub(crate) const DEGREE: f64 = 600_000.0;
}
pub(crate) fn value_of(field: &'static str) -> impl Fn(KernelError) -> AisError {
move |error| AisError::Value { field, error }
}
pub(crate) fn speed(field: u32) -> Result<Option<Speed>, AisError> {
if field == NO_SPEED {
return Ok(None);
}
Speed::from_knots(f64::from(field) / counts_per::TENTH)
.map(Some)
.map_err(value_of("speed"))
}
pub(crate) fn position(longitude: i32, latitude: i32) -> Result<Option<Position>, AisError> {
if longitude == NO_LONGITUDE || latitude == NO_LATITUDE {
return Ok(None);
}
Position::from_degrees(
f64::from(latitude) / counts_per::DEGREE,
f64::from(longitude) / counts_per::DEGREE,
)
.map(Some)
.map_err(value_of("position"))
}
pub(crate) fn course(field: u32) -> Result<Option<TrueCourse>, AisError> {
if field >= NO_COURSE {
return Ok(None);
}
TrueCourse::new(f64::from(field) / counts_per::TENTH)
.map(Some)
.map_err(value_of("course"))
}
pub(crate) fn heading(field: u32) -> Result<Option<TrueCourse>, AisError> {
if field >= NOT_A_HEADING {
return Ok(None);
}
TrueCourse::new(f64::from(field))
.map(Some)
.map_err(value_of("heading"))
}
pub(crate) fn second(field: u32) -> Option<u8> {
#[allow(clippy::cast_possible_truncation)]
(field <= 59).then_some(field as u8)
}
pub(crate) const fn byte(field: u32) -> u8 {
#[allow(clippy::cast_possible_truncation)]
{
field as u8
}
}