/* 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 << " ";
}

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

		print_to(out, *i, indent, cenv, types);
		if (elem_types)
			out << " :" << cenv->tsubst.apply(cenv->tenv.var(*i));

		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 (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_TYPE:
	{
		const AType* type = ast->as_type();
		switch (type->kind) {
		case AType::VAR:   return out << "?" << type->id;
		case AType::NAME:  return out << type->head();
		case AType::PRIM:  return out << type->head();
		case AType::DOTS:  return out << "...";
		case AType::EXPR:  break; // will catch Tuple case below
		}
	}
	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();
		if (sym)
			form = sym->sym();

		out << (*i++);
		if (i != tup->end())
			out << " ";

		if (form == "def") {
			out << (*i++) << " "; // Print symbol
			unsigned child_indent = types ? indent + 2 : indent;
			if (types) {
				out << ":" << cenv->type(tup->list_ref(2)) << " ";
				newline(out, child_indent);
			}

			print_to(out, (*i++), child_indent, cenv, types);
			out << ")";
			newline(out, 0);

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

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

			// Print body expression(s) indented on the following lines
			newline(out, indent + 2);
			print_list(out, tup, i, indent + 2, cenv, types, 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);
				if (types)
					out << " :" << cenv->tsubst.apply(cenv->tenv.var(*v));

				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, types, false);
			out << ")";
			if (types)
				out << " :" << cenv->tsubst.apply(cenv->tenv.var(tup->list_last()));

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

		} else {
			print_list_one_line(out, tup, i, indent + 1, 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:
		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);
	out << endl;
}