/* 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 Main program
 */

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

#include "resp.hpp"

using namespace std;

GC Object::pool(8 * 1024 * 1024);

bool
is_form(const AST* ast, const std::string& form)
{
	const ATuple* call = ast->to_tuple();
	if (!call || call->empty())
		return false;

	const ASymbol* const sym = call->fst()->to_symbol();
	if (!sym)
		return false;

	return form == sym->sym();
}

bool
is_primitive(const PEnv& penv, const AST* ast)
{
	const ATuple* call = ast->to_tuple();
	if (!call)
		return false;

	const ASymbol* const sym = call->fst()->to_symbol();
	if (!sym)
		return false;

	return penv.primitives.find(sym->sym()) != penv.primitives.end();
}

int
print_usage(char* name, bool error)
{
	ostream& os = error ? cerr : cout;
	os << "Usage: " << name << " [OPTION]... [FILE]..."  << endl;
	os << "Evaluate and/or compile Resp code"           << endl;
	os << endl;
	os << "  -a               Annotate output with types"                << endl;
	os << "  -b BACKEND       Use backend (llvm or c)"                   << endl;
	os << "  -e EXPRESSION    Evaluate EXPRESSION"                       << endl;
	os << "  -g               Debug (disable optimisation)"              << endl;
	os << "  -h               Display this help and exit"                << endl;
	os << "  -o FILE          Compile output to FILE (don't run)"        << endl;
	os << "  -r               Enter REPL after evaluating files"         << endl;
	os << "  -P               Parse only"                                << endl;
	os << "  -T               Type check only"                           << endl;
	os << "  -R               Reduce to simpler forms only"              << endl;
	os << "  -L               Lambda lift only"                          << endl;
	os << "  -S               Compile to assembly only (do not execute)" << endl;

	return error ? 1 : 0;
}

int
main(int argc, char** argv)
{
	// Read command line arguments
	map<string,string> args;
	typedef list<const char*> Files;
	Files files;
	for (int i = 1; i < argc; ++i) {
		if (!strncmp(argv[i], "-h", 3)) {
			return print_usage(argv[0], false);
		} else if (argv[i][0] != '-') {
			files.push_back(argv[i]);
		} else if (!strncmp(argv[i], "-L", 3)
		           || !strncmp(argv[i], "-P", 3)
		           || !strncmp(argv[i], "-R", 3)
		           || !strncmp(argv[i], "-S", 3)
		           || !strncmp(argv[i], "-T", 3)
		           || !strncmp(argv[i], "-a", 3)
		           || !strncmp(argv[i], "-g", 3)
		           || !strncmp(argv[i], "-r", 3)) {
			args.insert(make_pair(argv[i], ""));
		} else if (i == argc-1 || argv[i+1][0] == '-') {
			return print_usage(argv[0], true);
		} else {
			args.insert(make_pair(argv[i], argv[i+1]));
			++i;
		}
	}

	PEnv penv;
	TEnv tenv(penv);
	initLang(penv, tenv);

	Engine* engine = NULL;

	map<string,string>::const_iterator a = args.find("-b");
	const string backend_name = (a != args.end() ? a->second : "llvm");

	if (backend_name == "llvm")
		engine = resp_new_llvm_engine();
	else if (backend_name == "c")
		engine = resp_new_c_engine();

	if (!engine) {
		std::cerr << "Unable to open backend " << backend_name << std::endl;
		return 1;
	}

	CEnv* cenv = new CEnv(penv, tenv, engine);
	cenv->args = args;

	Object::pool.lock();

	int ret = 0;

	a = args.find("-o");
	const bool batch  = (a != args.end());
	string     output = batch ? a->second : "";

	if (args.find("-P") != args.end()) { // Pretty-print input
		ofstream fs;
		if (output != "")
			fs.open(output.c_str());

		ostream& os = (output == "") ? std::cout : fs;
		for (Files::iterator f = files.begin(); f != files.end(); ++f) {
			ifstream is(*f);
			try {
				while (is.good() && !is.eof()) {
					Cursor loc(*f);
					const AST* exp = cenv->penv.parse(loc, is);
					if (!exp || (exp->to_tuple() && exp->to_tuple()->empty()))
						break;

					const AST* ast = penv.expand(exp);
					pprint(os, ast, cenv, false);
					is.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // Skip newlines
				}
			} catch (Error e) {
			}
			is.close();
		}
		ret = 0;

	} else if (args.find("-e") != args.end()) { // Evaluate argument as expression
		if (files.size() > 0) {
			std::cerr << "Illegal options: both -e and files given" << std::endl;
			delete cenv;
			delete engine;
			return print_usage(argv[0], true);
		}

		std::istringstream is(a->second);
		Cursor cursor("(command line)");
		ret = eval(*cenv, cursor, is, !batch);

	} else if (files.size() == 0) { // Neither -e nor files given, run repl
		ret = repl(*cenv);

	} else { // Evalute (or just type check if -T, or just compile if -S all files
		for (Files::iterator f = files.begin(); f != files.end(); ++f) {
			const string filename = *f;
			if (!batch)
				output = filename + ".out";

			ifstream is(*f);
			if (is.good()) {
				Cursor cursor(filename);
				ret = ret | eval(*cenv, cursor, is, !batch);
			} else {
				cerr << argv[0] << ": " << *f << ": " << strerror(errno) << endl;
				++ret;
			}
			is.close();
		}
	}

	if (args.find("-r") != args.end()) // Run REPL after evaluations
		ret = repl(*cenv);

	delete cenv;
	delete engine;

	return ret;
}