/* 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)
{
	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();

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

			// Print symbol (possibly with type annotation)
			const AST* sym = *i++;
			out << sym;
			if (types)
				out << " :" << cenv->type(tup->list_ref(2));

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

			// 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 if (form == "def-type") {
			print_tuple(out, tup, i, indent + 1, false, cenv, types, false);
			newline(out, 0);
			return out;

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