use crate::{abstr::Phi, bitvector::BitvectorBound, boolean::abstr};

pub trait TypedEq {
    type Output;

    #[must_use]
    fn eq(self, rhs: Self) -> Self::Output;

    #[must_use]
    fn ne(self, rhs: Self) -> Self::Output;
}

pub trait TypedCmp {
    type Output;

    #[must_use]
    fn ult(self, rhs: Self) -> Self::Output;
    #[must_use]
    fn slt(self, rhs: Self) -> Self::Output;
    #[must_use]
    fn ule(self, rhs: Self) -> Self::Output;
    #[must_use]
    fn sle(self, rhs: Self) -> Self::Output;
}

pub trait Bitwise
where
    Self: Sized,
{
    #[must_use]
    fn bit_not(self) -> Self;
    #[must_use]
    fn bit_and(self, rhs: Self) -> Self;
    #[must_use]
    fn bit_or(self, rhs: Self) -> Self;
    #[must_use]
    fn bit_xor(self, rhs: Self) -> Self;
}

pub trait HwArith
where
    Self: Sized,
{
    type DivRemResult;

    #[must_use]
    fn arith_neg(self) -> Self;

    #[must_use]
    fn add(self, rhs: Self) -> Self;
    #[must_use]
    fn sub(self, rhs: Self) -> Self;
    #[must_use]
    fn mul(self, rhs: Self) -> Self;

    #[must_use]
    fn udiv(self, rhs: Self) -> Self::DivRemResult;
    #[must_use]
    fn sdiv(self, rhs: Self) -> Self::DivRemResult;

    #[must_use]
    fn urem(self, rhs: Self) -> Self::DivRemResult;
    #[must_use]
    fn srem(self, rhs: Self) -> Self::DivRemResult;
}

pub trait HwShift {
    type Output;

    #[must_use]
    fn logic_shl(self, amount: Self) -> Self::Output;
    #[must_use]
    fn logic_shr(self, amount: Self) -> Self::Output;
    #[must_use]
    fn arith_shr(self, amount: Self) -> Self::Output;
}

pub trait Ext<const M: u32> {
    type Output;

    #[must_use]
    fn uext(self) -> Self::Output;
    #[must_use]
    fn sext(self) -> Self::Output;
}

pub trait BExt<X: BitvectorBound> {
    type Output;
    #[must_use]
    fn uext(self, new_bound: X) -> Self::Output;
    #[must_use]
    fn sext(self, new_bound: X) -> Self::Output;
}

pub enum PhiArg<T: Phi> {
    Taken(T, abstr::Boolean),
    NotTaken(),
}

impl<T: Phi> PhiArg<T> {
    pub fn phi(self, other: Self) -> T {
        match (self, other) {
            (PhiArg::Taken(a, _), PhiArg::Taken(b, _)) => a.phi(b),
            (PhiArg::Taken(a, _), PhiArg::NotTaken()) => a,
            (PhiArg::NotTaken(), PhiArg::Taken(b, _)) => b,
            (PhiArg::NotTaken(), PhiArg::NotTaken()) => panic!("Neither branch taken"),
        }
    }
}

pub trait ReadWrite {
    type Index: Copy;
    type Element: Copy;
    type Deref: Sized;
    fn read(self, index: Self::Index) -> Self::Element;
    fn write(self, index: Self::Index, element: Self::Element) -> Self::Deref;
}
