use std::{convert::TryFrom, error::Error, str::FromStr};
use num_traits::{CheckedAdd, CheckedSub};
pub mod dd;
mod degree;
pub mod dms_dd;
mod errors;
pub use errors::AngleNotInRange;
#[allow(clippy::module_name_repetitions)]
pub trait AngleNames: Copy + PartialOrd {
fn zero() -> Self;
fn right() -> Self;
fn straight() -> Self;
fn complete() -> Self;
fn is_zero(self) -> bool {
self == Self::zero()
}
fn is_acute(self) -> bool {
self > Self::zero() && self < Self::right()
}
fn is_right(self) -> bool {
self == Self::right()
}
fn is_obtuse(self) -> bool {
self > Self::right() && self < Self::straight()
}
fn is_straight(self) -> bool {
self == Self::straight()
}
fn is_reflex(self) -> bool {
self > Self::straight() && self < Self::complete()
}
fn is_complete(self) -> bool {
self == Self::complete()
}
}
pub trait Angle:
AngleNames
+ Ord
+ CheckedAdd
+ CheckedSub
+ FromStr<Err = <Self as Angle>::ParseErr>
+ TryFrom<f64, Error = <Self as Angle>::NumErr>
{
type NumErr: Error;
type ParseErr: Error;
fn complement(self) -> Option<Self> {
Self::right().checked_sub(&self)
}
fn supplement(self) -> Option<Self> {
Self::straight().checked_sub(&self)
}
fn explement(self) -> Self {
Self::complete()
.checked_sub(&self)
.expect("Current implementation stores angles <=360 degrees")
}
fn abs_diff(self, rhs: Self) -> Self {
let diff = self.checked_sub(&rhs).or_else(|| rhs.checked_sub(&self));
diff.expect("Difference is small enough to be valid angle")
}
fn turn_eq(self, mut other: Self) -> bool {
while other >= Self::complete() {
other = other - Self::complete();
}
self == other
}
fn obtuse_err() -> Self::NumErr;
fn reflex_err() -> Self::NumErr;
fn turn_err() -> Self::NumErr;
fn and_not_obtuse(self) -> Result<Self, Self::NumErr> {
if self > Self::right() {
Err(Self::obtuse_err())
} else {
Ok(self)
}
}
fn and_not_reflex(self) -> Result<Self, Self::NumErr> {
if self.is_reflex() {
Err(Self::reflex_err())
} else {
Ok(self)
}
}
}
pub(super) trait UnitsAngle: Angle {
type Units: CheckedAdd + CheckedSub;
fn with_units(u: Self::Units) -> Result<Self, Self::NumErr>;
fn units(self) -> Self::Units;
fn max_units() -> Self::Units {
Self::complete().units()
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_angle_ops {
($t:ty: <$sum_t: ty) => {
impl Add for $t {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
if let Some(sum) = self.checked_add(&rhs) {
return sum;
}
let self_units = <$sum_t>::from(self.units());
let rhs_units = <$sum_t>::from(rhs.units());
let max = <$sum_t>::from(Self::max_units());
assert!(self_units <= max);
assert!(rhs_units <= max);
assert!(self_units + rhs_units > max);
let sum_units = (self_units + rhs_units - max)
.try_into()
.expect("Less than max should be valid");
Self::with_units(sum_units)
.expect("Wrapping sum around the max degree is always a valid degree")
}
}
impl CheckedAdd for $t {
fn checked_add(&self, rhs: &Self) -> Option<Self> {
self.units()
.checked_add(rhs.units())
.filter(|&sum_units| sum_units <= Self::max_units())
.and_then(|units| Self::with_units(units).ok())
}
}
impl Sub for $t {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
if let Some(diff) = self.checked_sub(&rhs) {
return diff;
}
let self_ = self.units();
let rhs = rhs.units();
assert!(self_ < rhs);
let max = Self::max_units();
let diff = max - (rhs - self_);
Self::with_units(diff).expect("The diff is less than the max angle")
}
}
impl CheckedSub for $t {
fn checked_sub(&self, rhs: &Self) -> Option<Self> {
self.units()
.checked_sub(rhs.units())
.and_then(|units| Self::with_units(units).ok())
}
}
};
}