/* Resp: A programming language
 * Copyright (C) 2008-2011 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 Flatten code (raise all nested expressions)
 */

#include <string>
#include <vector>

#include "resp.hpp"

using namespace std;

static const AST*
flatten_def(CEnv& cenv, Code& code, const ATuple* def) throw()
{
	const ASymbol* const sym  = def->list_ref(1)->as_symbol();
	const AST*     const body = def->list_ref(2);

	if (!is_form(body, "fn")) {
		code.push_back(def);
		return NULL;
	}

	const ATuple* fn   = (*def->iter_at(2))->as_tuple();
	const ATuple* prot = fn->frst()->as_tuple();
	List pre(Cursor(), cenv.penv.sym("fn-start"), sym, 0);
	for (ATuple::const_iterator i = prot->begin(); i != prot->end(); ++i)
		pre.push_back(*i);

	code.push_back(pre);

	cenv.def(sym, body, cenv.type(body), NULL); // define stub first for recursion

	const AST* retVal = NULL;
	const AST* retT   = NULL;
	for (ATuple::const_iterator i = fn->iter_at(2); i != fn->end(); ++i) {
		retVal = resp_flatten(cenv, code, *i);
		retT   = cenv.type(*i);
	}

	cenv.setTypeSameAs(retVal, retT);
	List post(Cursor(), cenv.penv.sym("fn-end"), sym, retVal, 0);
	code.push_back(post);

	return NULL;
}

static const AST*
flatten_def_type(CEnv& cenv, Code& code, const ATuple* def) throw()
{
	const ASymbol* name = def->frst()->to_symbol();
	if (name) {
		cenv.tenv.def(name, def->frrst());
	} else {
		name = def->frst()->as_tuple()->fst()->as_symbol();
		cenv.tenv.def(name, def->frrst());
	}
	code.push_back(def);
	return NULL;
}

static const AST*
flatten_do(CEnv& cenv, Code& code, const ATuple* ado) throw()
{
	const AST* ret = NULL;
	for (ATuple::const_iterator i = ado->iter_at(1); i != ado->end(); ++i) {
		ret = resp_flatten(cenv, code, *i);
		if (ret)
			code.push_back(ret);
	}
	const ASymbol* sym = cenv.penv.gensym("doval");
	List def(Cursor(), cenv.penv.sym("def"), sym, ret, NULL);
	code.push_back(def);
	cenv.setTypeSameAs(sym, ado);
	return sym;
}

static const AST*
flatten_if(CEnv& cenv, Code& code, const ATuple* aif) throw()
{
	const ASymbol* cond = NULL;
	if (aif->frst()->to_symbol()) {
		cond = aif->frst()->as_symbol();
	} else {
		cond = cenv.penv.gensym("ifcond");
		List def(Cursor(), cenv.penv.sym("def"), cond,
		         resp_flatten(cenv, code, aif->frst()), 0);
		cenv.setTypeSameAs(cond, aif->frst());
		code.push_back(def);
	}

	const ASymbol* if_lab = cenv.penv.gensym("if");
	const ASymbol* result = cenv.penv.gensym("ifval");

	List pre(Cursor(), cenv.penv.sym("if-start"),
	         if_lab, cond, /*then_lab, else_lab,*/ NULL);
	code.push_back(pre);
	cenv.setTypeSameAs(pre, aif);

	const AST* athen = *aif->iter_at(2);
	const AST* aelse = *aif->iter_at(3);

	const AST* then_val = resp_flatten(cenv, code, athen);
	cenv.setTypeSameAs(then_val, athen);
	List then_goto(Cursor(), cenv.penv.sym("if-then"), if_lab, then_val, NULL);
	code.push_back(then_goto);

	const AST* else_val = resp_flatten(cenv, code, aelse);
	cenv.setTypeSameAs(else_val, aelse);
	List else_goto(Cursor(), cenv.penv.sym("if-else"), if_lab, else_val, NULL);
	code.push_back(else_goto);

	List end(Cursor(), cenv.penv.sym("if-end"), if_lab, NULL);
	List def(Cursor(), cenv.penv.sym("def"), result, end, NULL);
	code.push_back(def);

	cenv.setTypeSameAs(end, aif);
	cenv.setTypeSameAs(result, aif);
	return result;
}

static const AST*
flatten_call(CEnv& cenv, Code& code, const ATuple* call) throw()
{
	List copy;
	for (ATuple::const_iterator i = call->begin(); i != call->end(); ++i) {
		const AST* flat_i = resp_flatten(cenv, code, *i);
		const AST* arg    = NULL;
		if (!flat_i->to_tuple()) {
			arg = flat_i;
		} else {
			const ASymbol* sym = cenv.penv.gensym();
			List def(Cursor(), cenv.penv.sym("def"), sym, flat_i, NULL);
			code.push_back(def);
			arg = sym;
			cenv.setTypeSameAs(sym, *i);
		}
		cenv.setTypeSameAs(flat_i, *i);
		copy.push_back(arg);
	}
	const ASymbol* sym = cenv.penv.gensym();
	List def(Cursor(), cenv.penv.sym("def"), sym, copy, NULL);
	code.push_back(def);

	cenv.setTypeSameAs(copy, call);
	cenv.setTypeSameAs(sym, call);
	return sym;
}

const AST*
resp_flatten(CEnv& cenv, Code& code, const AST* ast) throw()
{
	switch (ast->tag()) {
	case T_TUPLE: {
		const ATuple* const  call = ast->as_tuple();
		const ASymbol* const sym  = call->fst()->to_symbol();
		const std::string    form = sym ? sym->sym() : "";
		assert(form != "fn");
		if (form == "cast"
		    || form == "quote")
			return ast;
		else if (form == "def")
			return flatten_def(cenv, code, call);
		else if (form == "def-type")
			return flatten_def_type(cenv, code, call);
		else if (form == "do")
			return flatten_do(cenv, code, call);
		else if (form == "if")
			return flatten_if(cenv, code, call);
		else
			return flatten_call(cenv, code, call);
	}
	default:
		return ast;
	}

	cenv.err << "Attempt to compile unknown type: " << ast << endl;
	assert(false);
	return NULL;
}