/* $Id$ 
 * ConstantPropagation: propagate results of constant expressions.
 *
 * 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/ConstantPropagation.hpp"
#include "frontend/ast/FunctionCall.hpp"
#include "frontend/ast/ProcCallStat.hpp"
#include "frontend/ast/FunctionDeclaration.hpp"
#include "frontend/ast/ConstInteger.hpp"
#include "frontend/ast/ConstReal.hpp"
#include "frontend/ast/VarAssignStat.hpp"
#include "frontend/ast/SigAssignStat.hpp"
#include "frontend/ast/ElementAssociation.hpp"
#include "frontend/ast/ConditionedStat.hpp"
#include "frontend/ast/WhileLoopStat.hpp"
#include "frontend/ast/Subscript.hpp"
#include "frontend/ast/Slice.hpp"
#include "frontend/ast/TypeConversion.hpp"
#include "frontend/ast/SelectedName.hpp"
#include "frontend/ast/AttributeName.hpp"
#include "frontend/ast/WaitStat.hpp"
#include "frontend/ast/ReturnStat.hpp"
#include "frontend/ast/AssertStat.hpp"
#include "frontend/ast/CaseStat.hpp"
#include "frontend/ast/TemporaryName.hpp"
#include "frontend/ast/ConstantDeclaration.hpp"
#include "frontend/ast/ConstArray.hpp"
#include "frontend/ast/Aggregate.hpp"
#include "frontend/ast/UnconstrainedArrayType.hpp"
#include "frontend/ast/AttributeSpecification.hpp"
#include "frontend/visitor/ResolveAggregates.hpp"
#include "frontend/visitor/ResolveTypes.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include <vector>

namespace ast {

ConstantPropagation::ConstantPropagation() :	constValue(NULL)
{
}

void
ConstantPropagation::visit(ConstInteger &node)
{
	this->constValue = &node;
}

void
ConstantPropagation::visit(ConstReal &node)
{
	this->constValue = &node;
}

void
ConstantPropagation::visit(FunctionCall &node)
{
	assert(node.definition != NULL);
	FunctionDeclaration *decl = node.definition;

	bool isConst = true;
	std::list<Expression*> actuals;

	// FIXME this works only for positional arguments
	// FIXME this doesn't work for individual assoc
	for (std::list<AssociationElement*>::iterator i = 
		node.arguments->begin();
		i != node.arguments->end(); i++) {
		
		assert((*i)->formal == NULL);

		this->reset();
		(*i)->actual->accept(*this);
		isConst &= (this->constValue != NULL);
		
		this->setNode((*i)->actual);
		this->reset();
		actuals.push_back((*i)->actual);
	}

	if ((decl->isBuiltin) && (isConst)) {
		this->optimizeBuiltin(node, actuals);
		return;
	}

	this->reset();
}

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

	node.target->accept(*this);
	this->reset();

	node.source->accept(*this);
	this->setNode(node.source);
	this->reset();
}

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

	node.target->accept(*this);
	this->reset();
	this->listTraverse(*node.waveForm);
}

void
ConstantPropagation::visit(WaveFormElem &node)
{
	assert(node.value != NULL);
	node.value->accept(*this);
	this->setNode(node.value);
	this->reset();

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

	node.delay->accept(*this);
	this->setNode(node.delay);
	this->reset();
}

void
ConstantPropagation::visit(WhileLoopStat &node)
{
	assert(node.condition != NULL);
	node.condition->accept(*this);
	bool r = this->setNode(node.condition);
	this->reset();

	if (r) {
		ConstInteger *ci = 
			dynamic_cast<ConstInteger*>(node.condition);
		assert(ci != NULL);
		if (ci->value == 0) {
			// condition is false, loop will never execute.
			CompileError *ce = 
				new CompileError(
					node, 
					"While-loop will never be "
					"executed since condition is never "
					"met.");
			ErrorRegistry::addWarning(ce);
			util::MiscUtil::lterminate(node.loopStats);
			node.loopStats = new std::list<SeqStat*>();
		}
	}

	assert(node.loopStats != NULL);
	this->listTraverse(*node.loopStats);
}

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

	// TODO can even optimize the subscription itself, if 
	//      indices + source are constant

	node.source->accept(*this);
	bool r = this->setNode(node.source);
	this->reset();

	r &= this->listOptimize(node.indices);
	if (! r) {
		return;
	}

	ConstArray *ca = dynamic_cast<ConstArray*>(node.source);
	if (ca == NULL) {
		return;
	}

	// if it's a ConstArray, then (currently) there's exactly one
	// index.
	assert(node.indices != NULL);
	assert(node.indices->size() == 1);
	const Expression *idxE = node.indices->front();
	const ConstInteger *ci = dynamic_cast<const ConstInteger*>(idxE);
	assert(ci != NULL);

	// FIXME bounds/direction can diverge from [0 to len]!
	assert(ca->elements != NULL);
	assert(0 <= ci->value);
	const size_t idx = static_cast<size_t>(ci->value);
	assert(idx < ca->elements->size());
	
	ConstInteger *result = (*ca->elements)[idx];
	this->constValue = result;
}

void
ConstantPropagation::visit(Slice &node)
{
	assert(node.source != NULL);
	node.source->accept(*this);
	bool sc = this->setNode(node.source);

	/* FIXME throw an error instead of assert() */
	assert(! sc); /* constant source cannot get sliced */
	this->reset();

	node.range->accept(*this);
	this->reset(); /* FIXME not needed, once DiscreteRange is there */
}

void
ConstantPropagation::visit(TypeConversion &node)
{
	assert(node.source != NULL);
	node.source->accept(*this);

	// TODO this actually adds a hard barrier at a TypeConversion node.
	//      see sample implementation below how it should get circumvented.
	//      The sample implementation however doesn't solve int<->real
	//      conversions.
	this->setNode(node.source);
	this->reset();

#if 0 /* FIXME must think over this again */
	if (this->constValue != NULL) {
		assert(node.baseType == this->constValue.baseType);
		this->constValue.type = 
			new SubtypeIndication(node.targetType, node.location);
	}
#endif
}

void
ConstantPropagation::visit(SimpleName &node)
{
	this->reset();

	// find out if it is a constant.
	if (node.candidates.size() != 1) {
		return;
	}

	Symbol *sym = node.candidates.front();
	switch(sym->type) {
	case SYMBOL_VARIABLE:
		break;

	default:
		/* cannot be a constant */
		return;
	}

	ConstantDeclaration *cd = 
		dynamic_cast<ConstantDeclaration*>(&sym->declaration);
	if (cd == NULL) {
		/* not a constant */
		return;
	}

	if (! cd->fixedValue) {
		return;
	}

	assert(cd->init != NULL); // parser should have taken care for this.
	this->constValue = cd->init;
}

void
ConstantPropagation::visit(SelectedName &node)
{
	node.prefix->accept(*this);
	bool pc = this->setNode(node.prefix);
	/* FIXME report error */
	assert(! pc); // const prefix doesn't make sense
	this->reset();
}

void
ConstantPropagation::visit(AttributeName &node)
{
	node.prefix->accept(*this);
	bool pc = this->setNode(node.prefix);
	/* FIXME report error */
	assert(! pc); // const prefix doesn't make sense
	this->reset();
}

void
ConstantPropagation::visit(TemporaryName &node)
{
	assert(node.prefix != NULL);
	node.prefix->accept(*this);
	this->setNode(node.prefix);
	this->reset();
}

void
ConstantPropagation::visit(WaitStat &node)
{
	this->process(node);
	if (node.timeout != NULL) {
		node.timeout->accept(*this);
		this->setNode(node.timeout);
		this->reset();
	}

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

	for (std::list<Name*>::iterator i = node.sensitivities->begin();
		i != node.sensitivities->end(); i++) {
		
		(*i)->accept(*this);
		// don't set any node here
		this->reset();
	}
}

void
ConstantPropagation::visit(ReturnStat &node)
{
	if (node.result != NULL) {
		node.result->accept(*this);
		this->setNode(node.result);
		this->reset();
	}
}

void
ConstantPropagation::visit(AssertStat &node)
{
	this->process(node);
	if (node.report != NULL) {
		node.report->accept(*this);
		this->setNode(node.report);
		this->reset();
	}

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

void
ConstantPropagation::visit(DiscreteRange &node)
{
	if (node.from != NULL) {
		node.from->accept(*this);
		this->setNode(node.from);
		this->reset();

		assert(node.to != NULL);
		node.to->accept(*this);
		this->setNode(node.to);
		this->reset();

		return;
	}

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

void
ConstantPropagation::visit(CaseStat &node)
{
	assert(node.select != NULL);
	node.select->accept(*this);
	this->setNode(node.select);
	this->reset();
	assert(node.alternatives != NULL);
	this->listTraverse(*node.alternatives);
}

void
ConstantPropagation::visit(CaseAlternative &node)
{
	assert(node.isVals != NULL);
	this->listOptimize(node.isVals);

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

void
ConstantPropagation::visit(Aggregate &node)
{
	bool isConstArray = true;
	assert(node.associations != NULL);

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

		assert((*i)->actual != NULL);
		(*i)->actual->accept(*this);
		bool r = this->setNode((*i)->actual);
		this->reset();

		isConstArray &= r;
	}


	if (! isConstArray) {
		return;
	}

	// array is const, check ranges first.
	std::list<DiscreteRange*> idcs = std::list<DiscreteRange*>();
	const UnconstrainedArrayType *bt = 
		ResolveTypes::pickupIndexConstraint(node.type, idcs);
	DiscreteRange *neededIndex;

	if (idcs.size() == 0) {
		neededIndex = ResolveTypes::determineIndexRangeAgg(
						bt, *node.associations);
		assert(neededIndex != NULL);
	} else {
		neededIndex = idcs.front();
	}

	ResolveAggregates ra = ResolveAggregates(*neededIndex);
	node.accept(ra);

	std::vector<ConstInteger *> elems = std::vector<ConstInteger *>();
	enum DiscreteRange::Direction d = neededIndex->direction;

	for (universal_integer i = neededIndex->getLeftBound(); 
		(i * d) <= (neededIndex->getRightBound() * d);
		i += d) {

		ElementAssociation *assoc = this->findAggregateAssoc(i, node);
		if (assoc == NULL) {
			/* error -> return early. */
			return;
		}
		ConstInteger *ci = dynamic_cast<ConstInteger*>(assoc->actual);
		if (ci == NULL) {
			return;
		}

		elems.push_back(ci);
	}

	ConstArray *ca = new ConstArray(
				new std::vector<ConstInteger *>(elems),
				node.location);

	
	ca->type = ConstantPropagation::makeCAType(node.type, elems.size());

	this->constValue = ca;
}

void
ConstantPropagation::visit(AttributeSpecification &node)
{
	assert(node.init != NULL);
	node.init->accept(*this);
	bool r = this->setNode(node.init);
	if (! r) {
		// FIXME this is only true for *some* occurances,
		// cf. LRM 5.1
		CompileError *ce = 
			new CompileError(*node.init,
					"Non static initilizers for "
					"attribte specifications are "
					"currently not supported.");
		ErrorRegistry::addError(ce);
	}
	this->reset();
}

void
ConstantPropagation::optimizeBuiltin(
	FunctionCall &node, 
	std::list<Expression*> args
)
{
	assert(node.definition != NULL);
	if (! node.definition->isBuiltin) {
		return;
	}

	if (node.definition->builtin == NULL) {
		std::cerr << "WARNING: no builtin defined for " 
			<< node.definition->location 
			<< ": " << node.definition << std::endl;
		return;
	}

	BuiltinFunction *bf = node.definition->builtin;
	this->constValue = bf->execute(args);
}

bool
ConstantPropagation::setNode(Expression *&node) const
{
	if (this->constValue == NULL) {
		return false;
	}

	if (this->constValue == node) {
		return true;
	}

	node = this->constValue;
	return true;
}

void
ConstantPropagation::visit(AssociationElement &node)
{
	this->reset();
	assert(node.actual != NULL);
	node.actual->accept(*this);
	this->setNode(node.actual);
	this->reset();
}

void
ConstantPropagation::process(AstNode &node)
{
	this->reset();
}

void
ConstantPropagation::process(ValDeclaration &node)
{
	if (node.init != NULL) {
		node.init->accept(*this);
		if (this->setNode(node.init)) {
			// TODO propagate
		}
		this->reset();
	}

	/* do some out of order traversal */
	assert(node.subtypeIndic != NULL);
	node.subtypeIndic->accept(*this);
}

void
ConstantPropagation::process(ConditionedStat &node)
{
	if (node.condition != NULL) {
		node.condition->accept(*this);
		this->setNode(node.condition);
		this->reset();

		//FIXME should have some effect on the condition.
	}
}

void
ConstantPropagation::reset(void)
{
	this->constValue = NULL;
}

bool
ConstantPropagation::listOptimize(std::list<Expression*> *l)
{
	bool ret = true;

	if (l == NULL) {
		return false;
	}

	for (std::list<Expression*>::iterator i = l->begin(); i != l->end();
		i++) {

		(*i)->accept(*this);
		ret &= this->setNode(*i);
		this->reset();
	}

	return ret;
}

ElementAssociation *
ConstantPropagation::findAggregateAssoc(
	universal_integer index,
	Aggregate &node
) const
{
	for (std::list<ElementAssociation*>::iterator i = 
		node.associations->begin();
		i != node.associations->end();
		i++) {

		if ((*i)->range == NULL) {
			return NULL;
		}

		if ((*i)->range->contains(index)) {
			return *i;
		}
	}

	return NULL;
}

TypeDeclaration *
ConstantPropagation::makeCAType(TypeDeclaration *haveType, size_t numElems)
{
	std::list<DiscreteRange *> idxC = std::list<DiscreteRange *>();
	const UnconstrainedArrayType *ua = 
		ResolveTypes::pickupIndexConstraint(haveType, idxC);

	// perform subtype conversion, if there is a constraint target
	// type present.
	if (idxC.size() > 0) {
		// subtype conversion takes place, use inferred subtype.
		return haveType;
	}

	TypeDeclaration *bt = const_cast<UnconstrainedArrayType *>(ua);

	ConstInteger *lb = new ConstInteger(0, haveType->location);
	ConstInteger *ub = 
		new ConstInteger(static_cast<universal_integer>(numElems) - 1,
				haveType->location);
	
	DiscreteRange *dr = new DiscreteRange(lb, ub, 
					DiscreteRange::DIRECTION_UP,
					haveType->location);
	SubtypeIndication *si = 
		new SubtypeIndication(bt, haveType->location);
	si->indexConstraint = new std::list<DiscreteRange *>();
	si->indexConstraint->push_back(dr);
	return si;
}

}; /* namespace ast */
