/* Resp: A programming language
 * Copyright (C) 2008-2009 David Robillard <http://drobilla.net>
 *
 * Resp is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Resp is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Resp.  If not, see <http://www.gnu.org/licenses/>.
 */

/** @file
 * @brief Pretty-print AST expressions
 */

#include <string>

#include "resp.hpp"

ostream&
print_to(ostream& out, const AST* ast, unsigned indent, CEnv* cenv, bool types);

static void
newline(ostream& out, unsigned indent)
{
	out << endl;
	for (unsigned i = 0; i < indent; ++i)
		out << " ";
}

static inline void
print_annotation(ostream& out, const AST* ast, unsigned indent, CEnv* cenv, bool print)
{
	if (print) {
		const AST* var = cenv->tenv.var(ast);
		if (var) {
			out << " :";
			print_to(out, cenv->tsubst.apply(var), indent + 2, cenv, false);
		}
	}
}

ostream&
print_list_one_line(ostream& out, const ATuple* tup, ATuple::const_iterator i,
            unsigned indent, CEnv* cenv, bool types, const ATuple* elemsT)
{
	ATuple::const_iterator ti = elemsT ? elemsT->begin() : tup->end();
	for (; i != tup->end(); ) {
		ATuple::const_iterator next = i;
		++next;

		print_to(out, *i, indent, cenv, types);
		if (elemsT) {
			out << " :";
			print_to(out, *ti++, indent, cenv, false);
		}

		if (next != tup->end())
			out << " ";

		i = next;
	}

	return (out << ")");
}

/** Print list arguments on separate lines, possibly in separated pairs, e.g.:
 * (elem one)
 * (elem two)
 * (elem three)
 *
 * or, if split_pairs is true:
 *
 * (elem one)
 * (elem two)
 *
 * (elem three)
 * (elem four)
 */
ostream&
print_list(ostream& out, const ATuple* tup, ATuple::const_iterator i,
           unsigned indent, CEnv* cenv, bool types, bool split_pairs)
{
	for (; i != tup->end(); ) {
		ATuple::const_iterator next = i;
		++next;

		print_to(out, *i, indent, cenv, types);
		if (next != tup->end()) {
			newline(out, indent);
			print_to(out, *next++, indent, cenv, types);
			if (next != tup->end()) {
				if (split_pairs)
					newline(out, 0);
				newline(out, indent);
			}
		}

		i = next;
	}
	return out << ")";
}

ostream&
print_to(ostream& out, const AST* ast, unsigned indent, CEnv* cenv, bool types)
{
	switch (ast->tag()) {
	case T_UNKNOWN:
		return out << "?";
	case T_TVAR:
		return out << "?" << AType::var_id(ast);
	case T_TUPLE:
	{
		const ATuple* tup = ast->as_tuple();
		out << "(";
		ATuple::const_iterator i = tup->begin();
		if (i == tup->end())
			return out << ")";

		std::string    form         = "";
		const ASymbol* sym          = (*i)->to_symbol();
		unsigned       head_width   = 1;
		bool           head_is_list = (*i)->to_tuple();
		if (sym) {
			form = sym->sym();
			head_width = form.length() + 2;
		}

		print_to(out, *i++, indent + 1, cenv, types);
		if (i != tup->end()) {
			if (head_is_list)
				newline(out, indent + 1);
			else
				out << " ";
		}

		if (form == "def") {
			if (tup->rrst() && is_form(tup->frrst(), "fn")) {
				// Abreviate (def (fn (...) ...))
				out << "(" << (*i++) << " ";
				const ATuple* const fn   = tup->frrst()->as_tuple();
				const ATuple* const prot = fn->frst()->as_tuple();
				if (types) {
					const ATuple* const fnT  = cenv->type(fn)->as_tuple();
					print_list_one_line(out, prot, prot->begin(), indent + 7, cenv, types, fnT->prot());
					print_annotation(out, fn->list_last(), indent + head_width + 1, cenv, types);
				} else {
					print_list_one_line(out, prot, prot->begin(), indent + 7, cenv, types, NULL);
				}

				newline(out, indent + 2);
				print_list(out, fn, fn->iter_at(2), indent + 2, cenv, types, false);
			} else {
				const unsigned child_indent = types ? indent + 2 : indent;
				out << (*i++); // Print symbol
				if (types) {
					print_annotation(out, tup->list_ref(2), indent + head_width + 1, cenv, true);
					newline(out, child_indent);
				}
				out << " ";
				print_to(out, *i++, child_indent, cenv, types);
			}
			out << ")";

		} else if (form == "def-type") {
			out << (*i++);
			newline(out, indent + 2);
			print_list(out, tup, i, indent + 2, cenv, types, false);

		} else if (form == "fn") {
			// Print prototype (possibly with parameter type annotations)
			const ATuple* pat = (*i++)->as_tuple();
			out << "(";
			const ATuple* type  = types ? cenv->type(tup)->to_tuple() : NULL;
			const ATuple* protT = type ? type->prot() : NULL;
			print_list_one_line(out, pat, pat->begin(), indent + 2, cenv, types, protT);

			// Print body expression(s) indented on the following lines
			newline(out, indent + 2);
			print_list(out, tup, i, indent + 2, cenv, false, false);

		} else if (form == "if") {
			print_list(out, tup, i, indent + 4, cenv, types, true);

		} else if (form == "let") {
			out << "(";
			const ATuple* vars = (*i)->as_tuple();
			for (ATuple::const_iterator v = vars->begin(); v != vars->end();) {
				out << (*v);
				print_annotation(out, *v, indent, cenv, types);

				out << " " << (*++v);

				if (++v != vars->end())
					newline(out, indent + 6);
				else
					out << ")";
			}
			newline(out, indent + 2);
			print_list(out, tup, tup->iter_at(2), indent + 2, cenv, false, false);
			out << ")";
			//print_annotation(out, tup->list_last(), indent, cenv, types);

		} else if (form == "match") {
			out << (*i++);
			newline(out, indent + 2);
			print_list(out, tup, i, indent + 2, cenv, types, true);

		} else if (form == "do") {
			newline(out, indent + 2);
			print_list(out, tup, i, indent + 2, cenv, types, false);

		} else {
			// Print on multiple lines if list contains lists
			for (ATuple::const_iterator ti = tup->begin(); ti != tup->end(); ++ti) {
				if ((*ti)->to_tuple()) {
					print_list(out, tup, i, indent + head_width, cenv, types, false);
					return out;
				}
			}

			// Print on single line if list contains only atoms
			print_list_one_line(out, tup, i, indent + head_width, cenv, types, false);
		}

		return out;
	}
	case T_BOOL:
		return out << ((((const ALiteral<bool>*)ast)->val) ? "#t" : "#f");
	case T_FLOAT:
		return out << showpoint << ((const ALiteral<float>*)ast)->val;
	case T_INT32:
		return out << showpoint << ((const ALiteral<int32_t>*)ast)->val;
	case T_STRING:
		return out << '"' << ((const AString*)ast)->cppstr << '"';
	case T_SYMBOL:
	case T_LITSYM:
		return out << ((const ASymbol*)ast)->sym();
	}

	return out << "?";
}

ostream&
operator<<(ostream& out, const AST* ast)
{
	return print_to(out, ast, 0, NULL, false);
}

/// Pretty-print @a ast to @a out
void
pprint(ostream& out, const AST* ast, CEnv* cenv, bool types)
{
	print_to(out, ast, 0, cenv, types);
	print_annotation(out, ast, 0, cenv, types);
	out << endl << endl;
}