/* 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 REPL and eval
 */

#include <cerrno>
#include <cstring>
#include <limits>
#include <list>
#include <string>

#include "resp.hpp"

using namespace std;

static bool
readParseType(CEnv& cenv, Cursor& cursor, istream& is, const AST*& exp, const AST*& ast)
{
	try {
		exp = cenv.penv.parse(cursor, is);
	} catch (Error e) {
		is.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // Skip REPL junk
		throw e;
	}

	if (!exp || (exp->to_tuple() && exp->to_tuple()->empty()))
		return false;

	ast = cenv.penv.expand(exp); // Parse input

	Constraints c(cenv.tsubst);
	resp_constrain(cenv.tenv, c, ast); // Constrain types

	//cout << "(CONSTRAINTS " << endl << c << ")" << endl;

	const Subst subst = unify(c); // Solve type constraints
	for (Subst::const_iterator i = subst.begin(); i != subst.end(); ++i) {
		if (!cenv.tsubst.contains(i->first)) { // Substitution's LHS is a new variable
			cenv.tsubst.push_back(*i); // Add substitution to global type substitution
			Object::pool.addRoot(i->first);
			Object::pool.addRoot(i->second);
		}
	}

	//cout << "(SUBST " << endl << subst << ")" << endl;
	//cout << "(TSUBST " << endl << cenv.tsubst << ")" << endl;
	cenv.tsubst = Subst::compose(cenv.tsubst, subst); // FIXME: breaks polymorphism + repl

	Object::pool.addRoot(ast); // Make parsed expression a GC root so it is not deleted

	return true;
}

static void
callPrintCollect(CEnv& cenv, CFunc f, const AST* result, const AST* resultT, bool execute)
{
	if (execute)
		cenv.out << cenv.engine()->call(cenv, f, resultT);

	// Print type (if applicable)
	const std::string type_str = resultT->str();
	if (type_str != "Nothing")
		cenv.out << " : " << type_str << endl;

	Object::pool.collect(Object::pool.roots());
}

/// Compile and evaluate code from @a is
int
eval(CEnv& cenv, Cursor& cursor, istream& is, bool execute)
{
	const AST* exp = NULL;
	const AST* ast = NULL;

	typedef list<const AST*> Parsed;
	Parsed parsed;

	try {
		while (readParseType(cenv, cursor, is, exp, ast))
			parsed.push_back(ast);

		if (cenv.args.find("-T") != cenv.args.end()) {
			for (Parsed::const_iterator i = parsed.begin(); i != parsed.end(); ++i)
				pprint(cout, *i, &cenv, (cenv.args.find("-a") != cenv.args.end()));
			return 0;
		}

		// Simplify all expressions
		Code simplified;
		for (Parsed::const_iterator i = parsed.begin(); i != parsed.end(); ++i) {
			const AST* l = resp_simplify(cenv, *i);
			if (l)
				simplified.push_back(l);
		}

		if (cenv.args.find("-R") != cenv.args.end()) {
			for (Code::const_iterator i = simplified.begin(); i != simplified.end(); ++i)
				pprint(cout, *i, &cenv, (cenv.args.find("-a") != cenv.args.end()));
			return 0;
		}

		CVal  val = NULL;
		CFunc f   = NULL;

		// Lift all expressions
		Code lifted;
		for (Parsed::const_iterator i = simplified.begin(); i != simplified.end(); ++i) {
			const AST* l = resp_lift(cenv, lifted, *i);
			if (l)
				lifted.push_back(l);
		}

		if (cenv.args.find("-L") != cenv.args.end()) {
			for (Code::const_iterator i = lifted.begin(); i != lifted.end(); ++i)
				pprint(cout, *i, &cenv, (cenv.args.find("-a") != cenv.args.end()));
			return 0;
		}

		// Compile top-level (lifted) functions
		Code exprs;
		for (Code::const_iterator i = lifted.begin(); i != lifted.end(); ++i) {
			const ATuple* call = (*i)->to_tuple();
			if (call && (   (is_form(call, "def-type"))
			             || (is_form(call, "def") && is_form(call->frrst(), "fn")))) {
				val = resp_compile(cenv, call);
			} else {
				const ATuple* tup = (*i)->to_tuple();
				if (!tup || !tup->empty()) {
					exprs.push_back(*i);
				}
			}
		}

		if (!exprs.empty()) {
			const AST*    type = cenv.type(exprs.back());
			const ATuple* fnT  = tup(cursor, cenv.tenv.Fn, new ATuple(cursor), type, 0);

			// Create function for program containing all expressions except definitions
			f = cenv.engine()->startFn(cenv, "main", new ATuple(cursor), fnT);
			for (Code::const_iterator i = exprs.begin(); i != exprs.end(); ++i)
				val = resp_compile(cenv, *i);
			cenv.engine()->finishFn(cenv, f, val, type);

			// Call and print result
			callPrintCollect(cenv, f, ast, type, execute);
		}

		if (cenv.args.find("-S") != cenv.args.end()) {
			cenv.engine()->writeModule(cenv, cenv.out);
			return 0;
		}

	} catch (Error& e) {
		cenv.err << e.what() << endl;
		return 1;
	}
	return 0;
}

/// Read Eval Print Loop
int
repl(CEnv& cenv)
{
	cenv.repl = true;

	const AST*   exp        = NULL;
	const AST*   ast        = NULL;
	const string replFnName = cenv.penv.gensymstr("_repl");
	while (1) {
		cenv.out << "() ";
		cenv.out.flush();
		Cursor cursor("(stdin)");

		try {
			if (!readParseType(cenv, cursor, std::cin, exp, ast))
				break;

			Code lifted;
			ast = resp_lift(cenv, lifted, ast);
			const AST*    type = cenv.type(ast);
			const ATuple* fnT  = tup(cursor, cenv.tenv.Fn, new ATuple(cursor), type, 0);
			CFunc f = NULL;
			try {
				// Create function for this repl loop
				f = cenv.engine()->startFn(cenv, replFnName, new ATuple(cursor), fnT);
				cenv.engine()->finishFn(cenv, f, resp_compile(cenv, ast), type);
				callPrintCollect(cenv, f, ast, type, true);
				if (cenv.args.find("-d") != cenv.args.end())
					cenv.engine()->writeModule(cenv, cenv.out);
			} catch (Error& e) {
				cenv.out << e.msg << endl;
				cenv.engine()->eraseFn(cenv, f);
			}

		} catch (Error& e) {
			cenv.err << e.what() << endl;
		}
	}
	return 0;
}