/* 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 Simplify (reduce to simpler forms, e.g. match => if)
 */

#include <string>

#include "resp.hpp"

using namespace std;

static const AST*
simplify_if(CEnv& cenv, const ATuple* aif) throw()
{
	List copy(aif->loc, cenv.penv.sym("if"), NULL);
	copy.push_back(aif->list_ref(1));
	copy.push_back(aif->list_ref(2));
	
	ATuple* tail = copy.tail;
	ATuple::const_iterator i = aif->iter_at(3);
	for (; ; ++i) {
		ATuple::const_iterator next = i;
		if (++next == aif->end())
			break;

		List inner_if((*i)->loc, cenv.penv.sym("if"), *i, *next, NULL);
		tail->last(new ATuple(inner_if.head, NULL, Cursor()));
		tail = inner_if.tail;

		cenv.setTypeSameAs(inner_if, aif);

		i = next; // jump 2 elements (to the next predicate)
	}

	tail->last(new ATuple(*i, NULL, Cursor()));
	cenv.setTypeSameAs(copy, aif);
	return copy;
}

static const AST*
simplify_match(CEnv& cenv, const ATuple* match) throw()
{
	// Dot expression to get tag.  Note index is -1 to compensate for the lift phase
	// which adds 1 to skip the RTTI, which we don't want here (FIXME: ick...)
	List tval;
	tval.push_back(cenv.penv.sym("."));
	tval.push_back(resp_simplify(cenv, match->list_ref(1)));
	tval.push_back(new ALiteral<int32_t>(T_INT32, -1, Cursor()));

	const ASymbol* tsym = cenv.penv.gensym("__tag");

	List def(match->loc, cenv.penv.sym("def"), tsym, tval.head, NULL);
	cenv.setType(tval.head, cenv.tenv.named("Symbol"));
	
	List copyIf;
	copyIf.push_back(cenv.penv.sym("if"));
	for (ATuple::const_iterator i = match->iter_at(2); i != match->end();) {
		const ATuple* pat  = (*i++)->as_tuple();
		const AST*    body = *i++;

		const ASymbol* consTag = cenv.penv.sym(pat->fst()->str(), pat->fst()->loc);
		const_cast<ASymbol*>(consTag)->tag(T_LITSYM);
		cenv.setType(consTag, cenv.tenv.named("Symbol"));

		List cond(Cursor(), cenv.penv.sym("="), tsym, consTag, 0);
		cenv.setType(cond, cenv.tenv.named("Bool"));

		copyIf.push_back(cond);
		copyIf.push_back(resp_simplify(cenv, body));
	}
	copyIf.push_back(cenv.penv.sym("__unreachable"));
	cenv.setTypeSameAs(copyIf, match);
	
	List copy(match->loc, cenv.penv.sym("do"), def.head, simplify_if(cenv, copyIf), 0);
	cenv.setTypeSameAs(copy, match);

	return copy;
}

static const AST*
simplify_list(CEnv& cenv, const ATuple* call) throw()
{
	List copy;
	for (ATuple::const_iterator i = call->begin(); i != call->end(); ++i)
		copy.push_back(resp_simplify(cenv, *i));

	cenv.setTypeSameAs(copy.head, call);

	return copy;
}

static const AST*
simplify_let(CEnv& cenv, const ATuple* call) throw()
{
	const ATuple* vars = call->list_ref(1)->to_tuple();

	List fn(Cursor(), cenv.penv.sym("fn"), NULL);

	List fnProt;
	List fnArgs;
	List fnProtT;
	for (ATuple::const_iterator i = vars->begin(); i != vars->end();) {
		const ASymbol* sym = (*i++)->to_symbol();
		const AST*     val = (*i++);
		fnProt.push_back(sym);
		fnArgs.push_back(resp_simplify(cenv, val));
		fnProtT.push_back(cenv.type(val));
	}

	fn.push_back(fnProt.head);
	fn.push_back(resp_simplify(cenv, call->list_ref(2)));

	List fnT;
	fnT.push_back(cenv.tenv.Fn);
	fnT.push_back(fnProtT);
	fnT.push_back(cenv.type(call->list_ref(2)));
	cenv.setType(fn, fnT);

	ATuple* copy = new ATuple(fn, fnArgs, call->loc);
	cenv.setTypeSameAs(copy, call);

	return copy;
}

static inline const AST*
simplify_list_elem(CEnv& cenv, const ATuple* node, const AST* type)
{
	if (!node) {
		const AST* empty = new ATuple(cenv.tenv.Empty, 0, Cursor());
		cenv.setType(empty, type);
		return empty;
	}

	assert(cenv.type(node->fst()));
	
	const AST* const fst = tup(Cursor(), cenv.type(node->fst()), node->fst(), 0);
	const AST* const rst = simplify_list_elem(cenv, node->rst(), type);
	assert(node->fst());
	assert(rst);
	List cons(node->loc, cenv.tenv.List, fst, rst, 0);
	cenv.setType(fst, tup(Cursor(), cenv.penv.sym("Expr"), 0));
	cenv.setType(cons, type);
	return cons;
}

static const AST*
simplify_quote(CEnv& cenv, const ATuple* call) throw()
{
	switch (call->frst()->tag()) {
	case T_SYMBOL:
		// Symbols remain quoted so they are not interpreted as variables
		return call;
	case T_TUPLE: {
		// Lists are transformed into nested conses
		const ATuple* const list = call->frst()->as_tuple();
		return simplify_list_elem(cenv, list,
		                          tup(Cursor(), cenv.tenv.List, cenv.penv.sym("Expr"), 0));
	}
	default:
		// Other literals (e.g. numbers, strings) are self-evaluating, so the
		// quote can be removed, e.g. (quote 3) => 3
		return call->frst();
	}
}

const AST*
resp_simplify(CEnv& cenv, const AST* ast) throw()
{
	const ATuple* const list = ast->to_tuple();
	if (!list)
		return ast;
	
	const ASymbol* const sym  = list->fst()->to_symbol();
	const std::string    form = sym ? sym->sym() : "";

	if (form == "match")
		return simplify_match(cenv, list);
	else if (form == "if")
		return simplify_if(cenv, list);
	else if (form == "let")
		return simplify_let(cenv, list);
	else if (form == "quote")
		return simplify_quote(cenv, list);
	else
		return simplify_list(cenv, list);
}