/* $Id$ 
 *
 * FAUhdlc: main class to setup and execute compilation.
 *
 * Copyright (C) 2007-2010 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <cstring>
#include <fstream>
#include <iostream>
#include <cassert>
extern "C" {
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <getopt.h>
#undef _GNU_SOURCE
#else /* _GNU_SOURCE not externally defined */
#include <getopt.h>
#endif /* _GNU_SOURCE externally defined */
};
#include "compiler/FAUhdlc.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include "frontend/newparser/ParserDriver.hpp"
#include "util/MiscUtil.hpp"
#include "util/GarbageCollect.hpp"
#include "frontend/visitor/DotVisitor.hpp"
#include "frontend/misc/BuiltinSymbolTable.hpp"
#include "frontend/visitor/ResolveTypes.hpp"
#include "frontend/visitor/SetPathName.hpp"
#include "frontend/visitor/GatherImplicits.hpp"
#include "frontend/visitor/GenCode.hpp"
#include "frontend/visitor/ConstantPropagation.hpp"
#include "frontend/visitor/CheckLoops.hpp"
#include "frontend/visitor/CheckAccessMode.hpp"
#include "frontend/visitor/NormalizeAssocLists.hpp"
#include "frontend/visitor/WaitConditions.hpp"
#include "frontend/visitor/WarnUnused.hpp"
#include "frontend/visitor/TransformSigAssign.hpp"
#include "intermediate/visitor/PrintCode.hpp"
#include "intermediate/visitor/GenCCode.hpp"
#include "intermediate/visitor/LookupSymbols.hpp"

namespace compiler {

FAUhdlc::FAUhdlc() : topNode(NULL), 
			parseOnly(false), 
			dotParse(false), 
			resultCode(FAUHDLC_EXIT_SUCCESS),
			dotParseFile(NULL),
			outputFile(NULL),
			symbolTable(NULL),
			iTopNode(NULL),
			dotConst(false),
			dotConstFile(NULL),
			freeStanding(false),
			cFile(NULL)
{
}

FAUhdlc::~FAUhdlc() 
{
	// cleanup memory (only useful for leak checking)
	for (std::list<struct FAUhdlc::LibFile*>::iterator i =
		this->sourceFiles.begin(); i != this->sourceFiles.end(); 
		i++) {

		delete (*i)->filename;
		delete *i;
	}
	util::MiscUtil::terminate(this->topNode);
	delete this->symbolTable;
	util::MiscUtil::terminate(this->iTopNode);
}

void
FAUhdlc::parseCmdLine(int argc, char** argv) 
{
	const char *currentLib = "work";
	bool success = true;
	bool again = true;
	int c;
	struct option l_opts[] = {
		/* name, has_arg, *flag, val */
		{"parse-only", 0, NULL, 'p'},
		{"dot-parse", 1, NULL, 'd'},
		{"dot-const", 1, NULL, 'c'},
		{"lib", 1, NULL, 'l'},
		{"output", 1, NULL, 'o'},
		{"c-output", 1, NULL, 'C'},
		{"help", 0, NULL, 'h'},
		{"freestanding", 0, NULL, 'f'},
		{0, 0, 0, 0}
	};



	while (again) {
		c = getopt_long(argc, argv, "-pd:c:l:o:hW:C:f", l_opts, NULL);

		switch (c) {
		case -1: /* no more options */
			again = false;
			break;

		case 1: /* "non"-option, name of compile file */ {
			struct FAUhdlc::LibFile *f = new struct FAUhdlc::LibFile();
			f->filename = new std::string(optarg);
			f->library = currentLib;

			this->sourceFiles.push_back(f);
			break;
		    }
		case 'p': /* parse-only */
			this->parseOnly = true;
			break;

		case 'd': /* dot-parse=<filename> */
			this->dotParse = true;
			this->dotParseFile = optarg;
			break;

		case 'c': /* dot-const=<filename> */
			this->dotConst = true;
			this->dotConstFile = optarg;
			break;

		case 'l': /* lib=<libname> */
			currentLib = optarg;
			break;

		case 'o': /* output=<filename> */
			this->outputFile = optarg;
			break;

		case 'h': /* help */
			success = false;
			break;

		case 'W': /* warning options */
			if (strcmp("error", optarg) == 0) {
				ast::ErrorRegistry::setWerror(true);
			} else {
				success = false;
			}
			break;

		case 'f': /* freestanding */
			this->freeStanding = true;
			break;

		case 'C': /* C-Output */
			this->cFile = optarg;
			break;
		};
	}

	/** at least one file? */
	if (this->sourceFiles.size() == 0) {
		success = false;
	}

	if (! success) {
		this->resultCode = FAUHDLC_EXIT_COMMAND_ARGS;
		this->usage(std::cerr);
	}
}

int 
FAUhdlc::run(void)
{
	if (this->resultCode != FAUHDLC_EXIT_SUCCESS) {
		return this->resultCode;
	}

	this->doParse();

	if (   (this->resultCode != FAUHDLC_EXIT_SUCCESS) 
            || (this->topNode == NULL)
	    || ast::ErrorRegistry::hasErrors()) {

	    	this->putCompileMessages();
		return this->resultCode;
	}

	if (this->parseOnly) {
		return this->resultCode;
	}

	try {
		this->doSemanticAnalysis();
	} catch (std::runtime_error f) {
		this->resultCode = FAUHDLC_EXIT_EXCEPTION;
		std::cerr << f.what() << std::endl;
	} catch (ast::CompileError ce) {
		this->resultCode = FAUHDLC_EXIT_EXCEPTION;
		std::cerr << "ERROR> " << ce << std::endl;
	} catch (...) {
		this->resultCode = FAUHDLC_EXIT_EXCEPTION;
		std::cerr << "unknown exception occured." << std::endl;
	}

	this->putCompileMessages();

	if (this->resultCode != FAUHDLC_EXIT_SUCCESS) {
		return this->resultCode;
	}

	try {
		this->doIntermediateTransform();
	} catch (std::runtime_error f) {
		this->resultCode = FAUHDLC_EXIT_EXCEPTION;
		std::cerr << f.what() << std::endl;

	} catch (...) {
		this->resultCode = FAUHDLC_EXIT_EXCEPTION;
		std::cerr << "unknown exception occured." << std::endl;
	}

	return this->resultCode;
}

void
FAUhdlc::doParse(void)
{
	this->symbolTable = new ast::BuiltinSymbolTable();
	yy::ParserDriver driver = yy::ParserDriver(*symbolTable);

	if (! this->freeStanding) {
		FAUhdlc::LibFile *lf = new FAUhdlc::LibFile();
		lf->filename = new std::string(
					VHDL_DATA_DIR "/std_logic_1164.vhdl");
		lf->library = "ieee";
		this->sourceFiles.push_front(lf);
	}

	for (std::list<struct LibFile*>::const_iterator i = 
		this->sourceFiles.begin(); i != this->sourceFiles.end();
		i++) {
		
		try {
			driver.parse(*((*i)->filename), (*i)->library);
		} catch (yy::SyntaxError e) {
			if (ast::ErrorRegistry::hasErrors()) {
				// don't report syntax errors, there are 
				// other, probably more accute errors
				this->resultCode = FAUHDLC_EXIT_ERROR;
				return;
			}

			std::cerr << e.what() << std::endl;
			this->resultCode = FAUHDLC_EXIT_ERROR;
			return;
		} catch (std::runtime_error g) {
			std::cerr << g.what() << std::endl;
			this->resultCode = FAUHDLC_EXIT_EXCEPTION;
			return;
		} catch (ast::CompileError h) {
			std::cerr << "ERROR> " << h << std::endl;
		} catch (...) {
			std::cerr << "unknown exception occured." 
				  << std::endl;
			this->resultCode = FAUHDLC_EXIT_EXCEPTION;
			return;
		}
	}
	this->topNode = driver.topNode;
	if (this->topNode == NULL) {
		this->resultCode = FAUHDLC_EXIT_EXCEPTION;
		return;
	}

	// dot graph after parsing?
	if (this->dotParse) {
#ifdef DEBUG
		std::cerr << "========= DOT VISITOR ==========" << std::endl;
#endif
		ast::DotVisitor v = ast::DotVisitor();
		this->topNode->accept(v);
		
		std::ofstream stream;
		stream.open(this->dotParseFile, std::ofstream::out);
		v.put(stream);
		stream.close();
	}
}

void
FAUhdlc::putCompileMessages(void)
{
	// write warnings if any 
	if (ast::ErrorRegistry::hasWarnings()) {
		ast::ErrorRegistry::putWarnings(std::cerr);
	}

	// write errors if any
	if (ast::ErrorRegistry::hasErrors()) {
		ast::ErrorRegistry::putErrors(std::cerr);
		std::cerr << std::endl;
		this->resultCode = FAUHDLC_EXIT_ERROR;
	}
}

void
FAUhdlc::doSemanticAnalysis(void)
{
#ifdef DEBUG
	std::cerr << "======= TRANSFORM SIGASSIGNS =====" << std::endl;
#endif
	ast::TransformSigAssign tsa = ast::TransformSigAssign();
	this->topNode->accept(tsa);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= RESOLVE TYPES ==========" << std::endl;
#endif
	ast::ResolveTypes rt = ast::ResolveTypes(*this->symbolTable);
	this->topNode->accept(rt);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= PROPAGATE CONSTANT ========" << std::endl;
#endif
	ast::ConstantPropagation cp = ast::ConstantPropagation();
	this->topNode->accept(cp);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

	if (this->dotConst) {
#ifdef DEBUG
		std::cerr << "========= DOT VISITOR ==========" << std::endl;
#endif
		ast::DotVisitor v = ast::DotVisitor();
		this->topNode->accept(v);
		
		std::ofstream stream;
		stream.open(this->dotConstFile, std::ofstream::out);
		v.put(stream);
		stream.close();
	}

#ifdef DEBUG
	std::cerr << "========= CHECK LOOPS ========" << std::endl;
#endif
	ast::CheckLoops cl = ast::CheckLoops();
	this->topNode->accept(cl);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= NORMALIZE ASSOCS ========" << std::endl;
#endif
	ast::NormalizeAssocLists na = ast::NormalizeAssocLists();
	this->topNode->accept(na);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= CHECK ACCESS MODE ========" << std::endl;
#endif
	ast::CheckAccessMode cam = ast::CheckAccessMode();
	this->topNode->accept(cam);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "=========== WARN UNUSED ===========" << std::endl;
#endif
	ast::WarnUnused wuu = ast::WarnUnused();
	this->topNode->accept(wuu);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= GATHER IMPLICITS ========" << std::endl;
#endif
	ast::GatherImplicits giv = ast::GatherImplicits();
	this->topNode->accept(giv);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= SET PATH NAMES ==========" << std::endl;
#endif
	ast::SetPathName spn = ast::SetPathName();
	this->topNode->accept(spn);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

#ifdef DEBUG
	std::cerr << "========= CHECK WAIT CONDS ========" << std::endl;
#endif
	ast::WaitConditions cwc = ast::WaitConditions();
	this->topNode->accept(cwc);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}


#ifdef DEBUG
	std::cerr << "========= GENERATE ICODE =========" << std::endl;
#endif
	ast::GenCode gc = ast::GenCode();
	this->topNode->accept(gc);

	if (ast::ErrorRegistry::hasErrors()) {
		return;
	}

	this->iTopNode = gc.container;
}

void
FAUhdlc::doIntermediateTransform(void)
{
	intermediate::LookupSymbols ls = intermediate::LookupSymbols();
	assert(this->iTopNode != NULL);
	this->iTopNode->accept(ls);

	this->printICode();
	this->genCCode();
}

void
FAUhdlc::printICode(void)
{
	if (this->outputFile == NULL) {
		return;
	}

	std::ofstream out;
	out.open(this->outputFile, std::ofstream::out);
	if (! out.good()) {
		out.close();
		std::cerr << "Couldn't open " << this->outputFile 
			<< " for writing." << std::endl;
		return;
	}
	intermediate::PrintCode pc = intermediate::PrintCode(out);

	assert(this->iTopNode != NULL);
	this->iTopNode->accept(pc);
	out.close();
}

void
FAUhdlc::genCCode(void)
{
	if (this->cFile == NULL) {
		return;
	}

	std::ofstream out;
	out.open(this->cFile, std::ofstream::out);
	if (! out.good()) {
		out.close();
		std::cerr << "Couldn't open " << this->cFile
			<< " for writing." << std::endl;
		return;
	}

	intermediate::GenCCode gcv = intermediate::GenCCode(out);

	assert(this->iTopNode != NULL);
	this->iTopNode->accept(gcv);
	out.close();
}

void
FAUhdlc::usage(std::ostream &out) const
{
	out << "FAUhdlc a VHDL to C compiler" << std::endl;
	out << std::endl;
	out << "Usage: fauhdlc [OPTIONS] files" << std::endl;
	out << "OPTIONS:" << std::endl;

	out << "\t--help              \tDisplay this help." << std::endl;
	out << "\t-o filaneme       "
            << "\tOutput to file filename." 
            << std::endl;

	out << "\t--parse-only       "
            << "\tstop with semantic analysis after parsing." 
            << std::endl;

	out << "\t--dot-parse=filename"
            << "\tCreate a dot-file called filename after parsing." 
            << std::endl;

	out << "\t--dot-const=filename"
            << "\tCreate a dot-file called filename after const. propagation."
            << std::endl;

	out << "\t--lib=library       "
	    << "\tAll following files belong to library." 
            << std::endl;
	
	out << "\t-Werror            "
	    << "\tReport warnings as errors."
	    << std::endl;

	out << "\t--freestanding     "
	    << "\tFreestanding mode (don't load std_logic)."
	    << std::endl;

	out << "\t--c-output         "
	    << "\tCreate output filename in C language."
	    << std::endl;

	out << std::endl;

	out << "For each source file, a separate library may be specified."
            << std::endl;
	out << "If no library has been specified, 'work' will be used." 
	    << std::endl;

	out << std::endl;
	out << "Exit codes:" << std::endl;
	out << "\t1\twrong command line parameters." << std::endl;
	out << "\t3\tcompile error was found failed." << std::endl;
	out << "\t4\tOther exception occurred." << std::endl;
	out << std::endl;
}



}; /* namespace compiler */

int
main(int argc, char** argv) 
{
	util::GarbageCollect::initialize();
	compiler::FAUhdlc fauhdlc = compiler::FAUhdlc();
	fauhdlc.parseCmdLine(argc, argv);

	return fauhdlc.run();
}
