/* 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 (*sym == *cenv.penv.sym("__unreachable"))
		return NULL;

	if (cenv.repl && cenv.vals.topLevel(sym) && !is_form(cenv.type(sym), "Fn")) {
		CVal val = cenv.globals[sym->sym()];
		return cenv.engine()->compileGlobalGet(cenv, sym->sym(), val);
	} else {
		return *cenv.vals.ref(sym);
	}
}

static CVal
compile_literal_symbol(CEnv& cenv, const ASymbol* sym) throw()
{
	CEnv::CSyms::iterator i = cenv.cSyms.find(sym->sym());
	if (i != cenv.cSyms.end()) {
		return i->second;
	} else {
		CVal compiled = cenv.engine()->compileString(cenv, sym->sym());
		cenv.cSyms.insert(make_pair(sym->sym(), compiled));
		return compiled;
	}
}

static CVal
compile_cons(CEnv& cenv, const ATuple* cons) throw()
{
	if (is_form(cons, "Symbol")) {
		return compile_literal_symbol(cenv, cons->frst()->as_symbol());
	}

	const ASymbol* sym  = cons->fst()->as_symbol();
	ATuple*        type = new ATuple(sym, NULL, Cursor());
	List           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));
	}

	std::string name = sym->str();
	if (name == "Closure") {
		name = cenv.type(cons, Subst(), false)->as_symbol()->str();
	}
	return cenv.engine()->compileCons(
		cenv, name.c_str(), type, compile_literal_symbol(cenv, sym), 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);
	CVal val = resp_compile(cenv, body);
	if (cenv.repl && cenv.vals.topLevel(sym) && !is_form(cenv.type(sym), "Fn")) {
		CVal global = cenv.engine()->compileGlobalSet(
			cenv, sym->sym(), val, cenv.type(body));
		cenv.globals.insert(make_pair(sym->sym(), global));
		cenv.def(sym, body, cenv.type(body), global);
	} else {
		cenv.def(sym, body, cenv.type(body), val);
	}
	return NULL;
}

static CVal
compile_def_type(CEnv& cenv, const ATuple* def) throw()
{
	const ASymbol* name = def->frst()->to_symbol();
	if (name) {
		if (def->rrst()) {  // Definition
			cenv.engine()->compileType(cenv, name->str(), def->frrst());
			cenv.tenv.def(name, def->frrst());
		} else {  // Forward declaration
			cenv.engine()->compileType(cenv, name->str(), NULL);
		}
	} else {
		name = def->frst()->as_tuple()->fst()->as_symbol();
		cenv.engine()->compileType(cenv, name->str(), 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_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->frst())->as_tuple()->prot();
	CFunc         f     = resp_compile(cenv, call->frst());

	vector<CVal> args;
	ATuple::const_iterator p = protT->iter_at(0);
	for (ATuple::const_iterator a = call->iter_at(2); a != call->end(); ++a, ++p) {
		CVal arg = resp_compile(cenv, *a);
		if (cenv.type(*a) != *p && cenv.type(*a) != cenv.resolveType(*p)) {
			args.push_back(cenv.engine()->compileCast(cenv, arg, *p));
		} else {
			args.push_back(arg);
		}
	}

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

static CVal
compile_prot(CEnv& cenv, const ATuple* call) throw()
{
	const ASymbol* name = call->list_ref(1)->as_symbol();
	const ATuple*  type = cenv.type(name)->as_tuple();
	const ATuple*  args = call->frrst()->as_tuple()->frst()->as_tuple();

	cenv.engine()->compileProt(cenv, name->sym(), args, type);

	return NULL;
}

static CVal
compile_fn_start(CEnv& cenv, const ATuple* call) throw()
{
	const ASymbol* name = call->list_ref(1)->as_symbol();
	const ATuple*  type = cenv.type(name)->as_tuple();
	const ATuple*  args = call->rst()->rst();

	cenv.engine()->startFn(cenv, name->sym(), args, type);

	return NULL;
}

static CVal
compile_fn_end(CEnv& cenv, const ATuple* call) throw()
{
	const AST* ret  = (call->list_len() > 2) ? call->frrst() : NULL;
	const AST* retT = (ret)
		? cenv.type(call->frst())->as_tuple()->frrst()
		: cenv.penv.sym("Nothing");

	cenv.engine()->finishFn(cenv, resp_compile(cenv, ret), retT);

	cenv.pop();
	return NULL;
}

CVal
resp_compile(CEnv& cenv, const AST* ast) throw()
{
	if (!ast)
		return NULL;

	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 == "cons" || isupper(form[0]))
			return compile_cons(cenv, call);
		else if (form == ".")
			return compile_dot(cenv, call);
		else if (form == "define")
			return compile_def(cenv, call);
		else if (form == "def-type")
			return compile_def_type(cenv, call);
		else if (form == "quote")
			return compile_quote(cenv, call);
		else if (form == "prot")
			return compile_prot(cenv, call);
		else if (form == "fn-start")
			return compile_fn_start(cenv, call);
		else if (form == "fn-end")
			return compile_fn_end(cenv, call);
		else if (form == "if-start")
			return cenv.engine()->compileIfStart(cenv, call->frrst(), cenv.type(call));
		else if (form == "if-then")
			return cenv.engine()->compileIfThen(cenv, resp_compile(cenv, call->frrst()));
		else if (form == "if-else")
			return cenv.engine()->compileIfElse(cenv, resp_compile(cenv, call->frrst()));
		else if (form == "if-end")
			return cenv.engine()->compileIfEnd(cenv);
		else if (form == "call")
			return compile_call(cenv, call);
	}
	}

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