nyandere 0.1.2

i help with keeping track of purchases. meow
Documentation
//! Accounting and handling money.
//!
//! ***Reminder:
//! See the license if considering to use this project
//! for anything serious, please.***

use num_bigint::Sign;

use crate::{
    aux::{Owned, Stack},
    ext::math::{Integer, Natural},
    runtime::model::Dir,
};
use std::ops::{Add, AddAssign, Sub, SubAssign};

/// Count of european cents, always positive.
///
/// No guarantees type-wise.
/// Just a newtype over the natural numbers.
/// **May be 0.**
#[derive(Owned!, Default)]
#[repr(transparent)]
pub struct Money(pub Natural);

#[derive(Stack!)]
#[repr(transparent)]
pub struct MoneyRef<'a>(pub &'a Natural);

/// Count of european cents, just like [`Money`],
/// except that it may also be ***negative***.
#[derive(Owned!, Default)]
#[repr(transparent)]
pub struct Value(pub Integer);

/// How much two entities owe each other.
#[derive(Owned!)]
pub struct Balance {
    between: Dir,
    amount: Value,
}

impl<'a> From<&'a Money> for MoneyRef<'a> {
    fn from(value: &'a Money) -> Self {
        Self(&value.0)
    }
}

impl Money {
    pub fn zero() -> Self {
        Self::default()
    }
}

impl Value {
    /// True if the value is in ***positive*** (or neutral) standing.
    #[must_use]
    pub fn is_black(&self) -> bool {
        self.0 >= 0.into()
    }

    /// True if the value is in ***negative** standing.
    #[must_use]
    pub fn is_red(&self) -> bool {
        !self.is_black()
    }

    /// Turn a positive standing into a negative one and vice versa.
    pub fn flip(&mut self) {
        self.0 *= -1;
    }

    /// Truncates the sign, returning only the extent as money.
    #[must_use]
    pub fn to_abs(self) -> Money {
        Money(self.0.into_parts().1)
    }

    /// [`Self::to_abs`] but by reference.
    #[must_use]
    pub fn abs(&self) -> MoneyRef<'_> {
        MoneyRef(self.0.magnitude())
    }

    #[must_use]
    pub fn sign(&self) -> Sign {
        self.0.sign()
    }

    /// Flips this value
    /// if the `dir` would be reordered
    /// when being converted into a [`Pair`].
    pub fn align_to(&mut self, dir: &Dir) {
        if dir.would_reorder() {
            self.flip();
        }
    }
}

impl From<Money> for Value {
    fn from(money: Money) -> Self {
        Self(money.0.into())
    }
}

impl Balance {
    pub(crate) fn new(between: Dir, amount: Value) -> Self {
        Self { between, amount }
    }

    #[must_use]
    pub fn between(&self) -> &Dir {
        &self.between
    }

    #[must_use]
    pub fn amount(&self) -> &Value {
        &self.amount
    }

    /// See the balance from the other perspective.
    ///
    /// For example, A having a debit in B
    /// is equivalent to B having a credit from A.
    /// This method toggles between these representations.
    pub fn flip(&mut self) {
        self.between.flip();
        self.amount.flip();
    }
}

/// Implement noisy calculation traits,
/// delegating to subvalues.
macro_rules! calc {
    (+ : $lhs:ty, $rhs:ty $( as $inner:ty )? ) => {
        impl Add<$rhs> for $lhs {
            type Output = Self;
            fn add(self, rhs: $rhs) -> Self {
                Self(self.0 + $(<$inner>::from)?(rhs.0))
            }
        }
    };
    (- : $lhs:ty, $rhs:ty $( as $inner:ty )? ) => {
        impl Sub<$rhs> for $lhs {
            type Output = Self;
            fn sub(self, rhs: $rhs) -> Self {
                Self(self.0 - $(<$inner>::from)?(rhs.0))
            }
        }
    };
    (+= : $lhs:ty, $rhs:ty ) => {
        impl AddAssign<$rhs> for $lhs {
            fn add_assign(&mut self, rhs: $rhs) {
                *self = self.clone() + rhs;
            }
        }
    };
    (-= : $lhs:ty, $rhs:ty ) => {
        impl SubAssign<$rhs> for $lhs {
            fn sub_assign(&mut self, rhs: $rhs) {
                *self = self.clone() - rhs;
            }
        }
    };
}

calc!(+ : Money, Money);
calc!(- : Money, Money);
calc!(+= : Money, Money);
calc!(-= : Money, Money);

calc!(+ : Value, Value);
calc!(- : Value, Value);
calc!(+= : Value, Value);
calc!(-= : Value, Value);

calc!(+ : Value, Money as Integer);
calc!(- : Value, Money as Integer);
calc!(+= : Value, Money);
calc!(-= : Value, Money);