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

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

using namespace std;

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

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

	Constraints c(cenv.tsubst);
	ast->constrain(cenv.tenv, c); // Constrain types

	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)) {
			//cout << "New variable " << i->first << " = " << i->second << endl;
			cenv.tsubst.push_back(*i);
		}
	}

	//cout << "**** SUBST\n" << subst << "********" << endl;
	//cout << "**** CENV.SUBST\n" << cenv.tsubst << "********" << endl;
	//cenv.tsubst = Subst::compose(cenv.tsubst, subst);

	// 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);
	}

	return true;
}

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

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

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

/// Compile and evaluate code from @a is
int
eval(CEnv& cenv, const string& name, istream& is, bool execute)
{
	AST*         exp  = NULL;
	AST*         ast  = NULL;
	list<AST*> parsed;
	Cursor cursor(name);
	try {
		while (readParseType(cenv, cursor, is, exp, ast))
			parsed.push_back(ast);

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

		CVal  val = NULL;
		CFunc f   = NULL;

		/*
		// De-poly all expressions
		Code concrete;
		for (list<AST*>::iterator i = parsed.begin(); i != parsed.end(); ++i) {
			AST* c = (*i)->depoly(cenv, concrete);
			if (c)
				concrete.push_back(c);
		}

		if (cenv.args.find("-d") != cenv.args.end()) {
			cout << endl << ";;;; CONCRETE {" << endl << endl;
			for (Code::iterator i = concrete.begin(); i != concrete.end(); ++i) {
				cout << *i << endl;
				ADef* def = (*i)->to<ADef*>();
				if (def)
					std::cout << " :: " << cenv.type(def->body()) << std::endl;
				cout << endl;
			}
			cout << ";;;; } CONCRETE" << endl << endl;
		}
		*/

		// Lift all expressions
		Code lifted;
		for (list<AST*>::iterator i = parsed.begin(); i != parsed.end(); ++i) {
			AST* l = (*i)->lift(cenv, lifted);
			if (l)
				lifted.push_back(l);
		}

		if (cenv.args.find("-d") != cenv.args.end()) {
			cout << endl << ";;;; LIFTED {" << endl << endl;
			for (Code::iterator i = lifted.begin(); i != lifted.end(); ++i) {
				cout << *i << endl;
				ADef* def = (*i)->to<ADef*>();
				if (def)
					std::cout << " :: " << cenv.type(def->body()) << std::endl;
				cout << endl;
			}
			cout << ";;;; } LIFTED" << endl << endl;
		}

		// Compile top-level (lifted) functions
		Code exprs;
		for (Code::iterator i = lifted.begin(); i != lifted.end(); ++i) {
			ADef* def = (*i)->to<ADef*>();
			if (def && (*(def->begin() + 2))->to<AFn*>()) {
				val = def->compile(cenv);
			} else {
				exprs.push_back(*i);
			}
		}

		const AType* type = cenv.type(exprs.back());

		// Create function for top-level of program
		f = cenv.engine()->startFunction(cenv, "main", type, ATuple(cursor));

		// Compile expressions (other than function definitions) into it
		for (list<AST*>::const_iterator i = exprs.begin(); i != exprs.end(); ++i)
			val = (*i)->compile(cenv);

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

		// Call and print ast
		if (cenv.args.find("-S") == cenv.args.end())
			callPrintCollect(cenv, f, ast, type, 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*         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 = ast->lift(cenv, lifted);
			const AType* type = cenv.type(ast);
			CFunc f = NULL;
			try {
				// Create function for this repl loop
				f = cenv.engine()->startFunction(cenv, replFnName, type, ATuple(cursor));
				cenv.engine()->finishFunction(cenv, f, ast->compile(cenv));
				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()->eraseFunction(cenv, f);
			}

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