use super::{Disconnectable, FailEntropy};
use crate::dag::Dag;
use crate::{Cmr, Value};
use std::fmt;
use std::sync::Arc;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum Inner<C, J, X, W> {
    Iden,
    Unit,
    InjL(C),
    InjR(C),
    Take(C),
    Drop(C),
    Comp(C, C),
    Case(C, C),
    AssertL(C, Cmr),
    AssertR(Cmr, C),
    Pair(C, C),
    Disconnect(C, X),
    Witness(W),
    Fail(FailEntropy),
    Jet(J),
    Word(Arc<Value>),
}
impl<C, J: Clone, X, W> Inner<C, J, X, W> {
    pub fn map<D, F: FnMut(C) -> D>(self, mut f: F) -> Inner<D, J, X, W> {
        match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(f(c)),
            Inner::InjR(c) => Inner::InjR(f(c)),
            Inner::Take(c) => Inner::Take(f(c)),
            Inner::Drop(c) => Inner::Drop(f(c)),
            Inner::Comp(cl, cr) => Inner::Comp(f(cl), f(cr)),
            Inner::Case(cl, cr) => Inner::Case(f(cl), f(cr)),
            Inner::AssertL(c, cmr) => Inner::AssertL(f(c), cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, f(c)),
            Inner::Pair(cl, cr) => Inner::Pair(f(cl), f(cr)),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(f(cl), cr),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        }
    }
    pub fn map_result<D, E, F: FnMut(C) -> Result<D, E>>(
        self,
        mut f: F,
    ) -> Result<Inner<D, J, X, W>, E> {
        Ok(match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(f(c)?),
            Inner::InjR(c) => Inner::InjR(f(c)?),
            Inner::Take(c) => Inner::Take(f(c)?),
            Inner::Drop(c) => Inner::Drop(f(c)?),
            Inner::Comp(cl, cr) => Inner::Comp(f(cl)?, f(cr)?),
            Inner::Case(cl, cr) => Inner::Case(f(cl)?, f(cr)?),
            Inner::AssertL(c, cmr) => Inner::AssertL(f(c)?, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, f(c)?),
            Inner::Pair(cl, cr) => Inner::Pair(f(cl)?, f(cr)?),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(f(cl)?, cr),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        })
    }
    pub fn map_left_right<D, FL, FR>(self, fl: FL, fr: FR) -> Inner<D, J, X, W>
    where
        FL: FnOnce(C) -> D,
        FR: FnOnce(C) -> D,
    {
        match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(fl(c)),
            Inner::InjR(c) => Inner::InjR(fl(c)),
            Inner::Take(c) => Inner::Take(fl(c)),
            Inner::Drop(c) => Inner::Drop(fl(c)),
            Inner::Comp(cl, cr) => Inner::Comp(fl(cl), fr(cr)),
            Inner::Case(cl, cr) => Inner::Case(fl(cl), fr(cr)),
            Inner::AssertL(c, cmr) => Inner::AssertL(fl(c), cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, fl(c)),
            Inner::Pair(cl, cr) => Inner::Pair(fl(cl), fr(cr)),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(fl(cl), cr),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        }
    }
    pub fn as_ref(&self) -> Inner<&C, J, &X, &W> {
        match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, *cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(*cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, cr),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(*entropy),
            Inner::Jet(j) => Inner::Jet(j.clone()),
            Inner::Word(w) => Inner::Word(Arc::clone(w)),
        }
    }
    pub fn disconnect_as_ref(&self) -> Inner<C, J, &X, W>
    where
        J: Copy,
        C: Copy,
        W: Copy,
    {
        match *self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, ref cr) => Inner::Disconnect(cl, cr),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(ref w) => Inner::Word(Arc::clone(w)),
        }
    }
    pub fn map_disconnect<Y, F: FnOnce(X) -> Y>(self, f: F) -> Inner<C, J, Y, W> {
        match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, f(cr)),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        }
    }
    pub fn map_disconnect_result<Y, E, F: FnOnce(X) -> Result<Y, E>>(
        self,
        f: F,
    ) -> Result<Inner<C, J, Y, W>, E> {
        Ok(match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, f(cr)?),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        })
    }
    pub fn map_witness<V, F: FnOnce(W) -> V>(self, f: F) -> Inner<C, J, X, V> {
        match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, cr),
            Inner::Witness(w) => Inner::Witness(f(w)),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        }
    }
    pub fn map_witness_result<V, E, F: FnOnce(W) -> Result<V, E>>(
        self,
        f: F,
    ) -> Result<Inner<C, J, X, V>, E> {
        Ok(match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, cr),
            Inner::Witness(w) => Inner::Witness(f(w)?),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        })
    }
}
impl<C, J, X: Disconnectable<C>, W> Inner<Arc<C>, J, X, W> {
    pub fn as_dag(&self) -> Dag<&C> {
        match self {
            Inner::Iden
            | Inner::Unit
            | Inner::Witness(_)
            | Inner::Fail(_)
            | Inner::Jet(_)
            | Inner::Word(_) => Dag::Nullary,
            Inner::InjL(c)
            | Inner::InjR(c)
            | Inner::Take(c)
            | Inner::Drop(c)
            | Inner::AssertL(c, _)
            | Inner::AssertR(_, c) => Dag::Unary(c),
            Inner::Comp(cl, cr) | Inner::Case(cl, cr) | Inner::Pair(cl, cr) => Dag::Binary(cl, cr),
            Inner::Disconnect(cl, cr) => cr.disconnect_dag_ref(cl),
        }
    }
}
impl<C, J, X, W> Inner<Option<C>, J, X, W> {
    pub fn transpose(self) -> Option<Inner<C, J, X, W>> {
        Some(match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c?),
            Inner::InjR(c) => Inner::InjR(c?),
            Inner::Take(c) => Inner::Take(c?),
            Inner::Drop(c) => Inner::Drop(c?),
            Inner::Comp(cl, cr) => Inner::Comp(cl?, cr?),
            Inner::Case(cl, cr) => Inner::Case(cl?, cr?),
            Inner::AssertL(c, cmr) => Inner::AssertL(c?, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c?),
            Inner::Pair(cl, cr) => Inner::Pair(cl?, cr?),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl?, cr),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        })
    }
}
impl<C, J, X, W> Inner<C, J, Option<X>, W> {
    pub fn transpose_disconnect(self) -> Option<Inner<C, J, X, W>> {
        Some(match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, cr?),
            Inner::Witness(w) => Inner::Witness(w),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        })
    }
}
impl<C, J, X, W> Inner<C, J, X, Option<W>> {
    pub fn transpose_witness(self) -> Option<Inner<C, J, X, W>> {
        Some(match self {
            Inner::Iden => Inner::Iden,
            Inner::Unit => Inner::Unit,
            Inner::InjL(c) => Inner::InjL(c),
            Inner::InjR(c) => Inner::InjR(c),
            Inner::Take(c) => Inner::Take(c),
            Inner::Drop(c) => Inner::Drop(c),
            Inner::Comp(cl, cr) => Inner::Comp(cl, cr),
            Inner::Case(cl, cr) => Inner::Case(cl, cr),
            Inner::AssertL(c, cmr) => Inner::AssertL(c, cmr),
            Inner::AssertR(cmr, c) => Inner::AssertR(cmr, c),
            Inner::Pair(cl, cr) => Inner::Pair(cl, cr),
            Inner::Disconnect(cl, cr) => Inner::Disconnect(cl, cr),
            Inner::Witness(w) => Inner::Witness(w?),
            Inner::Fail(entropy) => Inner::Fail(entropy),
            Inner::Jet(j) => Inner::Jet(j),
            Inner::Word(w) => Inner::Word(w),
        })
    }
}
impl<C, J: Clone, X, W: Copy> Inner<C, J, X, &W> {
    pub fn copy_witness(self) -> Inner<C, J, X, W> {
        self.map_witness(W::clone)
    }
}
impl<C, J: Clone, X: Copy, W> Inner<C, J, &X, W> {
    pub fn copy_disconnect(self) -> Inner<C, J, X, W> {
        self.map_disconnect(X::clone)
    }
}
impl<C, J: fmt::Display, X, W> fmt::Display for Inner<C, J, X, W> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Inner::Iden => f.write_str("iden"),
            Inner::Unit => f.write_str("unit"),
            Inner::InjL(_) => f.write_str("injl"),
            Inner::InjR(_) => f.write_str("injr"),
            Inner::Take(_) => f.write_str("take"),
            Inner::Drop(_) => f.write_str("drop"),
            Inner::Comp(_, _) => f.write_str("comp"),
            Inner::Case(_, _) => f.write_str("case"),
            Inner::AssertL(_, _) => f.write_str("assertl"),
            Inner::AssertR(_, _) => f.write_str("assertr"),
            Inner::Pair(_, _) => f.write_str("pair"),
            Inner::Disconnect(_, _) => f.write_str("disconnect"),
            Inner::Witness(..) => f.write_str("witness"),
            Inner::Fail(..) => f.write_str("fail"),
            Inner::Jet(jet) => write!(f, "jet({})", jet),
            Inner::Word(w) => write!(f, "word({})", w),
        }
    }
}