/* $Id$
 *
 * Normalize association lists.
 *
 * 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/NormalizeAssocLists.hpp"
#include "frontend/ast/CompInstStat.hpp"
#include "frontend/ast/AttributeName.hpp"
#include "frontend/ast/SelectedName.hpp"
#include "frontend/ast/SimpleName.hpp"
#include "frontend/ast/TemporaryName.hpp"
#include "frontend/ast/AssociationElement.hpp"
#include "frontend/ast/LibraryList.hpp"
#include "frontend/ast/Library.hpp"
#include "frontend/ast/Architecture.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include "frontend/ast/NodeFactory.hpp"
#include <algorithm>
#include <vector>


namespace ast {

void
NormalizeAssocLists::visit(CompInstStat &node)
{
	assert(node.entity != NULL);
	assert(node.entity->ports != NULL);
	assert(node.entity->generics != NULL);
	assert(node.entity->region != NULL);

	this->formals = 
		*NodeFactory::listCast<ValDeclaration *>(node.entity->ports);
	this->positional = true;
	this->pos = 0;
	this->region = node.entity->region;

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

		/* sort port map by position */
		std::vector<AssociationElement*> v;
		v.insert(v.begin(), 
			node.portMap->begin(), node.portMap->end());

		std::sort(v.begin(), v.end(), 
			NormalizeAssocLists::sortPosPred);

		node.portMap->clear();
		node.portMap->insert(node.portMap->begin(), 
			v.begin(), v.end());
	}


	this->formals.clear();
	this->formals = 
		*NodeFactory::listCast<ValDeclaration *>(
						node.entity->generics);
	this->positional = true;
	this->pos = 0;

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

		/* sort generic map by position */
		std::vector<AssociationElement*> v;
		v.insert(v.begin(), 
			node.genericMap->begin(), node.genericMap->end());

		std::sort(v.begin(), v.end(), 
			NormalizeAssocLists::sortPosPred);

		node.genericMap->clear();
		node.genericMap->insert(node.genericMap->begin(), 
			v.begin(), v.end());
	}

	this->formals.clear();
	this->region = NULL;
}

void
NormalizeAssocLists::visit(AssociationElement &node)
{
	if (node.formal == NULL) {
		if (! this->positional) {
			CompileError *ce = 
				new CompileError(node, 
					"Positional association "
					"used after named association.");
			ErrorRegistry::addError(ce);
			return;
		}
		
		if (this->formals.empty()) {
			CompileError *ce = 
				new CompileError(node,
					"Cannot associate positional "
					"association (too many arguments?).");
			ErrorRegistry::addError(ce);
			return;
		}

		// register SimpleName as formal
		assert(this->region != NULL);
		const ValDeclaration *formal = this->formals.front();
		SimpleName *sn = 
			NormalizeAssocLists::makeNameOfVal(
							formal, 
							this->region,
							node.location);

		node.formal = sn;
		this->formals.pop_front();
		node.pos = this->pos;
		this->pos++;

		return;
	}

	// named association
	this->positional = false;
	this->pickedUpVal = NULL;
	node.formal->accept(*this);
	assert(this->pickedUpVal != NULL);

	node.pos = this->findAndRemove(this->pickedUpVal, node.location);
	this->pickedUpVal = NULL;
}

void
NormalizeAssocLists::visit(SimpleName &node)
{
	const SymbolDeclaration *decl = node.getDeclaration();
	const ValDeclaration *sig = 
		dynamic_cast<const ValDeclaration*>(decl);

	if (sig == NULL) {
		assert(decl != NULL);
		std::string msg = "AssociationElement <";
		msg += util::MiscUtil::toString(node);
		msg += "> refers to <";
		msg += util::MiscUtil::toString(decl->location);
		msg += ": ";
		msg += util::MiscUtil::toString(*decl);
		msg += ">, which is not an object declaration.";

		CompileError *ce = 
			new CompileError(node, msg);

		// bail out immediately, algorithm might otherwise
		// break elsewhere
		throw(*ce);
	}
	assert(sig != NULL);
	this->pickedUpVal = sig;
}

void
NormalizeAssocLists::visit(TemporaryName &node)
{
	CompileError *ce = 
		new CompileError(node,
				"Cannot use temporary names in "
				"associations.");
	ErrorRegistry::addError(ce);
}

void
NormalizeAssocLists::visit(SelectedName &node)
{
	// FIXME individual association currently unsupported.
	assert(false);
}

void
NormalizeAssocLists::visit(AttributeName &node)
{
	CompileError *ce = 
		new CompileError(node, 
				"Cannot use attributes in port "
				"associations.");
	ErrorRegistry::addError(ce);					
}

void 
NormalizeAssocLists::visit(Architecture &node) 
{
	/* traverse to concurrentStats */
	this->arch = &node;
	if (node.concurrentStats != NULL) {
		this->listTraverse(*node.concurrentStats);
	}
	this->arch = NULL;
}

void
NormalizeAssocLists::visit(Library& node)
{
	this->listTraverse(node.units);
}

void
NormalizeAssocLists::visit(LibraryList& node)
{
	this->listTraverse(node.libraries);
}

int
NormalizeAssocLists::findAndRemove(
	const ValDeclaration *val,
	Location errLoc
)
{
	int p = this->pos;
	for (std::list<ValDeclaration *>::iterator i = this->formals.begin();
		i != this->formals.end();
		i++, p++) {

		if ((*i) == val) {
			this->formals.erase(i);
			return p;
		}
	}

	std::string msg = "Error in association: <";
	msg += *(val->name);
	msg += "> already associated.";

	CompileError *ce = new CompileError(errLoc, msg);
	ErrorRegistry::addError(ce);
	return -1;

}

template <typename T>
void
NormalizeAssocLists::addMissingAssocs(
	CompInstStat &node,
	std::list<ValDeclaration*> &unassociated,
	const char *kind,
	enum symType symbolType,
	std::list<AssociationElement*> &mapList
)
{
	while (! unassociated.empty()) {
		ValDeclaration *formal = unassociated.front();
		this->addMissingAssoc<T>(
					node.location, 
					*formal, 
					kind, 
					symbolType, 
					mapList);
		unassociated.pop_front();
	}
}

template <typename T>
void
NormalizeAssocLists::addMissingAssoc(
	const Location &loc,
	ValDeclaration &formal,
	const char *kind,
	enum symType symbolType,
	std::list<AssociationElement*> &mapList
)
{
	switch (formal.mode) {
	case ValDeclaration::MODE_IN:
		// must have initializer (LRM 1.1.1.2)
		if (formal.init == NULL) {
			std::string msg = std::string("Must associate ");
			msg += kind;
			msg += " <";
			msg += *formal.name;
			msg += "> which has mode IN but no ";
			msg += "initializer.";
			CompileError *ce = new CompileError(loc, msg);
			ErrorRegistry::addError(ce);
			return;
		}
		break;

	case ValDeclaration::MODE_OUT:
	case ValDeclaration::MODE_INOUT:
		// ok
		break;
	}
	
	// make excplicit association with an implicit
	std::string *n = new std::string(*(formal.name));
	(*n) += "__hidden__";
	(*n) += util::MiscUtil::toString(this->hiddenCounter);

	T *hiddenActual = NormalizeAssocLists::createVal<T>(
					n,
					formal.mode,
					formal.init,
					formal.subtypeIndic,
					loc);

	if (hiddenActual->isUnconstraint()) {
		if (formal.init == NULL) {
			std::string msg = "Must associate ";
			msg += kind;
			msg += " <";
			msg += *formal.name;
			msg += "> which is an unconstraint ";
			msg += "array but has no initializer.";
			CompileError *ce = new CompileError(loc, msg);
			ErrorRegistry::addError(ce);
			return;
		}

		SubtypeIndication *actualInferredType = 
			dynamic_cast<SubtypeIndication*>(formal.init->type);
		assert(actualInferredType != NULL);
		hiddenActual->subtypeIndic = actualInferredType;
	}

	assert(this->arch != NULL);
	assert(this->arch->declarations != NULL);
	assert(this->arch->region != NULL);
	assert(this->region != NULL);

	this->arch->declarations->push_back(hiddenActual);
	Symbol *sym = new Symbol(n, symbolType, NULL, *hiddenActual);
	this->arch->region->registerSymbol(sym);

	SimpleName *formalN = 
		NormalizeAssocLists::makeNameOfVal(
						&formal,
						this->region,
						loc);
	AssociationElement *elem = new AssociationElement(
						formalN, 
						NULL,
						loc);
	
	SimpleName *hiddenFormalN =
		NormalizeAssocLists::makeNameOfVal(
						hiddenActual,
						this->arch->region,
						loc);
	elem->hiddenFormal = hiddenFormalN;

	formalN->baseType = formal.subtypeIndic->baseType;
	hiddenFormalN->baseType = formal.subtypeIndic->baseType;
	mapList.push_back(elem);
	
	this->hiddenCounter++;
}

template <typename T>
T *
NormalizeAssocLists::createVal(
		std::string *name,
		enum ValDeclaration::Mode pMode,
		Expression *varInit,
		SubtypeIndication *si,
		Location loc
)
{
	assert(false);
	return NULL;
}

template <>
SignalDeclaration *
NormalizeAssocLists::createVal(
		std::string *name,
		enum ValDeclaration::Mode pMode,
		Expression *varInit,
		SubtypeIndication *si,
		Location loc
)
{
	return new SignalDeclaration(
					name,
					pMode,
					false,
					varInit,
					si,
					loc);
}

template <>
ConstantDeclaration *
NormalizeAssocLists::createVal(
		std::string *name,
		enum ValDeclaration::Mode pMode,
		Expression *varInit,
		SubtypeIndication *si,
		Location loc
)
{
		return new ConstantDeclaration(
					name,
					varInit,
					si,
					false, // FIXME?
					loc);
}


void
NormalizeAssocLists::addMissingPortAssocs(CompInstStat &node)
{
	assert(node.portMap != NULL);

	this->addMissingAssocs<SignalDeclaration>(
				node, 
				this->formals, 
				"port", 
				SYMBOL_SIGNAL, 
				*node.portMap);
}

void
NormalizeAssocLists::addMissingGenericAssocs(CompInstStat &node)
{
	assert(node.genericMap != NULL);

	this->addMissingAssocs<ConstantDeclaration>(
				node, 
				this->formals,
				"generic", 
				SYMBOL_VARIABLE, 
				*node.genericMap);
}


bool 
NormalizeAssocLists::sortPosPred(
	AssociationElement *l, 
	AssociationElement *r
) 
{
	return l->pos < r->pos;
}

SimpleName *
NormalizeAssocLists::makeNameOfVal(
	const ValDeclaration *val,
	const DeclarativeRegion *lookupRegion,
	Location loc
)
{
	std::list<Symbol *> formalCands = 
		lookupRegion->lookupLocal(*val->name);
	assert(formalCands.size() == 1);

	SimpleName *sn = 
		new SimpleName(new std::string(*val->name),
				formalCands,
				loc);
	sn->type = val->subtypeIndic;
	sn->baseType = val->subtypeIndic->baseType;
	return sn;
}
	

}; /* namespace ast */
