/* $Id$
 *
 * DiscreteRange: AST node for a discrete range.
 *
 * Copyright (C) 2007-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/ast/DiscreteRange.hpp"
#include "frontend/ast/SubtypeIndication.hpp"
#include "frontend/ast/ConstInteger.hpp"
#include "frontend/ast/RangeConstraintType.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include <cassert>

namespace ast {

DiscreteRange::DiscreteRange(
	SubtypeIndication *si,
	Location loc
) :		Expression(loc),
		from(NULL),
		to(NULL),
		direction(DIRECTION_UP),
		rangeName(NULL)
{
	try {
		this->setFromAndTo(si);
	} catch(std::runtime_error) {
		std::string msg = "Not a constraint type found for range " 
				+ util::MiscUtil::toString(si);
		CompileError *ce = new CompileError(*this, msg);
		ErrorRegistry::addError(ce);
	}
}

void 
DiscreteRange::put(std::ostream &stream) const 
{
	stream << "RANGE ";
	if (this->rangeName) {
		stream << this->rangeName << ';';
		return;
	}

	stream << this->from;
	switch(this->direction) {
	case DIRECTION_DOWN:
		stream << " DOWNTO ";
		break;
	case DIRECTION_UP:
		stream << " TO ";
		break;
	}

	stream << this->to;
}

universal_integer 
DiscreteRange::getArraySize(void) const 
{
	assert(this->from != NULL);
	assert(this->to != NULL);

	ConstInteger *left = dynamic_cast<ConstInteger*>(this->from);
	ConstInteger *right = dynamic_cast<ConstInteger*>(this->to);

	assert(left != NULL);
	assert(right != NULL);
	//FIXME might do a second try with ConstantPropagation
	
	universal_integer sz = right->value - left->value + 1;
	switch(this->direction) {
	case DIRECTION_UP:
		break;
	case DIRECTION_DOWN:
		sz = left->value - right->value + 1;
		break;
	}

	if (sz < 0) {
		return 0;
	}
	return sz;
}

universal_integer 
DiscreteRange::getLowerBound(void) const 
{
	assert(this->from != NULL);
	assert(this->to != NULL);

	ConstInteger *left = dynamic_cast<ConstInteger*>(this->from);
	ConstInteger *right = dynamic_cast<ConstInteger*>(this->to);

	assert(left != NULL);
	assert(right != NULL);
	//FIXME might do a second try with ConstantPropagation
	
	switch(this->direction) {
	case DIRECTION_UP:
		return left->value;
		break;

	case DIRECTION_DOWN:
		return right->value;
		break;

	}

	// not reached.
	assert(false);
	return 0;
}

universal_integer 
DiscreteRange::getUpperBound(void) const 
{
	assert(this->from != NULL);
	assert(this->to != NULL);

	ConstInteger *left = dynamic_cast<ConstInteger*>(this->from);
	ConstInteger *right = dynamic_cast<ConstInteger*>(this->to);

	assert(left != NULL);
	assert(right != NULL);
	//FIXME might do a second try with ConstantPropagation
	
	switch(this->direction) {
	case DIRECTION_UP:
		return right->value;
		break;

	case DIRECTION_DOWN:
		return left->value;
		break;

	}

	// not reached.
	assert(false);
	return 0;
}

universal_integer
DiscreteRange::getLeftBound(void) const
{
	assert(this->from != NULL);

	ConstInteger *left = dynamic_cast<ConstInteger*>(this->from);
	assert(left != NULL);

	return left->value;
}

universal_integer
DiscreteRange::getRightBound(void) const
{
	assert(this->to != NULL);

	ConstInteger *right = dynamic_cast<ConstInteger*>(this->to);
	assert(right != NULL);

	return right->value;
}


void
DiscreteRange::setFromAndTo(const SubtypeIndication *si) 
	/* throw(std::runtime_error) */
{
	assert(si != NULL);
	if (si->constraint != NULL) {
		assert(si->constraint->from != NULL);
		assert(si->constraint->to != NULL);

		this->from = si->constraint->from;
		this->to = si->constraint->to;
		this->direction = si->constraint->direction;
		return;
	}

	/* not found */
	const SubtypeIndication *parent = 
		dynamic_cast<const SubtypeIndication*>(si->declaration);
	if (parent != NULL) {
		this->setFromAndTo(parent);
		return;
	}

	/* might be a RangeConstraintType */
	const RangeConstraintType *r = 
		dynamic_cast<const RangeConstraintType*>(si->declaration);
	if (r == NULL) {
		throw std::runtime_error("unconstraint");
	}

	assert(r->constraint != NULL);
	assert(r->constraint->from != NULL);
	assert(r->constraint->to != NULL);
	this->from = r->constraint->from;
	this->to = r->constraint->to;
	this->direction = r->constraint->direction;
}

}; /* namespace ast */
