/* $Id$
 *
 * Resolve ranges of positional and named aggregates.
 *
 * 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/ResolveAggregates.hpp"
#include <cassert>
#include "frontend/ast/Aggregate.hpp"
#include "frontend/ast/Others.hpp"
#include "frontend/ast/ConstInteger.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"

namespace ast {

ResolveAggregates::ResolveAggregates(
	const DiscreteRange &arrayRange
) :
	haveOthers(false),
	named(false),
	positional(false),
	othersErrorReported(false),
	currentRange(NULL),
	sequencer(arrayRange)
{
}

ResolveAggregates::~ResolveAggregates()
{
}

void
ResolveAggregates::visit(Aggregate &node)
{
	assert(node.associations != NULL);

	this->haveOthers = false;
	this->named = false;
	this->positional = false;

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

		(*i)->accept(*this);
	}

	if (this->named && this->positional) {
		CompileError *ce = 
			new CompileError(node,
				"Mixing named with positional association.");
		ErrorRegistry::addError(ce);
		return;
	}

	if (! this->sequencer.rs.empty()) {
		std::string s = 
			std::string("Indices not covered by aggregate: ");
		s += util::MiscUtil::toString(this->sequencer.rs);

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

void
ResolveAggregates::visit(ElementAssociation &node)
{
	if (this->haveOthers) {
		// element after an "others" element not allowed.
		if (! this->othersErrorReported) {
			this->othersErrorReported = true;
			CompileError *ce = 
				new CompileError(node,
					"Associaion after Others.");
			ErrorRegistry::addError(ce);
				
		}
		return;
	}

	if (node.choices == NULL) {
		// positional association
		this->positional = true;

		if (this->sequencer.rs.empty()) {
			CompileError *ce = 
				new CompileError(node, 
					"Index out of range.");
			ErrorRegistry::addError(ce);
			return;
		}

		universal_integer val = this->sequencer.getNext();
		node.range = new RangeSet(val, val);

		return;
	}

	// node.choices != NULL: either named or others
	this->listTraverse(*node.choices);
	if (this->haveOthers) {
		if (node.choices->size() > 1) {
			// others mixed with elements.
			CompileError *ce =
				new CompileError(node, 
					"Others in wrong place.");
			ErrorRegistry::addError(ce);
			this->currentRange = NULL;
			return;
		}

		// only one others element. set the Range by it.
		node.range = this->sequencer.getRemainder();
		// all elements have been consumed by others. clear sequencer
		this->sequencer.rs.clear();
		return;
	}

	this->named = true;
	node.range = this->currentRange;
	this->currentRange = NULL;
}

void
ResolveAggregates::visit(Others &node)
{
	if (this->haveOthers) {
		CompileError *ce = 
			new ast::CompileError(node, 
					"Duplicate others choice.");
		ErrorRegistry::addError(ce);
	}

	this->haveOthers = true;
}

void
ResolveAggregates::visit(ConstInteger &node)
{
	if (this->currentRange == NULL) {
		this->currentRange = new RangeSet(node.value, node.value);
	} else {
		this->currentRange->plus(node.value, node.value);
	}

	bool ret = this->sequencer.rs.minus(node.value, node.value);
	if (! ret) {
		CompileError *ce = new CompileError(node, 
				"Aggregate range doesn't fit array range.");
		ErrorRegistry::addError(ce);
	}
}

void
ResolveAggregates::visit(DiscreteRange &node)
{
	if (this->currentRange == NULL) {
		this->currentRange = new RangeSet(node);
	} else {
		this->currentRange->plus(node);
	}

	bool ret = this->sequencer.rs.minus(node);
	if (! ret) {
		CompileError *ce = new CompileError(node, 
				"Aggregate index doesn't fit array range.");
		ErrorRegistry::addError(ce);
	}
}

void
ResolveAggregates::process(AstNode &node)
{
	CompileError *ce = 
		new CompileError(node, 
			"Aggregate range/index not locally static.");
	ErrorRegistry::addError(ce);
}

/* ****************** nested class Sequencer **************** */
ResolveAggregates::Sequencer::Sequencer(
	const DiscreteRange &byRange
) : 
	rs(byRange),
	direction(byRange.direction)
{
}

universal_integer
ResolveAggregates::Sequencer::getNext(void)
{
	universal_integer ret = 0;

	switch (this->direction) {
	case DiscreteRange::DIRECTION_UP:
		ret = this->rs.getLowerBound();
		break;

	case DiscreteRange::DIRECTION_DOWN:
		ret = this->rs.getUpperBound();
		break;

	}

	bool b = this->rs.minus(ret, ret);
	assert(b);

	return ret;
}

RangeSet *
ResolveAggregates::Sequencer::getRemainder(void) const
{
	return new RangeSet(this->rs);
}

}; /* namespace ast */
