/* $Id$ 
 *
 * 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/visitor/ResolveSymbols.hpp"
#include <cassert>
#include <set>
#include "frontend/ast/SimpleName.hpp"
#include "frontend/ast/TemporaryName.hpp"
#include "frontend/ast/SelectedName.hpp"
#include "frontend/ast/AttributeName.hpp"
#include "frontend/ast/ConcurrentStat.hpp"
#include "frontend/ast/RecordTypeElement.hpp"
#include "frontend/ast/ValDeclaration.hpp"
#include "frontend/ast/PhysicalTypeUnit.hpp"
#include "frontend/ast/FunctionDeclaration.hpp"
#include "frontend/ast/EnumerationType.hpp"
#include "frontend/ast/Subscript.hpp"
#include "frontend/ast/Slice.hpp"
#include "frontend/ast/FunctionCall.hpp"
#include "frontend/ast/CompInstStat.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include "frontend/reporting/UndefinedSymbol.hpp"
#include "frontend/visitor/LookupTypes.hpp"
#include "frontend/misc/NameLookup.hpp"

namespace ast {

void
ResolveSymbols::visit(SimpleName &node)
{	
	assert(node.name);
	node.candidates = this->symTab.lookup(*node.name);
	this->prefixCands = &node.candidates;
}

void
ResolveSymbols::visit(TemporaryName &node)
{
	// only traverse to prefix.
	node.prefix->accept(*this);
}

void
ResolveSymbols::visit(SelectedName &node)
{
	node.prefix->accept(*this);
	if (this->prefixCands == NULL) {
		// error reported somewhere else already
		return;
	}

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

	for (symListT::iterator i = this->prefixCands->begin();
		i != this->prefixCands->end(); /* nothing */) {
		
		LookupTypes lt = LookupTypes(true, false);
		(*i)->declaration.accept(lt);
		if (lt.declaration == NULL) {
			i = this->prefixCands->erase(i);
			continue;
		}

		if (lt.declaration->region == NULL) {
			i = this->prefixCands->erase(i);
			continue;
		}
		
		symListT c = lt.declaration->region->lookupLocal(*node.name);
		if (c.empty()) {
			i = this->prefixCands->erase(i);
		}
		
		// candidate found
		symSet.insert(c.begin(), c.end());
		i++;
	}

	if (symSet.empty()) {
		UndefinedSymbol *us = 
			new UndefinedSymbol(*node.name, node.location);
		ErrorRegistry::addError(us);
		this->prefixCands = NULL;
		return;
	}

	node.candidates.insert(node.candidates.begin(), symSet.begin(), 
				symSet.end());
	this->prefixCands = &node.candidates;
}

void
ResolveSymbols::visit(AttributeName &node)
{
	assert(false); // attributes not supported yet
}

void
ResolveSymbols::visit(Slice &node)
{
	node.source->accept(*this);
	symListT *backup = this->prefixCands;
	this->prefixCands = NULL;
	
	node.range->accept(*this);
	this->prefixCands = backup;
}

void
ResolveSymbols::visit(Subscript &node)
{
	node.source->accept(*this);
	if (this->prefixCands == NULL) {
		// error reported already
		return;
	}

	symListT *backup = this->prefixCands;
	this->prefixCands = NULL;

	// traverse to indices
	assert(node.indices != NULL);
	this->listTraverse(*node.indices);

	// FIXME mem leak
	this->prefixCands = new symListT();
	*this->prefixCands = NameLookup::shiftSubscriptCands(
					*backup,
					node.location);

	if (this->prefixCands->empty()) {
		// error reported already via NameLookup
		delete this->prefixCands;
		this->prefixCands = NULL;
	}
}

void
ResolveSymbols::visit(FunctionCall &node)
{
	this->prefixCands = NULL;
	node.subprog->accept(*this);
	// leave prefixCands as is, AssociationElement will need them to
	// shift the lookup scope.

	if (node.arguments != NULL) {
		this->listTraverse(*node.arguments);
	}

	// leave prefixCands as is, LookupTypes will deal with function symbols
	// as intended.
}

void
ResolveSymbols::visit(AssociationElement &node)
{
	symListT *backup = this->prefixCands;
	this->prefixCands = NULL;

	if (node.formal != NULL) {
		//FIXME not quite correct, but will work atm.
		// 1) no idea, where the formal part is, parsing might
		//    have been wrong (but that should be fixed in the parser)
		// 2) formal names should be looked up only in local scope

		assert(backup->size() == 1);
		Symbol *sym = backup->front();
		assert(sym->region != NULL);
		this->symTab.pushRegion(*sym->region);

		node.formal->accept(*this);
		this->symTab.popRegion();
	}

	assert(node.actual != NULL);
	node.actual->accept(*this);
	this->prefixCands = backup;
}

void
ResolveSymbols::visit(ElementAssociation &node)
{
	/* FIXME for named (record) associations, the scope must be
	   shifted to element names */
	if (node.choices != NULL) {
		this->listTraverse(*node.choices);
	}

	if (node.actual != NULL) {
		node.actual->accept(*this);
	}
}

void
ResolveSymbols::process(Expression &node)
{
	// cannot be part of a name. get rid of prefixCands
	this->prefixCands = NULL;
}

void
ResolveSymbols::visit(CompInstStat &node)
{
	this->prefixCands = NULL;
	assert(node.entityName != NULL);

	// must have been resolved already by the parser!
	if (node.entityName->candidates.size() != 1) {
		// symbol error already reported from parser, bail
		// out early
		return;
	}

	// set prefixCands to instantiated Entity.
	this->prefixCands = &node.entityName->candidates;

	if (node.genericMap != NULL) {
		this->listTraverse(*node.genericMap);
	}

	if (node.portMap != NULL) {
		this->listTraverse(*node.portMap);
	}
	this->prefixCands = NULL;
}

void
ResolveSymbols::process(AstNode &node)
{
	// should not happen, ResolveSymbols is only meant to resolve 
	// formal parts, so make sure we encounter no unhandled node.
	assert(false);
}

}; /* namespace ast */
