use std::collections::HashMap;

pub type Ident = String;
pub type Env = HashMap<Ident, Value>;


#[derive(Debug, Clone)]
pub enum Stmt {
    Let(Pat, Option<Type>, Vec<Pat>, Expr),
    Expr(Expr),
}

#[derive(Debug, Clone)]
pub enum Pat {
    Var(Ident),
    Tuple(Vec<Pat>),
}

impl Pat {
    pub fn mk_var<S: Into<String>>(s: S) -> Pat {
        Pat::Var(s.into())
    }

    pub fn mk_tuple(pats: Vec<Pat>) -> Pat {
        Pat::Tuple(pats)
    }
}

#[derive(Debug, Clone)]
pub enum Type {
    Prim(PrimType),
    Arr(Box<Type>, Box<Type>),
}

#[derive(Debug, Clone)]
pub enum PrimType {
    Unit,
    Nat,
}

#[derive(Debug, Clone)]
pub enum Expr {
    Var(Ident),
    Lit(Literal),
    Fn(Box<Pat>, Box<Expr>),
    App(Box<Expr>, Box<Expr>),
    Do(Vec<Stmt>),
    Tuple(Vec<Expr>),
}

#[derive(Debug, Clone)]
pub enum Value {
    Closure(Pat, Expr, Box<Env>),
    Lit(Literal),
    Tuple(Vec<Value>),
    Unit,
}

#[derive(Debug, Clone)]
pub enum Literal {
    Int(i64),
}

impl Expr {
    pub fn mk_fn(p: Pat, e: Expr) -> Expr {
        Expr::Fn(Box::new(p), Box::new(e))
    }

    pub fn mk_app(e1: Expr, e2: Expr) -> Expr {
        Expr::App(Box::new(e1), Box::new(e2))
    }

    pub fn mk_var<S: Into<String>>(s: S) -> Expr {
        Expr::Var(s.into())
    }

    pub fn mk_tuple(es: Vec<Expr>) -> Expr {
        Expr::Tuple(es)
    }
}
