use std::collections::HashMap;

mod types;
mod pretty;
use types::*;
use pretty::show;

impl Stmt {
    fn eval(&self, env: &mut Env) -> Result<Value, String> {
        match *self {
            Stmt::Expr(ref e) => e.eval(env),
            Stmt::Let(ref p, _, _, ref e) => {
                p.bind(e.eval(env)?, env);
                Ok(Value::Unit)
            },
        }
    }
}

impl Pat {
    fn bind(&self, val: Value, env: &mut Env) {
        match *self {
            Pat::Var(ref n) => {
                let _ = env.insert(n.clone(), val);
            },
            Pat::Tuple(ref ps) => {
                match val {
                    Value::Tuple(es) => {
                        if es.len() != ps.len() {
                            panic!("non-matching tuples!");
                        }
                        for (p, e) in ps.iter().zip(es.into_iter()) {
                            p.bind(e, env);
                        }
                    }
                    _ => panic!("not matching a tuple!"),
                }
            }
        }
    }

}

impl Expr {
    pub fn eval(&self, env: &mut Env) -> Result<Value, String> {
        match *self {
            Expr::Var(ref n) => Ok(env.get(n).unwrap().clone()),

            Expr::Lit(ref l) => Ok(Value::Lit(l.clone())),

            Expr::Fn(ref pat, ref e) =>
                Ok(Value::Closure(
                    *pat.clone(),
                    *e.clone(),
                    Box::new(env.clone()),
                )),

            Expr::App(ref e1, ref e2) => {
                let v1 = e1.eval(env)?;
                let v2 = e2.eval(env)?;
                match v1 {
                    Value::Closure(p, e, mut env2) => {
                        p.bind(v2, &mut env2);
                        e.eval(&mut env2)
                    },
                    _ => return Err(format!("not a function")),
                }
            },

            Expr::Do(ref stmts) => {
                let mut result = Value::Unit;
                for stmt in stmts.iter() {
                    result = stmt.eval(env)?;
                }
                Ok(result)
            },

            Expr::Tuple(ref exprs) => {
                let res: Result<Vec<Value>, String> =
                    exprs.iter().map(|e|e.eval(env)).collect();
                Ok(Value::Tuple(res?))
            },
        }
    }

}

fn test(e: &Expr) {
    let mut env = HashMap::new();
    println!("> {}", show(e));
    println!("  -> {}", show(&e.eval(&mut env).unwrap()));
}

fn main() {
    let sample = Expr::mk_app(
        Expr::mk_fn(
            Pat::mk_var("x"),
            Expr::mk_fn(
                Pat::mk_var("y"),
                Expr::mk_tuple(vec![
                    Expr::mk_var("x"),
                    Expr::mk_var("y"),
                ]),
            ),
        ),
        Expr::Lit(Literal::Int(5)),
     );

    test(&sample);

    let rs = Expr::mk_app(sample, Expr::Lit(Literal::Int(6)));
    test(&rs);

    let sample2 = Expr::mk_fn(
        Pat::mk_tuple(vec![
            Pat::mk_var("a"),
            Pat::mk_var("b"),
        ]),
        Expr::mk_var("a"),
        );

    test(&Expr::mk_app(sample2, rs));
}
