use template::ast;
use lexical::lex::{error};
use haunparse = lexical::hare::unparse;
use haast = lexical::hare::ast;
use io;
use fmt;

// Unparse a formattable expression
export fn expr(
	out: io::handle,
	e: *ast::expr,
) (void | io::error) = {
	match (e.expr) {
	case let e: ast::fmt_expr => fmt_expr(out, &e)?;
	case let e: ast::if_expr => if_expr(out, &e)?;
	case let e: ast::for_expr => for_expr(out, &e)?;
	case let e: ast::switch_expr => switch_expr(out, &e)?;
	case let e: ast::match_expr => match_expr(out, &e)?;
	case let e: ast::match_case => match_case(out, &e)?;
	case let e: ast::switch_case => switch_case(out, &e)?;
	case let e: ast::binding_expr => binding_expr(out, &e)?;
	};
};

fn fmt_expr(
	out: io::handle,
	e: *ast::fmt_expr,
) (void | io::error) = {
	for (let i = 0z; i < len(e.values); i += 1) {
		if (i != 0) {
			fmt::fprint(out, ", ")?;
		};
		haunparse::expr(out, &haunparse::syn_nowrap, e.values[i])?;
	};
};

fn if_expr(
	out: io::handle,
	e: *ast::if_expr,
) (void | io::error) = {
	fmt::fprint(out, "if (")?;
	haunparse::expr(out, &haunparse::syn_nowrap, e.cond)?;
	fmt::fprint(out, ") ")?;
};

fn for_expr(
	out: io::handle,
	e: *ast::for_expr,
) (void | io::error) = {
	fmt::fprint(out, "for (")?;

	let assign_op = switch (e.kind) {
	case haast::for_kind::ACCUMULATOR =>
		yield "=";
	case haast::for_kind::EACH_VALUE =>
		yield "..";
	case haast::for_kind::EACH_POINTER =>
		yield "&..";
	case haast::for_kind::ITERATOR =>
		yield "=>";
	};

	match (e.bindings) {
	case let bind_expr: *haast::expr =>
		let ctx = haunparse::context {
			out = out,
			...
		};
		haunparse::binding_expr(&ctx,
			&haunparse::syn_nowrap,
			&(bind_expr.expr as haast::binding_expr),
			assign_op)?;

		if (e.kind == haast::for_kind::ACCUMULATOR) {
			fmt::fprint(out, "; ")?;
		};
	case null => void;
	};

	if (e.kind == haast::for_kind::ACCUMULATOR) {
		haunparse::expr(out, &haunparse::syn_nowrap, e.cond as *haast::expr)?;

		match (e.afterthought) {
		case null => void;
		case let e: *haast::expr =>
			fmt::fprint(out, "; ")?;
			haunparse::expr(out, &haunparse::syn_nowrap, e)?;
		};
	};

	fmt::fprint(out, ") ")?;
};

fn switch_expr(
	out: io::handle,
	e: *ast::switch_expr,
) (void | io::error) = {
	fmt::fprint(out, "switch (")?;
	haunparse::expr(out, &haunparse::syn_nowrap, e.value)?;
	fmt::fprint(out, ") ")?;
};

fn match_expr(
	out: io::handle,
	e: *ast::match_expr,
) (void | io::error) = {
	fmt::fprint(out, "match (")?;
	haunparse::expr(out, &haunparse::syn_nowrap, e.value)?;
	fmt::fprint(out, ") ")?;
};

fn match_case(
	out: io::handle,
	e: *ast::match_case,
) (void | io::error) = {
	match (e._type) {
	case null =>
		fmt::fprint(out, "case =>")?;
	case let _type: *haast::_type =>
		switch (e.name) {
		case "" => fmt::fprintf(out, "case ")?;
		case => fmt::fprintf(out, "case let {}: ", e.name)?;
		};
		haunparse::_type(out, &haunparse::syn_nowrap, _type)?;
		fmt::fprint(out, " =>")?;
	};
};

fn switch_case(
	out: io::handle,
	e: *ast::switch_case,
) (void | io::error) = {
	fmt::fprint(out, "case")?;
	for (let i = 0z; i < len(e.options); i += 1) {
		if (i == 0) {
			fmt::fprint(out, " ")?;
		} else {
			fmt::fprint(out, ",\n\t")?;
		};
		haunparse::expr(out, &haunparse::syn_nowrap,
			e.options[i])?;
	};
	fmt::fprint(out, " =>")?;
};

fn binding_expr(
	out: io::handle,
	e: *ast::binding_expr,
) (void | io::error) = {
	switch (e.kind) {
	case haast::binding_kind::DEF =>
		abort();
	case haast::binding_kind::CONST =>
		fmt::fprint(out, "const")?;
	case haast::binding_kind::LET =>
		fmt::fprint(out, "let")?;
	};
	fmt::fprint(out, " ")?;
	for (let i = 0z; i < len(e.bindings); i += 1) {
		let binding = e.bindings[i];

		switch (len(binding.names)) {
		case 0 =>
			fmt::fprint(out, "_")?;
		case 1 =>
			fmt::fprint(out, binding.names[0])?;
		case =>
			fmt::fprint(out, "(")?;
			for (let i = 0z; i < len(binding.names); i += 1) {
				let name = binding.names[i];
				if (name != "") {
					fmt::fprint(out, name)?;
				} else {
					fmt::fprint(out, "_")?;
				};
				if (i + 1 < len(binding.names)) {
					fmt::fprint(out, ", ")?;
				};
			};
			fmt::fprint(out, ")")?;
		};
		match (binding._type) {
		case let t: *haast::_type =>
			fmt::fprint(out, ": ")?;
			haunparse::_type(out, &haunparse::syn_nowrap, t)?;
		case null => void;
		};
		fmt::fprint(out, " = ")?;
		haunparse::expr(out, &haunparse::syn_nowrap, binding.init)?;
		if (i + 1 < len(e.bindings)) {
			fmt::fprint(out, ", ")?;
		};
	};
};
