use std::{ops, fmt};

#[derive(Copy, Clone, Debug)]
enum Trit {
    True,
    Maybe,
    False,
}

impl ops::Not for Trit {
    type Output = Self;
    fn not(self) -> Self {
        match self {
            Trit::True => Trit::False,
            Trit::Maybe => Trit::Maybe,
            Trit::False => Trit::True,
        }
    }
}

impl ops::BitAnd for Trit {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        match (self, other) {
            (Trit::True, Trit::True) => Trit::True,
            (Trit::False, _) | (_, Trit::False) => Trit::False,
            _ => Trit::Maybe,
        }
    }
}

impl ops::BitOr for Trit {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        match (self, other) {
            (Trit::True, _) | (_, Trit::True) => Trit::True,
            (Trit::False, Trit::False) => Trit::False,
            _ => Trit::Maybe,
        }
    }
}

impl Trit {
    fn imp(self, other: Self) -> Self {
        match self {
            Trit::True => other,
            Trit::Maybe => {
                if let Trit::True = other {
                    Trit::True
                } else {
                    Trit::Maybe
                }
            }
            Trit::False => Trit::True,
        }
    }

    fn eqv(self, other: Self) -> Self {
        match self {
            Trit::True => other,
            Trit::Maybe => Trit::Maybe,
            Trit::False =>!other,
        }
    }
}

impl fmt::Display for Trit {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Trit::True => 'T',
                Trit::Maybe => 'M',
                Trit::False => 'F',
            }
        )
    }
}

static TRITS: [Trit; 3] = [Trit::True, Trit::Maybe, Trit::False];

fn main() {
    println!("not");
    println!("-------");
    for &t in &TRITS {
        println!(" {}  | {}", t,!t);
    }

    table("and", |a, b| a & b);
    table("or", |a, b| a | b);
    table("imp", |a, b| a.imp(b));
    table("eqv", |a, b| a.eqv(b));
}

fn table(title: &str, f: impl Fn(Trit, Trit) -> Trit) {
    println!();
    println!("{:3} | T  M  F", title);
    println!("-------------");
    for &t1 in &TRITS {
        print!(" {}  | ", t1);
        for &t2 in &TRITS {
            print!("{}  ", f(t1, t2));
        }
        println!();
    }
}