/* $Id$ 
 *
 * Gather implicitly needed variables (e.g. drivers).
 *
 * 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/GatherImplicits.hpp"
#include "frontend/ast/SigAssignStat.hpp"
#include "frontend/ast/VarAssignStat.hpp"
#include "frontend/ast/SimpleName.hpp"
#include "frontend/ast/SignalDeclaration.hpp"
#include "frontend/ast/Process.hpp"
#include "frontend/ast/Architecture.hpp"
#include "frontend/ast/ProcCallStat.hpp"
#include "frontend/ast/FunctionCall.hpp"
#include "frontend/ast/AssociationElement.hpp"
#include "frontend/ast/ProcedureDeclaration.hpp"
#include "frontend/ast/ValDeclaration.hpp"
#include "frontend/ast/LibUnit.hpp"
#include "frontend/visitor/ResolveTypes.hpp"
#include "frontend/reporting/CompileError.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"

// subprogram: out, inout / signal -> driver to process
// signal gets assigned -> driver to process
//
// TODO it might be a more elegant idea to store ConstArrays 
//      as constants (and Aggregates as well).

namespace ast {

GatherImplicits::GatherImplicits() : mode(VMODE_SOURCE), driverCounter(0),
					uhfCounter(0)
{
}

void
GatherImplicits::visit(SigAssignStat &node)
{
	this->mode = VMODE_TARGET_SIGNAL;
	node.target->accept(*this);
	this->mode = VMODE_SOURCE;
	assert(node.waveForm);
	this->listTraverse(*node.waveForm);
}

void
GatherImplicits::visit(VarAssignStat &node)
{
	this->mode = VMODE_TARGET_VAR;
	node.target->accept(*this);
	this->mode = VMODE_SOURCE;
	node.source->accept(*this);
}

void
GatherImplicits::visit(SimpleName &node)
{

	switch(this->mode) {
	case VMODE_TARGET_SIGNAL: {
		SignalDeclaration *sig 
			= dynamic_cast<SignalDeclaration*>(
						node.getDeclaration());
		if (sig == NULL) {
			CompileError *err = new CompileError(node, 
				"Target of signal assignment not a signal.");
			ErrorRegistry::addError(err);
			return;
		}
		node.driver = this->registerDriver(*sig, node);
		break;
	    }
	case VMODE_TARGET_VAR: {
		// only check, if it is in fact a VarDeclaration.
		const VarDeclaration *var = 
			dynamic_cast<const VarDeclaration*>(
						node.getDeclaration());
		if (var == NULL) {
			CompileError *err = new CompileError(node,
				"Target of a variable assignment not a "
				"variable.");
			ErrorRegistry::addError(err);
		}
		break;
	    }
	case VMODE_SOURCE:
		// nothing to check
		break;
	}
}

void
GatherImplicits::visit(Process &node)
{
	assert(this->drivers.empty());
	formalMapT formalBackup = this->formals;
	this->formals.clear();

	assert(node.declarations != NULL);

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

	assert(node.drivers.empty());

	for (std::map<const SignalDeclaration*, Driver*>::const_iterator i = 
		this->drivers.begin(); i != this->drivers.end(); i++) {
		
		node.drivers.push_back(i->second);
	}

	for (formalMapT::const_iterator i = this->formals.begin(); 
		i != this->formals.end(); i++) {

		node.declarations->push_back(i->second.first);
	}

	this->formals = formalBackup;
	this->drivers.clear();
}

void
GatherImplicits::visit(FunctionCall &node)
{
	// type resolution must have happened before
	assert(node.definition != NULL);

	switch(this->mode) {
	case VMODE_TARGET_SIGNAL:
	case VMODE_TARGET_VAR: {
		CompileError *err = 
			new CompileError(node, 
					"Invalid lvalue in assignment");
		ErrorRegistry::addError(err);
		return;
	    }
	default:
		// nothing to do
		break;
	}

	if (node.arguments == NULL) {
		return;
	}

	if (node.definition->arguments == NULL) {
		return;
	}

	std::list<ValDeclaration *>::const_iterator i = 
				node.definition->arguments->begin();

	for (std::list<AssociationElement*>::const_iterator j = 
		node.arguments->begin(); j != node.arguments->end();
		i++, j++) {

		assert(i != node.definition->arguments->end());

		switch ((*i)->storageClass) {
		case ValDeclaration::OBJ_CLASS_CONSTANT:
			// need hidden formals for composite types only.
			assert((*i)->subtypeIndic != NULL);
			switch ((*i)->subtypeIndic->baseType) {
			case BASE_TYPE_ARRAY:
			case BASE_TYPE_RECORD:
				(*j)->hiddenFormal = 
					this->registerFormal(*i, 
							(*j)->actual->type);
				break;

			default:
				break;
			}
			break;

		default:
			break;
		}
	}
}

void
GatherImplicits::visit(ProcCallStat &node)
{
	assert(node.definition != NULL);

	if (node.definition->arguments == NULL) {
		return;
	}

	// TODO register implicit formals as necessary.
	if (node.arguments == NULL) {
		return;
	}

	assert(node.definition->arguments != NULL);
	std::list<ValDeclaration*>::const_iterator f = 
		node.definition->arguments->begin();

	// pickup drivers from actuals
	for (std::list<AssociationElement*>::iterator i = 
		node.arguments->begin();
		i != node.arguments->end(); i++, f++) {

		this->pickupImplicits(**f, **i);
	}
}

void
GatherImplicits::pickupImplicits(
	ValDeclaration &formalDecl,
	AssociationElement &assoc
)
{
	switch (formalDecl.mode) {
	case ValDeclaration::MODE_OUT:
	case ValDeclaration::MODE_INOUT:
		switch (formalDecl.storageClass) {
		case ValDeclaration::OBJ_CLASS_SIGNAL:
			this->mode = VMODE_TARGET_SIGNAL;
			break;

		case ValDeclaration::OBJ_CLASS_VARIABLE:
			this->mode = VMODE_TARGET_VAR;
			break;

		default:
			break;
		}
	default:
		break;
	}

	switch (formalDecl.storageClass) {
	case ValDeclaration::OBJ_CLASS_CONSTANT:
		assert(formalDecl.subtypeIndic != NULL);

		// already handled by NormalizeAssocs, if there's no
		// association in the first place.
		// (then an association must have been created anyway,
		// together with a hidden formal).
		if (assoc.hiddenFormal != NULL) {
			this->mode = VMODE_SOURCE;
			return;
		}

		switch (formalDecl.subtypeIndic->baseType) {
		case BASE_TYPE_RECORD:
			assoc.hiddenFormal = 
				this->registerFormal(&formalDecl, 
						assoc.actual->type);
			break;

		case BASE_TYPE_ARRAY: {
			// if the actual is an unconstraint array,
			// we have a pointer to storage space already.
			// Hence we'll only need a hiddenFormal, if 
			// there's a constraint array.
			bool isConstraint = 
				ResolveTypes::isConstraintArray(
							assoc.actual->type);

			if (! isConstraint) {
				break;
			}
			assoc.hiddenFormal = 
				this->registerFormal(&formalDecl, 
						assoc.actual->type);
			break;
		    }
		default:
			break;
		}
		break;

	default:
		break;
	}

	assoc.actual->accept(*this);
	this->mode = VMODE_SOURCE;
}

void
GatherImplicits::visit(FunctionDeclaration &node)
{
	this->processCallable(node);
}

void
GatherImplicits::visit(ProcedureDeclaration &node)
{
	this->processCallable(node);
}

void
GatherImplicits::processCallable(Callable &node)
{
	assert(this->drivers.empty());
	formalMapT formalBackup = this->formals;
	this->formals.clear();

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

	// FIXME that's still ugly and will fail for procedures that 
	//       are defined in a process and directly modify signals.

	if (node.arguments != NULL) {
		for (std::list<ValDeclaration*>::const_iterator i = 
			node.arguments->begin();
			i != node.arguments->end(); i++) {

			switch ((*i)->mode) {
			case ValDeclaration::MODE_IN:
				break;

			case ValDeclaration::MODE_OUT:
			case ValDeclaration::MODE_INOUT:
				switch ((*i)->storageClass) {
				case ValDeclaration::OBJ_CLASS_SIGNAL:
					this->registerDriver(**i,
							SYMBOL_PARAMETER);
					break;

				default:
					break;
				}
				break;
			}
		}
	}


	for (std::map<const SignalDeclaration*, Driver*>::const_iterator i = 
		this->drivers.begin(); i != this->drivers.end(); i++) {
		node.drivers.push_back(i->second);
	}

	// FIXME do s.th. with formals!

	this->formals = formalBackup;
	this->drivers.clear();
}

void
GatherImplicits::process(LibUnit &node)
{
	if (node.declarations != NULL) {
		this->listTraverse(*node.declarations);
	}
}

void
GatherImplicits::visit(Architecture &node)
{
	assert(this->drivers.empty());
	assert(this->formals.empty());

	this->process(node);

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

	for (formalMapT::const_iterator i = this->formals.begin();
		i != this->formals.end(); i++) {

		node.declarations->push_back(i->second.first);
	}

	this->drivers.clear();
	this->formals.clear();
}

void
GatherImplicits::visit(CompInstStat &node)
{
	// at this point, association lists have already been normalized
	
	assert(node.genericMap != NULL);
	assert(node.entity != NULL);
	assert(node.entity->generics != NULL);

	std::list<ConstantDeclaration *>::const_iterator g = 
					node.entity->generics->begin();

	for (std::list<AssociationElement *>::iterator i = 
		node.genericMap->begin();
		i != node.genericMap->end();
		i++, g++) {

		this->pickupImplicits(**g, **i);
	}
		
}

Driver *
GatherImplicits::registerDriver(const SignalDeclaration &decl, SimpleName &n)
{

	std::map<const SignalDeclaration*, Driver*>::const_iterator i = 
		this->drivers.find(&decl);
	
	if (i != this->drivers.end()) {
		// driver registered already
		return i->second;
	}

	// driver not yet present.
	Driver *d = new Driver(decl, this->driverCounter, &n);
	this->driverCounter++;
	this->drivers[&decl] = d;
	return d;
}

void
GatherImplicits::registerDriver(ValDeclaration &decl, enum symType t)
{
	// must not throw an exception, otherwise logic error
	SignalDeclaration &d = dynamic_cast<SignalDeclaration&>(decl);

	std::map<const SignalDeclaration*, Driver*>::const_iterator i = 
		this->drivers.find(&d);
	
	if (i != this->drivers.end()) {
		// driver registered already
		return;
	}

	Symbol *sym = new Symbol(d.name, t, NULL, d);
	std::list<Symbol *> cands = std::list<Symbol*>();
	cands.push_back(sym);
	SimpleName *sn = new SimpleName(new std::string(*d.name), cands, 
					d.location);
	this->registerDriver(d, *sn);
}

SimpleName *
GatherImplicits::registerFormal(
	const ValDeclaration *formal,
	TypeDeclaration *actualType
)
{
	formalMapT::const_iterator i = this->formals.find(formal);
	if (i != this->formals.end()) {
		return i->second.second;
	}

	std::string *name = new std::string("__");
	*name += *formal->name;
	*name += "__hidden_formal__";

	SubtypeIndication *si;
	bool isUnconstraint = formal->isUnconstraint();
	if (isUnconstraint) {
		si = dynamic_cast<SubtypeIndication*>(actualType);
		assert(si != NULL);
		*name += util::MiscUtil::toString(this->uhfCounter);
		this->uhfCounter++;
	} else {
		si = formal->subtypeIndic;
	}

	// generate a ValDeclaration
	VarDeclaration *hidden = 
		new VarDeclaration(
					formal->mode, 
					name, 
					NULL, 
					si,
					Location("__hidden_formal__"));
	Symbol *sym = new Symbol(name, SYMBOL_VARIABLE, NULL, *hidden);
	std::list<Symbol *> cands = std::list<Symbol*>();
	cands.push_back(sym);
	
	SimpleName *ref = new SimpleName(new std::string(*name), 
					cands,
					Location("__builtin__"));

	if (isUnconstraint) {
		// need seperate entries for each actual of a 
		// unconstraint formal, since the storage size
		// can differs.
		this->formals[hidden] =
			std::pair<ValDeclaration*, SimpleName*>(hidden, ref);
	} else {
		// but only one entry for a constraint formal, since
		// the storage size is fixed.
		this->formals[formal] = 
			std::pair<ValDeclaration*, SimpleName*>(hidden, ref);
	}
	return ref;
}


}; /* namespace ast */
