/* 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")) {
		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()
{
	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_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);
	CVal val = resp_compile(cenv, body);
	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) {
		cenv.engine()->compileType(cenv, name->sym(), def->frrst());
		cenv.tenv.def(name, def->frrst());
	} else {
		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_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());

	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() && (cenv.type(*e) != cenv.type(*t))) {

			args.push_back(cenv.engine()->compileCast(cenv, arg, *t));
		} else {
			args.push_back(arg);
		}
	}

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

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();

	CFunc func = cenv.engine()->startFn(cenv, name->sym(), args, type);
	cenv.def(name, NULL, type, func);

	cenv.engine()->pushFnArgs(cenv, args, type, func);

	return NULL;
}

static CVal
compile_fn_end(CEnv& cenv, const ATuple* call) throw()
{
	const AST* retT = cenv.type(call->frst())->as_tuple()->frrst();
	cenv.engine()->finishFn(cenv, resp_compile(cenv, call->frrst()), 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 == "cast")
			return cenv.engine()->compileCast(cenv,
			                                  resp_compile(cenv, call->frst()),
			                                  cenv.type(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 == "quote")
			return compile_quote(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
			return compile_call(cenv, call);
	}
	}

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