use core::fmt;
use kinavis_kernel::event::TargetId;
use kinavis_kernel::position::Position;
use kinavis_kernel::InlineStr;
use crate::bits::Bits;
use crate::error::AisError;
use crate::fields::{byte, position, second, Fields};
use crate::vessel::{Dimensions, PositionFixingDevice};
const NAME_CHARS: usize = 20;
const EXTENSION_CHARS: usize = 14;
const ALL_NAME_CHARS: usize = NAME_CHARS + EXTENSION_CHARS;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AidToNavigation {
pub mmsi: TargetId,
pub aid_type: Option<AidType>,
pub name: Option<InlineStr<ALL_NAME_CHARS>>,
pub accurate: bool,
pub position: Option<Position>,
pub dimensions: Option<Dimensions>,
pub fixing_device: Option<PositionFixingDevice>,
pub second: Option<u8>,
pub off_position: Option<bool>,
pub regional: u8,
pub raim: bool,
pub is_virtual: bool,
pub assigned: bool,
}
impl AidToNavigation {
const NEEDED: usize = 272;
pub(crate) fn decode(bits: &Bits) -> Result<Self, AisError> {
let field = Fields::of(bits, Self::NEEDED)?;
let second = second(field.unsigned(253, 6));
Ok(Self {
mmsi: TargetId::new(field.unsigned(8, 30)),
aid_type: AidType::from_code(byte(field.unsigned(38, 5))),
name: name(&field),
accurate: field.bit(163),
position: position(field.signed(164, 28), field.signed(192, 27))?,
dimensions: Dimensions::from_fields(
field.unsigned(219, 9),
field.unsigned(228, 9),
field.unsigned(237, 6),
field.unsigned(243, 6),
)?,
fixing_device: PositionFixingDevice::from_code(byte(field.unsigned(249, 4))),
second,
off_position: second.map(|_| field.bit(259)),
regional: byte(field.unsigned(260, 8)),
raim: field.bit(268),
is_virtual: field.bit(269),
assigned: field.bit(270),
})
}
}
fn name(field: &Fields<'_>) -> Option<InlineStr<ALL_NAME_CHARS>> {
let extension = ((field.len() - AidToNavigation::NEEDED) / 6).min(EXTENSION_CHARS);
let in_field = (0..NAME_CHARS).map(|index| 43 + index * 6);
let in_extension = (0..extension).map(|index| AidToNavigation::NEEDED + index * 6);
field.text_at(in_field.chain(in_extension))
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AidType {
ReferencePoint,
Racon,
FixedStructureOffshore,
Light {
sectored: bool,
},
LeadingLight {
rear: bool,
},
Beacon(Mark),
Floating(Mark),
LightVessel,
Reserved(u8),
}
impl AidType {
#[must_use]
pub const fn from_code(code: u8) -> Option<Self> {
Some(match code {
0 => return None,
1 => Self::ReferencePoint,
2 => Self::Racon,
3 => Self::FixedStructureOffshore,
5 => Self::Light { sectored: false },
6 => Self::Light { sectored: true },
7 => Self::LeadingLight { rear: false },
8 => Self::LeadingLight { rear: true },
9..=19 => Self::Beacon(Mark::from_offset(code - 9)),
20..=30 => Self::Floating(Mark::from_offset(code - 20)),
31 => Self::LightVessel,
_ => Self::Reserved(code),
})
}
#[must_use]
pub const fn code(self) -> u8 {
match self {
Self::ReferencePoint => 1,
Self::Racon => 2,
Self::FixedStructureOffshore => 3,
Self::Light { sectored: false } => 5,
Self::Light { sectored: true } => 6,
Self::LeadingLight { rear: false } => 7,
Self::LeadingLight { rear: true } => 8,
Self::Beacon(mark) => 9 + mark.offset(),
Self::Floating(mark) => 20 + mark.offset(),
Self::LightVessel => 31,
Self::Reserved(code) => code,
}
}
}
impl fmt::Display for AidType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ReferencePoint => f.write_str("reference point"),
Self::Racon => f.write_str("RACON"),
Self::FixedStructureOffshore => f.write_str("fixed structure off shore"),
Self::Light { sectored: false } => f.write_str("light without sectors"),
Self::Light { sectored: true } => f.write_str("light with sectors"),
Self::LeadingLight { rear: false } => f.write_str("leading light, front"),
Self::LeadingLight { rear: true } => f.write_str("leading light, rear"),
Self::Beacon(mark) => write!(f, "beacon, {mark}"),
Self::Floating(mark) => write!(f, "floating mark, {mark}"),
Self::LightVessel => f.write_str("light vessel"),
Self::Reserved(code) => write!(f, "reserved aid type {code}"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Mark {
Cardinal(Quadrant),
PortHand,
StarboardHand,
PreferredChannelPortHand,
PreferredChannelStarboardHand,
IsolatedDanger,
SafeWater,
Special,
}
impl Mark {
const fn from_offset(offset: u8) -> Self {
match offset {
0 => Self::Cardinal(Quadrant::North),
1 => Self::Cardinal(Quadrant::East),
2 => Self::Cardinal(Quadrant::South),
3 => Self::Cardinal(Quadrant::West),
4 => Self::PortHand,
5 => Self::StarboardHand,
6 => Self::PreferredChannelPortHand,
7 => Self::PreferredChannelStarboardHand,
8 => Self::IsolatedDanger,
9 => Self::SafeWater,
_ => Self::Special,
}
}
const fn offset(self) -> u8 {
match self {
Self::Cardinal(Quadrant::North) => 0,
Self::Cardinal(Quadrant::East) => 1,
Self::Cardinal(Quadrant::South) => 2,
Self::Cardinal(Quadrant::West) => 3,
Self::PortHand => 4,
Self::StarboardHand => 5,
Self::PreferredChannelPortHand => 6,
Self::PreferredChannelStarboardHand => 7,
Self::IsolatedDanger => 8,
Self::SafeWater => 9,
Self::Special => 10,
}
}
}
impl fmt::Display for Mark {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cardinal(quadrant) => write!(f, "cardinal {quadrant}"),
Self::PortHand => f.write_str("port hand"),
Self::StarboardHand => f.write_str("starboard hand"),
Self::PreferredChannelPortHand => f.write_str("preferred channel, port hand"),
Self::PreferredChannelStarboardHand => f.write_str("preferred channel, starboard hand"),
Self::IsolatedDanger => f.write_str("isolated danger"),
Self::SafeWater => f.write_str("safe water"),
Self::Special => f.write_str("special"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Quadrant {
North,
East,
South,
West,
}
impl fmt::Display for Quadrant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::North => "north",
Self::East => "east",
Self::South => "south",
Self::West => "west",
})
}
}