/* 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 Compile all code (compilation pass 2)
 */

#include <string>
#include <vector>

#include "resp.hpp"

using namespace std;

static CVal
compile_symbol(CEnv& cenv, const ASymbol* sym) throw()
{
	if (cenv.repl && cenv.vals.topLevel(sym) && !is_form(cenv.type(sym), "Fn")) {
		return cenv.engine()->compileGlobalGet(cenv, sym->sym(), *cenv.vals.ref(sym));
	} else {
		return *cenv.vals.ref(sym);
	}
}

static CVal
compile_literal_symbol(CEnv& cenv, const ASymbol* sym) throw()
{
	CVal* existing = cenv.vals.ref(sym);
	if (existing) {
		return *existing;
	} else {
		CVal compiled = cenv.engine()->compileString(cenv, sym->sym());
		cenv.vals.def(sym, compiled);
		return compiled;
	}

}

static CVal
compile_cast(CEnv& cenv, const ATuple* cast) throw()
{
	return cenv.engine()->compileCast(cenv,
	                                  resp_compile(cenv, cast->frst()),
	                                  cenv.type(cast));
}

static CVal
compile_type(CEnv& cenv, const AST* type) throw()
{
	return compile_literal_symbol(cenv, type->as_tuple()->fst()->as_symbol());
}

static CVal
compile_cons(CEnv& cenv, const ATuple* cons) throw()
{
	ATuple*      type = new ATuple(cons->fst()->as_symbol(), NULL, Cursor());
	List         tlist(type);
	vector<CVal> fields;
	for (ATuple::const_iterator i = cons->iter_at(1); i != cons->end(); ++i) {
		assert(cenv.type(*i));
		tlist.push_back(cenv.type(*i));
		fields.push_back(resp_compile(cenv, *i));
	}
	return cenv.engine()->compileCons(cenv, type, compile_type(cenv, type), fields);
}

static CVal
compile_dot(CEnv& cenv, const ATuple* dot) throw()
{
	ATuple::const_iterator   i     = dot->begin();
	const AST*               tup   = *++i;
	const ALiteral<int32_t>* index = (ALiteral<int32_t>*)(*++i);
	assert(index->tag() == T_INT32);
	CVal tupVal = resp_compile(cenv, tup);
	assert((unsigned)index->val < cenv.type(dot->frst())->as_tuple()->list_len());
	return cenv.engine()->compileDot(cenv, tupVal, index->val);
}

static CVal
compile_def(CEnv& cenv, const ATuple* def) throw()
{
	const ASymbol* const sym  = def->list_ref(1)->as_symbol();
	const AST*     const body = def->list_ref(2);
	cenv.def(sym, body, cenv.type(body), NULL); // define stub first for recursion
	CVal val = resp_compile(cenv, body);
	if (cenv.repl && cenv.vals.size() == 1 && !is_form(cenv.type(body), "Fn")) {
		val = cenv.engine()->compileGlobalSet(
			cenv, sym->str(), val, cenv.type(body));
		cenv.lock(def);
	}
	cenv.vals.def(sym, val);
	return NULL;
}

static CVal
compile_def_type(CEnv& cenv, const ATuple* def) throw()
{
	const ASymbol* name = def->frst()->as_tuple()->fst()->as_symbol();
	cenv.engine()->compileType(cenv, name->sym(), def->frst());
	for (ATuple::const_iterator i = def->iter_at(2); i != def->end(); ++i) {
		const ATuple*  exp = (*i)->as_tuple();
		const ASymbol* tag = (*exp->begin())->as_symbol();
		cenv.engine()->compileType(cenv, tag->sym(), exp);
	}
	return NULL;
}

static CVal
compile_do(CEnv& cenv, const ATuple* ado) throw()
{
	CVal retVal = NULL;
	for (ATuple::const_iterator i = ado->iter_at(1); i != ado->end(); ++i)
		retVal = resp_compile(cenv, *i);

	return retVal;
}

static CVal
compile_fn(CEnv& cenv, const ATuple* fn) throw()
{
	assert(!cenv.currentFn);

	const AST* const type = cenv.type(fn);
	CFunc f = cenv.findImpl(fn, type);
	if (f)
		return f;

	// Write function declaration and push stack frame
	f = cenv.engine()->startFn(cenv, cenv.name(fn), fn->prot(), type->as_tuple());
	cenv.engine()->pushFnArgs(cenv, fn->prot(), type->as_tuple(), f);
	cenv.currentFn = f;

	// Write function body
	CVal retVal = NULL;
	for (ATuple::const_iterator i = fn->iter_at(2); i != fn->end(); ++i)
		retVal = resp_compile(cenv, *i);

	// Write function conclusion and pop stack frame
	cenv.engine()->finishFn(cenv, f, retVal, type->as_tuple()->frrst());
	cenv.pop();
	cenv.currentFn = NULL;

	cenv.vals.def(cenv.penv.sym(cenv.name(fn)), f);
	cenv.addImpl(fn, f);
	return f;
}

static CVal
compile_if(CEnv& cenv, const ATuple* aif) throw()
{
	const AST* aelse = NULL;
	if (*aif->list_last() != *cenv.penv.sym("__unreachable"))
		aelse = aif->list_ref(3);

	return cenv.engine()->compileIf(cenv, aif->list_ref(1), aif->list_ref(2), aelse);
}

static CVal
compile_quote(CEnv& cenv, const ATuple* quote) throw()
{
	switch (quote->list_ref(1)->tag()) {
	case T_SYMBOL:
		return compile_literal_symbol(cenv, quote->list_ref(1)->as_symbol());
	default:
		assert(false);
		return NULL;
	}	
}

static CVal
compile_call(CEnv& cenv, const ATuple* call) throw()
{
	const ATuple* protT = cenv.type(call->fst())->as_tuple()->prot();
	CFunc f = resp_compile(cenv, call->fst());

	if (!f)
		f = cenv.currentFn; // Recursive call (callee defined as a stub)

	vector<CVal> args;
	ATuple::const_iterator t = protT->iter_at(0);
	for (ATuple::const_iterator e = call->iter_at(1); e != call->end(); ++e, ++t) {
		CVal arg = resp_compile(cenv, *e);
		if ((*e)->to_symbol()) {
			if (cenv.type(*e) != cenv.type(*t)) {
				args.push_back(cenv.engine()->compileCast(cenv, arg, *t));
				continue;
			}
		}
		args.push_back(arg);
	}

	return cenv.engine()->compileCall(cenv, f, cenv.type(call->fst())->as_tuple(), args);
}

CVal
resp_compile(CEnv& cenv, const AST* ast) throw()
{
	switch (ast->tag()) {
	case T_UNKNOWN:
		return NULL;
	case T_BOOL:
	case T_FLOAT:
	case T_INT32:
	case T_TVAR:
		return cenv.engine()->compileLiteral(cenv, ast);
	case T_STRING:
		return cenv.engine()->compileString(cenv, ((AString*)ast)->cppstr.c_str());
	case T_SYMBOL:
		return compile_symbol(cenv, ast->as_symbol());
	case T_LITSYM:
		return compile_literal_symbol(cenv, (ASymbol*)ast);
	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() : "";
		if (is_primitive(cenv.penv, call))
			return cenv.engine()->compilePrimitive(cenv, ast->as_tuple());
		else if (form == "cast")
			return compile_cast(cenv, call);
		else if (form == "cons" || isupper(form[0]))
			return compile_cons(cenv, call);
		else if (form == ".")
			return compile_dot(cenv, call);
		else if (form == "def")
			return compile_def(cenv, call);
		else if (form == "def-type")
			return compile_def_type(cenv, call);
		else if (form == "do")
			return compile_do(cenv, call);
		else if (form == "fn")
			return compile_fn(cenv, call);
		else if (form == "if")
			return compile_if(cenv, call);
		else if (form == "quote")
			return compile_quote(cenv, call);
		else
			return compile_call(cenv, call);
	}
	}

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