pub trait Group
where
    Self: Eq + Clone + Copy,
{
    fn identity() -> Self;
    fn op(self, rhs: Self) -> Self;
    fn inverse(self) -> Self;
}

impl Group for () {
    fn identity() -> Self {}
    fn op(self, rhs: Self) -> Self {}
    fn inverse(self) -> Self {}
}

impl Group for bool {
    fn identity() -> Self {
        false
    }

    fn op(self, rhs: Self) -> Self {
        match (self, rhs) {
            (false, false) => false,
            (false, true) => true,
            (true, false) => true,
            (true, true) => false,
        }
    }

    fn inverse(self) -> Self {
        self
    }
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
enum ThreeGroup {
    E,
    A,
    B,
}

impl Group for ThreeGroup {
    fn identity() -> Self {
        Self::E
    }

    fn op(self, rhs: Self) -> Self {
        match (self, rhs) {
            (Self::E, Self::E) => Self::E,
            (Self::E, Self::A) => Self::A,
            (Self::E, Self::B) => Self::B,

            (Self::A, Self::E) => Self::A,
            (Self::A, Self::A) => Self::B,
            (Self::A, Self::B) => Self::E,

            (Self::B, Self::E) => Self::B,
            (Self::B, Self::A) => Self::E,
            (Self::B, Self::B) => Self::A,
        }
    }

    fn inverse(self) -> Self {
        match self {
            Self::E => Self::E,
            Self::A => Self::B,
            Self::B => Self::A,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
enum Vierergruppe {
    E,
    A,
    B,
    C,
}

impl Group for Vierergruppe {
    fn identity() -> Self {
        Self::E
    }

    fn op(self, rhs: Self) -> Self {
        if matches!(self, Self::E) {
            return rhs;
        }
        if matches!(rhs, Self::E) {
            return self;
        }
        if self == rhs {
            return Self::E;
        }
        match (self, rhs) {
            (Self::A, Self::B) => Self::C,
            (Self::B, Self::A) => Self::C,
            (Self::A, Self::C) => Self::B,
            (Self::C, Self::A) => Self::B,
            (Self::B, Self::C) => Self::A,
            (Self::C, Self::B) => Self::A,
            _ => unreachable!(),
        }
    }

    fn inverse(self) -> Self {
        self
    }
}

impl Group for i8 {
    fn identity() -> Self {
        0
    }

    fn op(self, rhs: Self) -> Self {
        self.wrapping_add(rhs)
    }

    fn inverse(self) -> Self {
        self.wrapping_neg()
    }
}

#[cfg(test)]
mod tests {

    use super::{Group, ThreeGroup, Vierergruppe};

    pub trait IterateGroup
    where
        Self: Sized + Group,
    {
        fn all() -> Vec<Self>;
    }

    impl IterateGroup for bool {
        fn all() -> Vec<Self> {
            vec![false, true]
        }
    }

    impl IterateGroup for ThreeGroup {
        fn all() -> Vec<Self> {
            vec![ThreeGroup::E, ThreeGroup::A, ThreeGroup::B]
        }
    }

    impl IterateGroup for Vierergruppe {
        fn all() -> Vec<Self> {
            vec![
                Vierergruppe::E,
                Vierergruppe::A,
                Vierergruppe::B,
                Vierergruppe::C,
            ]
        }
    }

    impl IterateGroup for i8 {
        fn all() -> Vec<Self> {
            (i8::MIN..=i8::MAX).collect()
        }
    }

    fn group_test<G: IterateGroup + std::fmt::Debug>() {
        let elements = G::all();
        for g in elements {
            assert_eq!(G::identity().op(g), g);
            assert_eq!(g.op(G::identity()), g);
            assert_eq!(g.op(g.inverse()), G::identity());
            assert_eq!(g.inverse().op(g), G::identity());
        }
    }

    fn commutativity_test<G: IterateGroup + std::fmt::Debug>() {
        let elements = G::all();
        for a in elements.iter().cloned() {
            for b in elements.iter().cloned() {
                assert_eq!(a.op(b), b.op(a));
            }
        }
    }

    #[test]
    fn c2() {
        group_test::<bool>();
    }

    #[test]
    fn c2_size() {
        assert_eq!(2, bool::all().len())
    }

    #[test]
    fn c2_commutes() {
        commutativity_test::<bool>();
    }

    #[test]
    fn c3() {
        group_test::<ThreeGroup>();
    }

    #[test]
    fn c3_size() {
        assert_eq!(3, ThreeGroup::all().len())
    }

    #[test]
    fn c3_commutes() {
        commutativity_test::<ThreeGroup>();
    }

    #[test]
    fn vierergruppe() {
        group_test::<Vierergruppe>();
    }

    #[test]
    fn vierergruppe_size() {
        assert_eq!(4, Vierergruppe::all().len())
    }

    #[test]
    fn vierergruppe_commutes() {
        commutativity_test::<Vierergruppe>();
    }

    #[test]
    fn c256() {
        group_test::<i8>();
    }

    #[test]
    fn c256_size() {
        assert_eq!(256, i8::all().len())
    }

    #[test]
    fn c256_commutes() {
        commutativity_test::<i8>();
    }
}
