/* Tuplr: A programming language
 * Copyright (C) 2008-2009 David Robillard <dave@drobilla.net>
 *
 * Tuplr 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.
 *
 * Tuplr 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 Tuplr.  If not, see <http://www.gnu.org/licenses/>.
 */

/** @file
 * @brief REPL and eval
 */

#include <cerrno>
#include <cstring>
#include <fstream>
#include "tuplr.hpp"

using namespace std;

static bool
readParseTypeCompile(CEnv& cenv, Cursor& cursor, istream& is, AST** exp, AST** result, AType** resultT)
{
	*exp = readExpression(cursor, is);
	if ((*exp)->to<ATuple*>() && (*exp)->to<ATuple*>()->empty())
		return false;

	*result = cenv.penv.parse(*exp); // Parse input
	Constraints c;
	(*result)->constrain(cenv.tenv, c); // Constrain types

	cenv.tsubst = Subst::compose(cenv.tsubst, unify(c)); // Solve type constraints

	// Add types in type substition as GC roots
	for (Subst::iterator i = cenv.tsubst.begin(); i != cenv.tsubst.end(); ++i) {
		Object::pool.addRoot(i->first);
		Object::pool.addRoot(i->second);
	}

	(*resultT) = cenv.type(*result);
	THROW_IF(!*resultT, cursor, "call to untyped body");

	(*result)->lift(cenv); // Lift functions

	// Add definitions as GC roots
	if ((*result)->to<ADef*>())
		cenv.lock(*result);

	return true;
}

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

	// Print type (if applicable)
	if (resultT->at(0)->to<const ASymbol*>()->cppstr != "Nothing")
		cenv.out << " : " << resultT << endl;

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

	if (cenv.args.find("-d") != cenv.args.end())
		cenv.engine()->writeModule(cenv, cenv.out);
}

/// Compile and evaluate code from @a is
int
eval(CEnv& cenv, const string& name, istream& is, bool execute)
{
	AST*   exp     = NULL;
	AST*   result  = NULL;
	AType* resultT = NULL;
	list< pair<AST*, AST*> > exprs;
	Cursor cursor(name);
	try {
		while (readParseTypeCompile(cenv, cursor, is, &exp, &result, &resultT))
			exprs.push_back(make_pair(exp, result));

		//for (list< pair<SExp, AST*> >::const_iterator i = exprs.begin(); i != exprs.end(); ++i)
		//	pprint(cout, i->second->cps(cenv.tenv, cenv.penv.sym("cont")));

		CValue    val = NULL;
		CFunction f   = NULL;
		if (resultT->concrete()) {
			// Create function for top-level of program
			f = cenv.engine()->startFunction(cenv, "main", resultT, ATuple(cursor));

			// Compile all expressions into it
			for (list< pair<AST*, AST*> >::const_iterator i = exprs.begin(); i != exprs.end(); ++i)
				val = cenv.compile(i->second);

			// Finish compilation
			cenv.engine()->finishFunction(cenv, f, resultT, val);
		}

		// Call and print result
		callPrintCollect(cenv, f, result, resultT, execute);

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

/// Read Eval Print Loop
int
repl(CEnv& cenv)
{
	AST*         exp        = NULL;
	AST*         result     = NULL;
	AType*       resultT    = NULL;
	const string replFnName = cenv.penv.gensymstr("_repl");
	while (1) {
		cenv.out << "() ";
		cenv.out.flush();
		Cursor cursor("(stdin)");

		try {
			Subst oldSubst = cenv.tsubst;
			if (!readParseTypeCompile(cenv, cursor, std::cin, &exp, &result, &resultT))
				break;

			CFunction f = NULL;
			try {
				// Create function for this repl loop
				f = cenv.engine()->startFunction(cenv, replFnName, resultT, ATuple(cursor));
				cenv.engine()->finishFunction(cenv, f, resultT, cenv.compile(result));
				callPrintCollect(cenv, f, result, resultT, true);
			} catch (Error& e) {
				cenv.out << e.msg << endl;
				cenv.engine()->eraseFunction(cenv, f);
			}

			cenv.tsubst = oldSubst;

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