use std::num::ParseIntError;
use thiserror::Error;
use crate::System;
use crate::math::CheckedAdd;
use crate::math::CheckedSub;
use crate::system::Base;
use crate::system::Interbase;
pub mod base;
pub mod interbase;
#[cfg(not(feature = "position-u64"))]
pub type Number = u32;
#[cfg(feature = "position-u64")]
pub type Number = u64;
const _: () = {
const fn is_copy<T: Copy>() {}
is_copy::<Number>();
is_copy::<Position<Interbase>>();
is_copy::<Position<Base>>();
};
#[derive(Error, Debug, PartialEq, Eq)]
pub enum ParseError {
#[error("failed to parse {system} position from `{value}`: {inner}")]
Int {
system: &'static str,
inner: ParseIntError,
value: String,
},
}
pub type ParseResult<T> = std::result::Result<T, ParseError>;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("parse error: {0}")]
Parse(#[from] ParseError),
#[error("incompatible value for system \"{system}\": `{value}`")]
IncompatibleValue {
system: &'static str,
value: Number,
},
}
pub type Result<T> = std::result::Result<T, Error>;
pub mod r#trait {
use std::num::NonZero;
use super::*;
pub trait Position<S: System>:
std::fmt::Display
+ std::fmt::Debug
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ std::str::FromStr<Err = Error>
+ CheckedAdd<Number, Output = Self>
+ CheckedSub<Number, Output = Self>
+ TryFrom<Number>
+ From<NonZero<Number>>
where
Self: Sized,
{
}
}
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Position<S: System> {
system: S,
value: Number,
}
impl<S: System> Position<S> {
pub fn get(&self) -> Number {
self.value
}
pub fn checked_add(&self, rhs: Number) -> Option<Self>
where
Self: r#trait::Position<S>,
{
<Self as CheckedAdd<Number>>::checked_add(self, rhs)
}
pub fn checked_sub(&self, rhs: Number) -> Option<Self>
where
Self: r#trait::Position<S>,
{
<Self as CheckedSub<Number>>::checked_sub(self, rhs)
}
pub(crate) fn distance_unchecked(&self, rhs: &Position<S>) -> Number {
let a = self.get();
let b = rhs.get();
if a >= b {
a.checked_sub(b).unwrap()
} else {
b.checked_sub(a).unwrap()
}
}
}
impl<S: System> std::fmt::Display for Position<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !f.alternate() {
write!(f, "{}", self.value)
} else {
write!(f, "{} ({})", self.value, self.system)
}
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write as _;
use super::*;
use crate::system::Interbase;
#[test]
fn serialize() {
let position = Position::<Interbase>::from(0u8);
let mut buffer = String::new();
write!(&mut buffer, "{position}").unwrap();
assert_eq!(buffer, "0");
buffer.clear();
write!(&mut buffer, "{position:#}").unwrap();
assert_eq!(buffer, "0 (interbase coordinate system)");
}
}