/* $Id$ 
 * CheckAccessMode: visitor to check if reading/writing data is permitted
 * via in,inout,out access modes.
 *
 * 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/CheckAccessMode.hpp"
#include "frontend/ast/SimpleName.hpp"
#include "frontend/ast/VarAssignStat.hpp"
#include "frontend/ast/SigAssignStat.hpp"
#include "frontend/ast/Subscript.hpp"
#include "frontend/ast/Slice.hpp"
#include "frontend/ast/Aggregate.hpp"
#include "frontend/ast/CompInstStat.hpp"
#include "frontend/ast/ConstInteger.hpp"
#include "frontend/ast/ConstReal.hpp"
#include "frontend/ast/ConstArray.hpp"
#include "frontend/ast/FunctionCall.hpp"
#include "frontend/ast/ProcCallStat.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"

namespace ast {

void
CheckAccessMode::visit(SimpleName &node)
{
	assert(node.candidates.size() >= 1);
	const Symbol *sym = node.candidates.front();

	switch (sym->type) {
	case SYMBOL_SIGNAL:
	case SYMBOL_PARAMETER:
	case SYMBOL_VARIABLE:
	case SYMBOL_PORT:
		break;

	default:
		// skip non variables/signals/parameters
		return;
	}

	ValDeclaration *decl = 
		dynamic_cast<ValDeclaration*>(
					node.getDeclaration());

	std::string *action = NULL;
	std::string *mode_s = NULL;

	if (this->isForeign) {
		decl->usage |= ValDeclaration::USAGE_FOREIGN;
	}

	switch (this->accessMode) {
	case ValDeclaration::MODE_IN:
		if (! this->isForeign) {
			decl->usage |= ValDeclaration::USAGE_READ;
		}

		switch (decl->mode) {
		case ValDeclaration::MODE_IN:
		case ValDeclaration::MODE_INOUT:
			// ok
			break;

		case ValDeclaration::MODE_OUT:
			// error
			action = new std::string("read from");
			mode_s = new std::string("OUT");
			break;
		}
		break;

	case ValDeclaration::MODE_OUT:
		if (! this->isForeign) {
			decl->usage |= ValDeclaration::USAGE_WRITE;
		}

		switch (decl->mode) {
		case ValDeclaration::MODE_OUT:
		case ValDeclaration::MODE_INOUT:
			// ok
			break;

		case ValDeclaration::MODE_IN:
			action = new std::string("write to");
			mode_s = new std::string("IN");
			break;

		}
		break;

	case ValDeclaration::MODE_INOUT:
		if (! this->isForeign) {
			decl->usage |= ValDeclaration::USAGE_READ 
			            |  ValDeclaration::USAGE_WRITE;
		} 

		switch (decl->mode) {
		case ValDeclaration::MODE_INOUT:
			// ok
			break;

		case ValDeclaration::MODE_IN:
			// error
			action = new std::string("use as INOUT");
			mode_s = new std::string("IN");
			break;

		case ValDeclaration::MODE_OUT:
			// error
			action = new std::string("use as INOUT");
			mode_s = new std::string("OUT");
			break;
		}
		break;
	}

	if (action == NULL) {
		return;
	}

	std::string msg = std::string("Trying to ");
	msg += *action;
	msg += " '";
	msg += util::MiscUtil::toString(node);
	msg += "' which is of mode ";
	msg += *mode_s;
	msg += ".";

	delete action;
	delete mode_s;

	CompileError *ce = new CompileError(node, msg);
	ErrorRegistry::addError(ce);
}

void
CheckAccessMode::visit(VarAssignStat &node)
{
	assert(node.source != NULL);
	assert(node.target != NULL);

	this->accessMode = ValDeclaration::MODE_OUT;
	node.target->accept(*this);
	this->accessMode = ValDeclaration::MODE_IN;
	node.source->accept(*this);
}

void
CheckAccessMode::visit(SigAssignStat &node)
{
	assert(node.target != NULL);
	assert(node.waveForm != NULL);

	this->accessMode = ValDeclaration::MODE_OUT;
	node.target->accept(*this);
	this->accessMode = ValDeclaration::MODE_IN;
	this->listTraverse(*node.waveForm);
}

void
CheckAccessMode::visit(Subscript &node)
{
	assert(node.indices != NULL);
	assert(node.source != NULL);

	enum ValDeclaration::Mode backup = this->accessMode;
	bool ifc = this->isForeign;
	this->isForeign = false;
	this->accessMode = ValDeclaration::MODE_IN;

	this->listTraverse(*node.indices);

	this->accessMode = backup;
	this->isForeign = ifc;
	node.source->accept(*this);
}

void
CheckAccessMode::visit(Slice &node)
{
	assert(node.source != NULL);
	assert(node.range != NULL);

	bool ifc = this->isForeign;
	this->isForeign = false;
	enum ValDeclaration::Mode backup = this->accessMode;
	this->accessMode = ValDeclaration::MODE_IN;

	node.range->accept(*this);

	this->isForeign = ifc;
	this->accessMode = backup;
	node.source->accept(*this);
}

void
CheckAccessMode::visit(Aggregate &node)
{
	enum ValDeclaration::Mode backup = this->accessMode;
	bool ifc = this->isForeign;

	assert(node.associations != NULL);

	for (std::list<ElementAssociation *>::iterator i = 
		node.associations->begin();
		i != node.associations->end(); i++) {

		if ((*i)->choices != NULL) {
			this->isForeign = false;
			this->accessMode = ValDeclaration::MODE_IN;

			this->listTraverse(*(*i)->choices);

			this->accessMode = backup;
			this->isForeign = ifc;
		}

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


void
CheckAccessMode::visit(ConstInteger &node)
{
	this->processConst(node);
}

void
CheckAccessMode::visit(ConstReal &node)
{
	this->processConst(node);
}

void
CheckAccessMode::visit(ConstArray &node)
{
	this->processConst(node);
}

void 
CheckAccessMode::processConst(const Expression &node) const
{
	switch (this->accessMode) {
	case ValDeclaration::MODE_IN:
		// ok
		return;

	case ValDeclaration::MODE_INOUT:
	case ValDeclaration::MODE_OUT:
		// error
		break;
	}

	// error case.
	CompileError *ce = 
		new CompileError(node, "Trying to access constant expression "
					"with OUT/INOUT mode.");
	ErrorRegistry::addError(ce);
}

void
CheckAccessMode::visit(FunctionCall &node)
{
	assert(node.definition != NULL);
	if (node.arguments != NULL) {
		this->processCall(*node.arguments, *node.definition);
	}

	switch (this->accessMode) {
	case ValDeclaration::MODE_IN:
		/* ok */
		return;

	case ValDeclaration::MODE_INOUT:
	case ValDeclaration::MODE_OUT:
		// error (return is a value, and must not be written to
		break;
	}

	CompileError *ce =
		new CompileError(node, "Trying to access the return value of "
					" a function call via OUT/INOUT.");
	ErrorRegistry::addError(ce);
}

void
CheckAccessMode::visit(ProcCallStat &node)
{
	assert(node.definition != NULL);
	if (node.arguments != NULL) {
		this->processCall(*node.arguments, *node.definition);
	}
}

void
CheckAccessMode::processCall(
	std::list<AssociationElement *> &args,
	const Callable &node
)
{
	bool ifc = this->isForeign;
	AttributeSpecification *spec = node.hasAttr("foreign");
	if (spec != NULL) {
		this->isForeign = true;
	} else {
		this->isForeign = false;
	}

	assert(node.arguments != NULL);

	enum ValDeclaration::Mode backup = this->accessMode;

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

		assert(a != node.arguments->end());
		// FIXME handle formals
		assert((*i)->formal == NULL);
		assert((*i)->actual != NULL); // FIXME open assocs!

		this->accessMode = (*a)->mode;
		(*i)->actual->accept(*this);
	}

	this->accessMode = backup;
	this->isForeign = ifc;
}

void
CheckAccessMode::visit(CompInstStat &node)
{
	assert(node.entity != NULL);

	// FIXME generic map

	if (node.portMap != NULL) {
		for (std::list<AssociationElement*>::iterator i = 
			node.portMap->begin(); i != node.portMap->end(); 
			i++) {

			assert((*i)->formal != NULL);
			ValDeclaration *port = 
				dynamic_cast<SignalDeclaration*>(
					(*i)->formal->getDeclaration());

			// also propagate info from inside to outside.
			// it's not necessary to propagate this info *back* 
			// to the inside of other entities on the same nesting
			// level, since it doesn't affect these.
			if ((port->usage & ValDeclaration::USAGE_FOREIGN) 
				!= 0) {
				this->isForeign = true;
			}
	
			this->accessMode = port->mode;
			if ((*i)->actual != NULL) {
				(*i)->actual->accept(*this);
			} else {
				assert((*i)->hiddenFormal != NULL);
				(*i)->hiddenFormal->accept(*this);
			}
			this->isForeign = false;
		}
	}

	this->accessMode = ValDeclaration::MODE_IN;
	this->isForeign = false;
}

void
CheckAccessMode::visit(Entity &node)
{
	AttributeSpecification *spec = node.hasAttr("foreign");
	if (spec != NULL) {
		assert(node.ports != NULL);

		for (std::list<SignalDeclaration*>::iterator i = 
			node.ports->begin();
			i != node.ports->end();
			i++) {

			(*i)->usage |= ValDeclaration::USAGE_FOREIGN;
		}
	}
	TopDownVisitor::visit(node);
}

void
CheckAccessMode::process(Callable &node)
{
	AttributeSpecification *spec = node.hasAttr("foreign");

	// arguments of foreign subprograms always have foreign usage
	if (spec != NULL) {
		if (node.arguments != NULL) {
			for (std::list<ValDeclaration *>::const_iterator i = 
				node.arguments->begin();
				i != node.arguments->end(); i++) {

				(*i)->usage = ValDeclaration::USAGE_FOREIGN;
			}
		}
	}
	TopDownVisitor::process(node);
}

}; /* namespace ast */
