use crate::{angle::Angle, geonum_mod::Geonum};

pub trait Projection: Sized {
    /// view data through a specific projection path
    /// conventional: complex lens functions with higher-order abstractions
    /// geonum: encode projection paths as angles for direct access
    fn view<T>(&self, data: &T, path_encoder: fn(&T) -> Angle) -> Self;

    /// compose two projections into a single new projection
    /// conventional: nested higher-order functions
    /// geonum: direct angle addition
    fn compose(&self, other: &Self) -> Self;
}

impl Projection for Geonum {
    fn view<T>(&self, data: &T, path_encoder: fn(&T) -> Angle) -> Self {
        let path_angle = path_encoder(data);

        // create a projection by encoding the path as an angle
        Geonum::new_with_angle(self.mag, self.angle + path_angle)
    }

    fn compose(&self, other: &Self) -> Self {
        // compose projections by angle addition
        Geonum::new_with_angle(self.mag * other.mag, self.angle + other.angle)
    }
}
