use std::io::Write;
use std::collections::HashSet;

use crate::parser;

pub struct LexicalContext<'a> {
    parent: Option<&'a LexicalContext<'a>>,
    local: HashSet<String>,
    is_tail: bool,
}

impl <'a> LexicalContext<'a> {
    pub fn toplevel() -> Self {
	LexicalContext {
	    parent: None,
	    local: HashSet::new(),
	    is_tail: false,
	}
    }
    #[allow(dead_code)]
    fn new(parent: &'a LexicalContext<'a>) -> Self {
	LexicalContext {
	    parent: Some(parent),
	    local: HashSet::new(),
	    is_tail: false,
	}
    }
    fn contains(&self, s: &str) -> bool {
	self.local.contains(s) || self.parent.map_or(false, |c| c.contains(s))
    }
    fn insert(&mut self, s: String) -> bool {
	self.local.insert(s)
    }
}

pub fn emit_all(w: &mut dyn Write, ast: &Vec<parser::Stmt>, ctx: &mut LexicalContext) -> std::io::Result<()> {
    for stmt in ast {
	emit(w, stmt, ctx)?;
    }
    Ok(())
}

pub fn emit(w: &mut dyn Write, stmt: &parser::Stmt, ctx: &mut LexicalContext) -> std::io::Result<()> {
    match &stmt {
	parser::Stmt::ReplPrint(expr) => {
	    write!(w, "console.log((")?;
	    emit_expr(w, expr, ctx)?;
	    writeln!(w, "));")?;
	}
	parser::Stmt::Assignment(id, expr) => {
	    if !ctx.contains(id) {
		ctx.insert(id.clone());
		write!(w, "let ")?;
	    }
	    write!(w, "{} = ", id)?;
	    emit_expr(w, expr, ctx)?;
	    writeln!(w, ";")?;
	}
	parser::Stmt::Funcall(call) => {
	    emit_expr(w, call, ctx)?;
	    writeln!(w, ";")?;
	}
	parser::Stmt::Conditional(if_blocks, else_block) => {
	    let mut first_block = true;
	    for if_block in if_blocks {
		if first_block {
		    write!(w, "if (")?;
		} else {
		    write!(w, " else if(")?;
		}
		first_block = false;
		emit_expr(w, &if_block.guard, ctx)?;
		writeln!(w, ") {{")?;
		let mut block_ctx = LexicalContext::new(ctx);
		emit_expr(w, &if_block.block, &mut block_ctx)?;
		writeln!(w, "}}")?;
	    }
	    if let Some(block) = else_block {
		writeln!(w, "else {{")?;
		let mut block_ctx = LexicalContext::new(ctx);
		emit_expr(w, block, &mut block_ctx)?;
		writeln!(w, "}}")?;
	    }
	}
    }
    Ok(())
}

pub fn emit_expr(w: &mut dyn Write, expr: &parser::Expr, ctx: &mut LexicalContext) -> std::io::Result<()> {
    match &expr {
	parser::Expr::Id(id) => {
	    write!(w, "{}", id)?;
	}
	parser::Expr::Atom(atom) => {
	    write!(w, "{}", atom)?;
	}
	parser::Expr::Funcall(id, args) => {
	    write!(w, "{}", id)?;
	    if args.is_empty() {
		write!(w, "()")?;
	    }
	    for arg in args {
		write!(w, "(")?;
		emit_expr(w, arg, ctx)?;
		write!(w, ")")?;
	    }
	}
	parser::Expr::Funcdef(arg, body) => {
	    if let Some(arg) = arg {
		writeln!(w, "function ({}){{", arg)?;
	    } else {
		writeln!(w, "function (){{")?;
	    }
	    let mut fun_ctx = LexicalContext::new(ctx);
	    fun_ctx.is_tail = true;
	    emit_expr(w, body.as_ref(), &mut fun_ctx)?;
	    write!(w, "}}")?;
	}
	parser::Expr::Block(stmts) => {
	    let mut peek_iter = stmts.iter().peekable();
	    while let Some(stmt) = peek_iter.next() {
		if peek_iter.peek().is_none() {
		    write!(w, "return ")?;
		}
		emit(w, stmt, ctx)?;
	    }
	}   
	parser::Expr::Plus(e1, e2) => {
	    emit_expr(w, e1.as_ref(), ctx)?;
	    write!(w, " + ")?;
	    emit_expr(w, e2.as_ref(), ctx)?;
	}
	_ => todo!(),
    }
    Ok(())
}