use super::*;
use crate::sim::Sim;
use crate::sim::Value;
impl Expr {
    pub fn eval(&self, bitsy: &Sim) -> Value {
        self.eval_with_ctx(bitsy, Context::empty())
    }
    fn eval_with_ctx(&self, bitsy: &Sim, ctx: Context<Path, Value>) -> Value {
        match self {
            Expr::Reference(_loc, _typ, path) => {
                if let Some(value) = ctx.lookup(path) {
                    value.clone()
                } else {
                    bitsy.peek(path.clone())
                }
            }
            Expr::Net(_loc, _typ, netid) => bitsy.peek_net(*netid),
            Expr::Word(_loc, typ, _width, value) => {
                if let Type::Word(width) = typ.get().unwrap() {
                    Value::Word(*width, *value)
                } else {
                    unreachable!()
                }
            },
            Expr::Enum(_loc, _typ, typedef, name) => Value::Enum(typedef.clone(), name.clone()),
            Expr::Ctor(_loc, _typ, name, es) => {
                let values: Vec<Value> = es.iter().map(|e| e.eval_with_ctx(bitsy, ctx.clone())).collect();
                Value::Ctor(name.to_string(), values)
            },
            Expr::Struct(_loc, typ, fields) => {
                let mut field_values = vec![];
                for (name, e) in fields {
                    let value = e.eval_with_ctx(bitsy, ctx.clone());
                    field_values.push((name.to_string(), value));
                }
                Value::Struct(typ.get().unwrap().clone(), field_values)
            },
            Expr::Let(_loc, _typ, name, _ascription, e, b) => {
                let v = e.eval_with_ctx(bitsy, ctx.clone());
                b.eval_with_ctx(bitsy, ctx.extend(name.clone().into(), v))
            },
            Expr::UnOp(_loc, _typ, op, e) => {
                match (op, e.eval_with_ctx(bitsy, ctx.clone())) {
                    (UnOp::Not, Value::Word(n, v)) => Value::Word(n, (!v) & ((1 << n) - 1)),
                    _ => Value::X,
                }
            },
            Expr::BinOp(_loc, _typ, op, e1, e2) => {
                match (op, e1.eval_with_ctx(bitsy, ctx.clone()), e2.eval_with_ctx(bitsy, ctx.clone())) {
                    (BinOp::Add, Value::X, _other) => Value::X,
                    (BinOp::Add, _other, Value::X) => Value::X,
                    (BinOp::Add, Value::Word(w, a),  Value::Word(_w, b)) => Value::Word(w, a.wrapping_add(b) % (1 << w)),
                    (BinOp::AddCarry, Value::Word(w, a),  Value::Word(_w, b)) => {
                        let new_w = w + 1;
                        Value::Word(new_w, a.wrapping_add(b) % (1 << new_w))
                    },
                    (BinOp::Sub, Value::Word(w, a),  Value::Word(_w, b)) => Value::Word(w, a.wrapping_sub(b) % (1 << w)),
                    (BinOp::And, Value::Word(w, a),  Value::Word(_w, b)) => Value::Word(w, a & b),
                    (BinOp::Or,  Value::Word(w, a),  Value::Word(_w, b)) => Value::Word(w, a | b),
                    (BinOp::Eq,  Value::Word(_w, a), Value::Word(_v, b)) => (a == b).into(),
                    (BinOp::Eq,  Value::Enum(_typedef, a), Value::Enum(_typedef2, b)) => (a == b).into(),
                    (BinOp::Lt,  Value::Word(_w, a), Value::Word(_v, b)) => (a < b).into(),
                    (BinOp::Neq, Value::Word(_w, a), Value::Word(_v, b)) => (a != b).into(),
                    (BinOp::Xor, Value::Word(n, a),  Value::Word(_m, b)) => Value::Word(n, a ^ b),
                    _ => Value::X,
                }
            },
            Expr::If(_loc, _typ, cond, e1, e2) => {
                let cond_v = cond.eval_with_ctx(bitsy, ctx.clone());
                if cond_v.is_x() {
                    return Value::X;
                }
                match cond_v {
                    Value::Word(1, 1) => e1.eval_with_ctx(bitsy, ctx.clone()),
                    Value::Word(1, 0) => e2.eval_with_ctx(bitsy, ctx.clone()),
                    _ => Value::X,
                }
            },
            Expr::Match(loc, _typ, subject, arms) => {
                let subject_value = subject.eval_with_ctx(bitsy, ctx.clone());
                if subject_value.is_x() {
                    return Value::X;
                }
                for MatchArm(pat, e) in arms {
                    if let Some(new_ctx) = pat.bind(&subject_value, ctx.clone()) {
                        let e_value = e.eval_with_ctx(bitsy, new_ctx);
                        return e_value;
                    }
                }
                panic!("No match arm matched: {loc:?}")
            },
            Expr::Mux(_loc, _typ, cond, e1, e2) => {
                let cond_v = cond.eval_with_ctx(bitsy, ctx.clone());
                if cond_v.is_x() {
                    return Value::X;
                }
                match cond_v {
                    Value::Word(1, 1) => e1.eval_with_ctx(bitsy, ctx.clone()),
                    Value::Word(1, 0) => e2.eval_with_ctx(bitsy, ctx.clone()),
                    _ => Value::X,
                }
            },
            Expr::Cat(loc, _typ, es) => {
                let mut cat_width: u64 = 0;
                let mut cat_val: u64 = 0;
                let mut wss: Vec<Value> = vec![];
                for v in es.iter().map(|e| e.eval_with_ctx(bitsy, ctx.clone())).rev() {
                    if let Value::X = v {
                        return Value::X;
                    } else if let Value::Word(width, val) = v {
                        cat_val |= val << cat_width;
                        cat_width += width;
                    } else if let Value::Vec(ws) = v {
                        wss.extend(ws.into_iter().rev());
                    } else {
                        panic!("Can't cat on a non-Word {loc:?}");
                    }
                }
                if wss.len() == 0 {
                    Value::Word(cat_width, cat_val)
                } else {
                    Value::Vec(wss.into_iter().rev().collect())
                }
            },
            Expr::Sext(loc, typ, e) => {
                let n = if let Type::Word(n) = typ.get().unwrap() {
                    n
                } else {
                    unreachable!()
                };
                match e.eval_with_ctx(bitsy, ctx.clone()) {
                    Value::X => Value::X,
                    Value::Word(0, _x) => panic!("Can't sext a Word[0] {loc:?}"),
                    Value::Word(w, x) => {
                        if w <= *n {
                            let is_negative = x & (1 << (w - 1)) > 0;
                            if is_negative {
                                let flips = ((1 << (n - w)) - 1) << w;
                                Value::Word(*n, flips | x)
                            } else {
                                Value::Word(*n, x)
                            }
                        } else {
                            panic!("Can't sext a Word[{w}] to Word[{n}] because {w} > {n}. {loc:?}")
                        }
                    },
                    Value::Vec(_vs) => panic!("Can't sext a Vec {loc:?}"),
                    Value::Enum(typedef, _name) => panic!("Can't sext a {} {loc:?}", typedef.name()),
                    _ => panic!("Can't sext {self:?} {loc:?}"),
                }
            },
            Expr::Zext(loc, typ, e) => {
                let n = if let Type::Word(n) = typ.get().unwrap() {
                    n
                } else {
                    unreachable!()
                };
                match e.eval_with_ctx(bitsy, ctx.clone()) {
                    Value::X => Value::X,
                    Value::Word(w, x) => {
                        if w <= *n {
                            Value::Word(*n, x)
                        } else {
                            panic!("Can't sext a Word[{w}] to Word[{n}] because {w} > {n}. {loc:?}")
                        }
                    },
                    Value::Vec(_vs) => panic!("Can't sext a Vec {loc:?}"),
                    Value::Enum(typedef, _name) => panic!("Can't sext a {} {loc:?}", typedef.name()),
                    _ => panic!("Can't sext {self:?} {loc:?}"),
                }
            },
            Expr::TryCast(_loc, typ, e) => {
                let typ = typ.get().unwrap();
                let typedef = if let Type::Valid(inner_type) = typ {
                    if let Type::Enum(typedef) = &**inner_type {
                        typedef
                    } else {
                        unreachable!()
                    }
                } else {
                    unreachable!()
                };
                let value = e.eval_with_ctx(bitsy, ctx.clone());
                if let Value::X = value {
                    return Value::X;
                }
                let value = value.to_u64().unwrap();
                for (name, WordLit(_w, v)) in &typedef.values {
                    if value == *v {
                        return Value::Ctor("Valid".to_string(), vec![Value::Enum(Type::Enum(typedef.clone()), name.clone())]);
                    }
                }
                return Value::Ctor("Invalid".to_string(), vec![]);
            },
            Expr::ToWord(loc, _typ, e) => {
                let v = e.eval_with_ctx(bitsy, ctx.clone());
                match &v {
                    Value::X => Value::X,
                    Value::Enum(typ, name) => {
                        if let Type::Enum(typ) = typ {
                            Value::Word(typ.bitwidth(), typ.value_of(&name).expect(&format!("{loc:?}")))
                        } else {
                            panic!("{loc:?}: Can't call word() on {v:?}");
                        }
                    },
                    _ => panic!("Can only call word() on enum values, but found {v:?} {loc:?}"),
                }
            },
            Expr::Vec(_loc, _typ, es) => {
                let mut vs = vec![];
                for v in es.iter().map(|e| e.eval_with_ctx(bitsy, ctx.clone())) {
                    if let Value::X = v {
                        return Value::X;
                    } else {
                        vs.push(v.clone());
                    }
                }
                Value::Vec(vs)
            },
            Expr::IdxField(_loc, _typ, e, field) => {
                let value = e.eval_with_ctx(bitsy, ctx.clone());
                if let Value::X = value {
                    Value::X
                } else if let Value::Struct(_typ, fields) = e.eval_with_ctx(bitsy, ctx.clone()) {
                    for (fieldname, fieldval) in fields {
                        if *field == fieldname {
                            return fieldval;
                        }
                    }
                    panic!();
                } else {
                    panic!();
                }
            },
            Expr::Idx(_loc, _typ, e, i) => {
                let value = e.eval_with_ctx(bitsy, ctx.clone());
                if let Value::X = value {
                    Value::X
                } else if let Value::Word(width, val) = value {
                    if *i < width {
                        Value::Word(1, (val >> i) & 1)
                    } else {
                        panic!("Index at {i} out of range (width {width})")
                    }
                } else if let Value::Vec(vs) = value {
                    if *i < vs.len().try_into().unwrap() {
                        vs[*i as usize].clone()
                    } else {
                        panic!("Index at {i} out of range (length {})", vs.len())
                    }
                } else {
                        panic!("Index with invalid value: {value:?}")
                }
            },
            Expr::IdxRange(_loc, _typ, e, j, i) => {
                let value = e.eval_with_ctx(bitsy, ctx.clone());
                if let Value::X = value {
                    Value::X
                } else if let Value::Word(width, val) = value {
                    if width >= *j && *j >= *i {
                        let new_width = j - i;
                        let mask = (1 << new_width) - 1;
                        Value::Word(new_width, (val >> i) & mask)
                    } else {
                        panic!("Index {j}..{i} out of range (width {width})")
                    }
                } else if let Value::Vec(vs) = value {
                    let width = vs.len();
                    if j <= i && *i <= width as u64 {
                        Value::Vec(vs[*j as usize..*i as usize].to_vec())
                    } else {
                        panic!("Index {j}..{i} out of range (length {width})")
                    }
                } else {
                    panic!("Can't index into value: {value:?}")
                }
            },
            Expr::Call(_loc, _typ, fndef, es) => {
                assert_eq!(fndef.args.len(), es.len());
                let mut new_ctx = ctx.clone();
                for ((arg_name, _arg_typ), e) in fndef.args.iter().zip(es.iter()) {
                    let v = e.eval_with_ctx(bitsy, ctx.clone());
                    new_ctx = new_ctx.extend(arg_name.clone().into(), v);
                }
                fndef.body.eval_with_ctx(bitsy, new_ctx)
            },
            Expr::Hole(loc, _typ, opt_name) => {
                let name = opt_name.clone().unwrap_or_default();
                eprintln!("{loc} EVALUATED A HOLE: ?{name}: {ctx}");
                Value::X
            },
        }
    }
}
impl Pat {
    fn bind(&self, v: &Value, ctx: Context<Path, Value>) -> Option<Context<Path, Value>> {
        match self {
            Pat::At(ctor, pats) => {
                match v {
                    Value::X => None,
                    Value::Word(_w, _n) => None,
                    Value::Vec(_vs) => None,
                    Value::Ctor(v_ctor, vs) => {
                        if ctor == v_ctor {
                            assert_eq!(pats.len(), vs.len());
                            let mut ctx_result = ctx.clone();
                            for (pat, v) in pats.iter().zip(vs.iter()) {
                                if let Some(ctx) = pat.bind(v, ctx_result.clone()) {
                                    ctx_result = ctx;
                                } else {
                                    return None;
                                }
                            }
                            Some(ctx_result)
                        } else {
                            None
                        }
                    },
                    Value::Enum(_typ, val) => {
                        assert_eq!(pats.len(), 0);
                        if ctor == val {
                            Some(ctx)
                        } else {
                            None
                        }
                    },
                    Value::Struct(_typ, _fields) => None,
                }
            },
            Pat::Bind(x) => Some(ctx.extend(x.clone().into(), v.clone())),
            Pat::Otherwise => Some(ctx),
        }
    }
}