/* Resp Serialisation
 * Copyright (C) 2008-2009 David Robillard <dave@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 "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_tuple(ostream& out, const ATuple* tup, ATuple::const_iterator i,
            unsigned indent, bool newlines, CEnv* cenv, bool types, bool elem_types)
{
	for (; i != tup->end(); ) {
		ATuple::const_iterator next = i;
		++next;
		
		print_to(out, *i, newlines ? indent + 2 : indent, cenv, types);
		if (elem_types)
			out << " :" << cenv->tsubst.apply(cenv->tenv.var(*i));

		if (next != tup->end()) {
			if (newlines)
				newline(out, indent + 2);
			else
				out << " ";
		}
		
		i = next;
	}

	return (out << ")");
}
	
ostream&
print_to(ostream& out, const AST* ast, unsigned indent, CEnv* cenv, bool types)
{
	const ALexeme* lexeme = ast->to<const ALexeme*>();
	if (lexeme)
		return out << *lexeme;

	const ALiteral<float>* flit = ast->to<const ALiteral<float>*>();
	if (flit)
		return out << showpoint << flit->val;

	const ALiteral<int32_t>* ilit = ast->to<const ALiteral<int32_t>*>();
	if (ilit)
		return out << ilit->val;

	const ALiteral<bool>* blit = ast->to<const ALiteral<bool>*>();
	if (blit)
		return out << (blit->val ? "#t" : "#f");

	const AString* str = ast->to<const AString*>();
	if (str)
		return out << '"' << *str << '"';

	const ASymbol* sym = ast->to<const ASymbol*>();
	if (sym)
		return out << sym->cppstr;

	const AType* type = ast->to<const AType*>();
	if (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
		}
	 }

	const ATuple* tup = ast->to<const ATuple*>();
	if (tup) {
		out << "(";
		ATuple::const_iterator i = tup->begin();

		std::string form = "";
		if (i != tup->end()) {
			const ASymbol* sym = (*i)->to<const ASymbol*>();
			if (sym) {
				form = sym->cppstr;
			} else {
				const ALexeme* lexeme = (*i)->to<const ALexeme*>();
				if (lexeme)
					form = *lexeme;
			}
		}
		
		if (form == "def") {
			out << (*i++) << " ";

			// Print symbol (possibly with type annotation)
			const AST* sym = *i++;
			out << sym;
			if (types)
				out << " :" << cenv->tsubst.apply(cenv->tenv.var(sym));

			// Print value on following lines, indented
			newline(out, indent + 2);
			return print_tuple(out, tup, i, indent, true, cenv, types, false);
				
		} else if (form == "fn") {
			out << (*i++) << " ";
			const ATuple* pat = (*i++)->as<const ATuple*>();

			// Print prototype (possibly with parameter type annotations)
			out << "(";
			print_tuple(out, pat, pat->begin(), indent, false, cenv, types, types);

			// Print body expression(s) on following lines, indented
			newline(out, indent + 2);
			return print_tuple(out, tup, i, indent + 2, true, cenv, types, false);

		} else if (form == "if") {
			out << (*i++) << " ";

			// Print each condition and consequent pair separated by blank lines
			for (; i != tup->end(); ) {
				ATuple::const_iterator next = i;
				++next;
		
				print_to(out, *i, indent + 2, cenv, types);
				if (next != tup->end()) {
					newline(out, indent + 2);
					print_to(out, *next++, indent + 2, cenv, types);
					newline(out, 0);
					newline(out, indent + 2);
				}

				i = next;
			}

			return out;
			
		} else {
			return print_tuple(out, tup, i, indent + 1, false, cenv, types, false);
		}
	}
	
	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;
}