/* Resp: A programming language
 * 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 Compile all code (compilation pass 2)
 */

#include "resp.hpp"

using namespace std;

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

static CVal
compile_fn(CEnv& cenv, const ATuple* fn) throw()
{
	assert(!cenv.currentFn);
	
	const AType* 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);
	cenv.engine()->pushFnArgs(cenv, fn->prot(), type, 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);
	cenv.pop(); 
	cenv.currentFn = NULL;
	
	cenv.vals.def(cenv.penv.sym(cenv.name(fn)), f);
	cenv.addImpl(fn, f);
	return f;
}

static CVal
compile_type(CEnv& cenv, const AType* type) throw()
{
	const ASymbol* sym = type->head()->as_symbol();
	CVal* existing = cenv.vals.ref(sym);
	if (existing) {
		return *existing;
	} else {
		CVal compiled = cenv.engine()->compileString(
			cenv, (string("__T_") + type->head()->str()).c_str());
		cenv.vals.def(sym, compiled);
		return compiled;
	}
}

static CVal
compile_call(CEnv& cenv, const ATuple* call) throw()
{
	CFunc f = resp_compile(cenv, call->head());

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

	vector<CVal> args;
	for (ATuple::const_iterator e = call->iter_at(1); e != call->end(); ++e)
		args.push_back(resp_compile(cenv, *e));

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

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.vals.size() == 1 && cenv.type(body)->head()->str() != "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_cons(CEnv& cenv, const ATuple* cons) throw()
{
	AType*       type = new AType(cons->head()->as_symbol(), NULL, Cursor());
	TList        tlist(type);
	vector<CVal> fields;
	for (ATuple::const_iterator i = cons->iter_at(1); i != cons->end(); ++i) {
		tlist.push_back(cenv.type(*i));
		fields.push_back(resp_compile(cenv, *i));
	}
	return cenv.engine()->compileCons(cenv, type, resp_compile(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);
	return cenv.engine()->compileDot(cenv, tupVal, index->val + 1); // + 1 to skip RTTI
}

static CVal
compile_if(CEnv& cenv, const ATuple* aif) throw()
{
	IfState state = cenv.engine()->compileIfStart(cenv);
	for (ATuple::const_iterator i = aif->iter_at(1); ; ++i) {
		ATuple::const_iterator next = i;
		if (++next == aif->end())
			break;

		cenv.engine()->compileIfBranch(cenv, state, resp_compile(cenv, *i), *next);

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

	CVal elseV = resp_compile(cenv, aif->list_last());
	return cenv.engine()->compileIfEnd(cenv, state, elseV, cenv.type(aif));
}

static CVal
compile_match(CEnv& cenv, const ATuple* match) throw()
{
	IfState     state   = cenv.engine()->compileIfStart(cenv);
	CVal        matchee = resp_compile(cenv, match->list_ref(1));
	CVal        rtti    = cenv.engine()->compileDot(cenv, matchee, 0);
	
	size_t idx = 1;
	for (ATuple::const_iterator i = match->iter_at(2); i != match->end(); ++idx) {
		const AST*     pat  = *i++;
		const AST*     body = *i++;
		const ASymbol* sym  = pat->as_tuple()->head()->as_symbol();

		CVal condV = cenv.engine()->compileIsA(cenv, rtti, sym);
		
		cenv.engine()->compileIfBranch(cenv, state, condV, body);
	}

	const AType* type  = cenv.type(match);
	return cenv.engine()->compileIfEnd(cenv, state, NULL, type);
}

CVal
resp_compile(CEnv& cenv, const AST* ast) throw()
{
	switch (ast->tag()) {
	case T_UNKNOWN:
		return NULL;
	case T_TYPE:
		return compile_type(cenv, ast->as_type());
	case T_BOOL:
	case T_FLOAT:
	case T_INT32:
		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_TUPLE:
	{
		const ATuple* const  call = ast->as_tuple();
		const ASymbol* const sym  = call->head()->to_symbol();
		const std::string    form = sym ? sym->cppstr : "";
		if (is_primitive(cenv.penv, call))
			return cenv.engine()->compilePrimitive(cenv, ast->as_tuple());
		else if (form == "fn")
			return compile_fn(cenv, call);
		else if (form == "def")
			return compile_def(cenv, call);
		else if (form == "if")
			return compile_if(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 == "match")
			return compile_match(cenv, call);
		else if (form == "def-type")
			return NULL;
		else
			return compile_call(cenv, call);
	}
	}

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