/* $Id$ 
 * NameLookup: Knows about VHDL names and how/where to look these up in the
 * symboltable (i.e. it can e.g. switch the scope for prefixed names etc.)
 *
 * Copyright (C) 2008-2009 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 "frontend/misc/NameLookup.hpp"
#include "frontend/misc/Symbol.hpp"
#include "frontend/ast/SimpleName.hpp"
#include "frontend/ast/SelectedName.hpp"
#include "frontend/ast/FunctionCall.hpp"
#include "frontend/ast/UnconstrainedArrayType.hpp"
#include "util/MiscUtil.hpp"
#include "frontend/ast/FunctionDeclaration.hpp"
#include "frontend/reporting/CompileError.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include "frontend/reporting/UndefinedSymbol.hpp"
#include "frontend/visitor/ResolveTypes.hpp"
#include "frontend/visitor/LookupTypes.hpp"
#include <set>
#include <sstream>

namespace ast {

NameLookup::NameLookup(
	SymbolTable &symtab
) : 		symbolTable(symtab),
		shiftedLookups(false)
{
}

void
NameLookup::openRegion(Name *name) /* throw(CompileError) */
{
	const SimpleName *sname = dynamic_cast<const SimpleName*>(name);

	assert(sname != NULL); /* logic error in parser */

	if (sname->candidates.size() > 1) {
		std::string msg = "Multiple definitions for Name '";
		msg += util::MiscUtil::toString(*name);
		msg += "'.";

		throw CompileError(*name, msg);
	}

	if (sname->candidates.empty()) {
		std::string msg = "Cannot find definition for Name '";
		msg += util::MiscUtil::toString(*name);
		msg += "'.";
		
		throw CompileError(*name, msg);
	}

	const Symbol *sym = sname->candidates.front();
	assert(sym->region != NULL); // logic error in parser

	this->symbolTable.pushRegion(*sym->region);
}

void
NameLookup::shiftScope(Name &prefix, bool isFunction)
{
	this->shiftedLookups = true;
	//LRM: 6.3

	// hit by preference? symbol is an enclosing named entity
	// -> expanded name, nothing else considered.
	for(std::list<Symbol*>::const_iterator i = prefix.candidates.begin();
		i != prefix.candidates.end(); i++) {
		
		const DeclarativeRegion *current = 
				this->symbolTable.getCurrentRegion();

		assert(current != NULL);
		if (current->isEnclosingSymbol(**i)) {
			//immediate hit.
			this->shiftExpanded(**i);
			return;
		}
	}

	// is the prefix a function call? -> only selected name possible
	if (isFunction) {
		this->shiftSelectedScope(prefix);
		return;
	}

	// does the prefix denote a library/package?
	if (prefix.candidates.size() == 1) {
		switch(prefix.candidates.front()->type) {
		case SYMBOL_PACKAGE:
		case SYMBOL_LIBRARY:
			this->shiftExpanded(*prefix.candidates.front());
			return;
		default:
			break;
		}
	}

	//remaining symbols must be selected names
	this->shiftSelectedScope(prefix);
}

void
NameLookup::shiftAttributeScope(void)
{
	assert(this->lookupScopes.empty());

	DeclarativeRegion *hidden = this->symbolTable.getAttributeRegion();
	this->lookupScopes.push_back(hidden);
	this->shiftedLookups = true;
}

ast::Callable*
NameLookup::findOrRegisterSubprog(Callable* spec)
{
	enum symType t = SYMBOL_PROCEDURE;

	switch(spec->kind) {
	case SubprogBody::PROG_KIND_PROCEDURE:
		t = SYMBOL_PROCEDURE;
		break;

	case SubprogBody::PROG_KIND_FUNCTION:
		t = SYMBOL_FUNCTION;
		break;

	default:
		assert(false);
	}

	/* lookup if specification registered already. */
	assert(spec->name);
	bool found = false;
	Callable *c = NULL;

	std::list<Symbol*> ret = this->symbolTable.lookup(*spec->name);
	for (std::list<Symbol*>::const_iterator i = ret.begin(); 
		i != ret.end(); i++) {

		if ((*i)->type != t) {
			continue;
		}

		switch(spec->kind) {
		case SubprogBody::PROG_KIND_PROCEDURE:
			c = dynamic_cast<Callable*>(&(*i)->declaration);
			found = this->conformProcSig(spec, c);
			break;

		case SubprogBody::PROG_KIND_FUNCTION: {
			FunctionDeclaration *f;
			FunctionDeclaration *g;

			f = dynamic_cast<FunctionDeclaration*>(
						&(*i)->declaration);
			assert(f);
			g = dynamic_cast<FunctionDeclaration*>(spec);
			assert(g);

			found = this->conformFuncSig(g, f);
			c = f;
			break;
		    }
		default:
			assert(false);
		}

		if (found) {
			break;
		}
	}

	if (! found) {
		this->symbolTable.registerSymbolWithRegion(t, *spec);
		if (spec->arguments == NULL) {
			return spec;
		}

		//register formals
		for (std::list<ValDeclaration*>::const_iterator i = 
			spec->arguments->begin(); i != spec->arguments->end();
			i++) {

			this->symbolTable.registerSymbol(SYMBOL_PARAMETER, 
							**i);
		}
		return spec;
	}

	assert(c != NULL);
	assert(c->region != NULL);
	this->symbolTable.pushRegion(*c->region);
	c->seen = true;
	return c;
}

std::list<Symbol*>
NameLookup::lookup(std::string &id) const
{
	if (! this->shiftedLookups) {
		assert(this->lookupScopes.empty());
		return this->symbolTable.lookup(id);
	}

	std::set<Symbol*> unionSet = std::set<Symbol*>();

	for (std::list<DeclarativeRegion*>::const_iterator i = 
		this->lookupScopes.begin(); i != this->lookupScopes.end();
		i++) {

		std::list<Symbol*> lu = (*i)->lookupLocal(id);
		if (! lu.empty()) {
			unionSet.insert(lu.begin(), lu.end());
		}
	}

	std::list<Symbol*> ret;
	ret.insert(ret.end(), unionSet.begin(), unionSet.end());
	return ret;
}

void
NameLookup::unshiftScope(void)
{
	this->lookupScopes.clear();
	this->shiftedLookups = false;
}

Name* 
NameLookup::makeSelectedName(
	Expression *prefix,
	std::string *suffix,
	std::list<Symbol*> candidates,
	Location loc) const
{
	if (this->isExpanded) {
		SimpleName *s = dynamic_cast<SimpleName*>(prefix);
		assert(s); // otherwise isExpanded is wrong

		SimpleName *ret = new SimpleName(suffix, candidates, loc);
		if (*suffix == "all") {
			//store candidates of prefix, to be used for use
			//clauses
			ret->candidates = s->candidates;
		}

		ret->prefixStrings = s->prefixStrings;
		ret->prefixStrings.push_back(s->name);
		s->name = NULL;
		s->prefixStrings.clear();

		util::MiscUtil::terminate(s);
		return ret;
	}

	//selected name
	return new SelectedName(suffix, prefix, candidates, loc);
}

void
NameLookup::registerLibClauses(const std::list<SimpleName*> &libs)
{
	for (std::list<SimpleName*>::const_iterator i = libs.begin();
		i != libs.end(); i++) {

		if (! this->symbolTable.addlibrary(*(*i)->name)) {
			std::string msg = "Library '" + *(*i)->name 
					+ "' not found.";
			CompileError *err = new CompileError(*(*i), msg);
			ErrorRegistry::addError(err);
		}
	}
}

void
NameLookup::registerUseClauses(const std::list<Name*> &usecs)
{
	for (std::list<Name*>::const_iterator i = usecs.begin();
		i != usecs.end(); i++) {

		SimpleName *s = dynamic_cast<SimpleName*>(*i);
		if (s == NULL) {
			// not an expanded name, report an error.
			CompileError *ce = new CompileError(
						**i,
						"Wrong name in use-clause.");
			ErrorRegistry::addError(ce);
			return;
		}

		//need to resolve to exactly one symbol.
		if (s->candidates.empty()) {
			// error was already reported.
			continue;
		}

		// FIXME could be more than one symbols
		assert(s->candidates.size() == 1);
		Symbol *sym = s->candidates.front();

		// suffix "all"
		if (*s->name == "all") {
			assert(sym->region);
			this->symbolTable.importSymbols(*sym->region);

			continue;
		}

		// FIXME potyra (this goes through to the parser): s.th. l.ike
		//       import x.'y' is also possible, which would be 
		//       a character enumeration literal.
		this->symbolTable.importSymbol(*sym);
	}

}

void
NameLookup::shiftExpanded(const Symbol &sym)
{
	assert(sym.region != NULL);
	assert(this->lookupScopes.empty());

	this->lookupScopes.push_back(sym.region);
	this->isExpanded = true;
	assert(this->shiftedLookups);
}

std::list<Symbol*>
NameLookup::shiftSubscriptCands(std::list<Symbol*> &cands, Location loc)
{
	std::list<Symbol*> result;

	for (std::list<Symbol*>::iterator i = cands.begin(); 
		i != cands.end(); /* nothing */) {
		
		Symbol *s = NameLookup::shiftSubscriptCand(*i);
		if (s == NULL) {
			// not an array, remove candidate
			i = cands.erase(i);
			continue;
		}

		result.push_back(s);
		i++;
	}

	if (result.empty()) {
		CompileError *ce = new CompileError(
					loc,
					"Subscript to non-array");
		ErrorRegistry::addError(ce);
	}

	return result;
}

Symbol *
NameLookup::shiftSubscriptCand(Symbol *candidate)
{
	LookupTypes lt = LookupTypes(true, false);
	candidate->declaration.accept(lt);

	const UnconstrainedArrayType *array = 
		dynamic_cast<const UnconstrainedArrayType*>(lt.declaration);

	if (array == NULL) {
		return NULL;
	}

	TypeDeclaration *et = array->elementType;
	assert(et != NULL);

	// FIXME: memory leak (noone will free this symbol)
	// the return value is a temporary symbol, which points only to the
	// TypeDeclaration in question and has it's region set.
	std::string *tn;

	if (et->name != NULL) {
		tn = new std::string(*et->name);
	} else {
		tn = new std::string("__temporary__");
	}
	Symbol *ret = new Symbol(tn,
				SYMBOL_TYPE, 
				et->region, // region unneeded
				*et);

	return ret;
}

void
NameLookup::shiftSelectedScope(Name &prefix)
{
	/* open scope of type of prefix */
	this->isExpanded = false;
	this->shiftedLookups = true;

	if (prefix.candidates.empty()) {
		/* unresolved symbol already reported. */
		return;
	}

	for (std::list<Symbol*>::const_iterator i = prefix.candidates.begin();
		i != prefix.candidates.end(); i++) {

		this->shiftSelectedSym(*i);
	}
}

void
NameLookup::shiftSelectedSym(Symbol *prefixSym)
{
	LookupTypes lt = LookupTypes(true, false);
	prefixSym->declaration.accept(lt);

	if (lt.declaration == NULL) {
		return;
	}

	if (lt.declaration->region == NULL) {
		return;
	}

	this->lookupScopes.push_back(lt.declaration->region);
	assert(this->shiftedLookups);
}

/* FIXME for conform*: 
 *       according to LRM, 2.7 this check is not enough: 
 *       instead, lexical elements (!) have to get compared.
 */
bool
NameLookup::conformProcSig(const Callable* a, const Callable* b)
{
	if ((a == NULL) || (b == NULL)) {
		return false;
	}

	return util::MiscUtil::listMatch(*a->arguments, *b->arguments, 
					NameLookup::conformParameter);

}

bool
NameLookup::conformFuncSig(
	const FunctionDeclaration* a,
	const FunctionDeclaration* b
)
{
	if (! NameLookup::conformProcSig(a, b)) {
		return false;
	}
	
	assert(a->returnType);
	assert(b->returnType);

	assert(a->returnType->typeName);
	assert(a->returnType->typeName->candidates.size() == 1);
	assert(b->returnType->typeName);
	assert(b->returnType->typeName->candidates.size() == 1);

	if (   a->returnType->typeName->candidates.front() 
	    != b->returnType->typeName->candidates.front()) {

		return false;
	}

	return true;
}

bool
NameLookup::conformParameter(
	const ValDeclaration* a, 
	const ValDeclaration* b
)
{
	assert(a);
	assert(b);

	if ((*a->name) != (*b->name)) {
		return false;
	}

	if (a->mode != b->mode) {
		return false;
	}

	if (a->storageClass != b->storageClass) {
		return false;
	}

	//compare pointers!
	assert(a->subtypeIndic);
	assert(b->subtypeIndic);
	assert(a->subtypeIndic->typeName);
	assert(b->subtypeIndic->typeName);
	assert(a->subtypeIndic->typeName->candidates.size() == 1);
	assert(b->subtypeIndic->typeName->candidates.size() == 1);
	
	if (   a->subtypeIndic->typeName->candidates.front() 
	    != b->subtypeIndic->typeName->candidates.front()) {

	    	return false;
	}

	return true;
}

}; /* namespace ast */
