use crate::{
    Binder1,
    BinderN,
    Comp,
    Pattern,
    Val,
    VName,
};

impl Binder1 {
    fn substitute(self, x: &VName, v: &Val) -> Self {
        match self {
            Self::Eq(pos, args1, args2) => {
                let args1 =
                    args1.into_iter().map(|a| a.substitute(x,v));
                let args2 =
                    args2.into_iter().map(|a| a.substitute(x,v));
                Self::Eq(pos, args1.collect(), args2.collect())
            }
            Self::LogQuantifier(q, xs, m) => {
                let shadowed = xs.iter().any(|(x2,_)| x == x2);
                if shadowed {
                    Self::LogQuantifier(q,xs,m)
                } else {
                    let m2 = m.substitute(x,v);
                    Self::LogQuantifier(q, xs, Box::new(m2))
                }
            }
            Self::LogNot(v2) => Self::LogNot(v2.substitute(x,v)),
            Self::LogOpN(op, vs) => {
                let mut vs2 = Vec::new();
                for v2 in vs {
                    vs2.push(v2.substitute(x,v));
                }
                Self::LogOpN(op.clone(), vs2)
            }
        }
    }
}

impl BinderN {
    fn substitute(self, x: &VName, v: &Val) -> Self {
        match self {
            Self::Call(s,vs) => Self::Call(
                s,
                vs.into_iter().map(|v1| v1.substitute(x,v)).collect(),
            ),
            Self::Seq(m) => Self::Seq(Box::new(m.substitute(x, v))),
        }
    }
}

impl Comp {
    pub fn substitute(self, x: &VName, v: &Val) -> Self {
        match self {
            Self::Apply(m, vs) => {
                Self::apply(
                    m.substitute(x,v),
                    vs
                        .into_iter()
                        .map(|v1| v1.substitute(x,v))
                        .collect::<Vec<Val>>()
                )
            }
            Self::Return(vs) => {
                Self::return_many(
                    vs
                        .into_iter()
                        .map(|e| e.substitute(x,v))
                        .collect::<Vec<Val>>()
                )
            }
            Self::Bind1(b, x2, m) => {
                let b2 = b.substitute(x,v);
                let m2 = if *x == x2 {
                    m.clone()
                } else {
                    Box::new(m.substitute(x,v))
                };
                Self::Bind1(b2, x2.clone(), m2)
            }
            Self::BindN(b, ps, m) => {
                let b2 = b.substitute(x,v);
                let m2 = if ps.iter().any(|p| p.contains(x)) {
                    m
                } else {
                    Box::new(m.substitute(x,v))
                };
                Self::BindN(b2, ps, m2)
            }
            Self::Force(v1) => Self::Force(v1.substitute(x,v)),
            Self::Fun(xs, m) => {
                let names: Vec<&VName> = xs.iter().map(|(x,_)| x).collect();
                if !names.contains(&x) {
                    Self::Fun(xs, Box::new(m.substitute(x,v)))
                } else {
                    Self::Fun(xs, m)
                }
            }
            Self::Ite(cond, then_b, else_b) => {
                Self::ite(
                    cond.substitute(x,v),
                    then_b.substitute(x,v),
                    else_b.substitute(x,v),
                )
            }
        }
    }
    pub fn substitute_many(mut self, ss: &Vec<(VName,Val)>) -> Self {
        for (x,v) in ss {
            self = self.substitute(x,v)
        }
        self
    }
}

impl Pattern {
    fn contains(&self, x: &VName) -> bool {
        match self {
            Self::NoBind => false,
            Self::Atom(y) => x == y,
            Self::Tuple(ps) => ps.iter().any(|p| p.contains(x)),
        }
    }
}

impl Val {
    fn substitute(self, x: &VName, v: &Val) -> Self {
        match self {
            Self::Var(x2) if *x == x2 => { v.clone() },
            Self::Thunk(c) => Self::Thunk(Box::new(c.substitute(x,v))),
            Self::Tuple(vs) => Self::Tuple(
                vs
                    .into_iter()
                    .map(|v1| v1.substitute(x,v))
                    .collect()
            ),
            _ => self,
        }
    }
}
