/* $Id$ 
 * ResolveTypes: perform type analysis.
 *
 * 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.
 */

#ifndef __RESOLVE_TYPES_HPP_INCLUDED
#define __RESOLVE_TYPES_HPP_INCLUDED

#include <list>
#include <set>
#include "frontend/visitor/TopDownVisitor.hpp"
#include "frontend/ast/ValDeclaration.hpp"
#include "frontend/ast/LibUnit.hpp"
#include "frontend/ast/ConcurrentStat.hpp"
#include "frontend/ast/RecordTypeElement.hpp"
#include "frontend/ast/PhysicalTypeUnit.hpp"
#include "frontend/ast/EnumerationType.hpp"
#include "frontend/misc/SymbolTable.hpp"
#include "frontend/ast/NodeFactory.hpp"

namespace ast {

//! Resolve all types.
/** This visitor will resolve all types from the set of candidates
 *  evaluated by the SymbolTable.
 *
 *  Notes:
 *  * implicit conversions: 
 *  	universal_integer -> any integer type.
 *  	universal_real -> any floating point type.
 *  	array definition: index_constraint -> "integer", in case both bounds
 *  	                  are universal_integer and literals or attributes.
 *
 *      function calls: parameter must match subtype!
 */
class ResolveTypes : public TopDownVisitor {
public:
	//! c'tor
	/** @param symTab symbol table instance.
	 */
	ResolveTypes(SymbolTable &symTab) : symbolTable(symTab) {}

private:
	/** Visit a CompInstStat
	 *  @param node CompInstStat node that gets visited.
	 */
	virtual void visit(CompInstStat &node);

	/** Visit an AssociationElement
	 *  @param node AssociationElement node that gets visited.
	 */
	virtual void visit(AssociationElement &node);

	/** Visit an UnconstrainedArrayType
	 *  @param node UnconstrainedArrayType node that gets visited.
	 */
	virtual void visit(UnconstrainedArrayType &node);

	/** Visit a DiscreteRange
	 *  @param node DiscreteRange node that gets visited.
	 */
	virtual void visit(DiscreteRange &node);

	/** Visit a VarAssignStat
	 *  @param node VarAssignStat node that gets visited.
	 */
	virtual void visit(VarAssignStat &node);

	/** Visit a SigAssignStat
	 *  @param node SigAssignStat node that gets visited.
	 */
	virtual void visit(SigAssignStat &node);

	/** Visit a Subscript node.
	 *  @param node Subscript node that gets visited.
	 */
	virtual void visit(Subscript &node);

	/** Visit a SimpleName node.
	 *  @param node SimpleName node that gets visited.
	 */
	virtual void visit(SimpleName &node);

	/** Visit a SelectedName node.
	 *  @param node SelectedName node that gets visited.
	 */
	virtual void visit(SelectedName &node);

	/** Visit an AttributeName node.
	 *  @param node AttributeName node that gets visited.
	 */
	virtual void visit(AttributeName &node);

	/** Visit a FunctionCall node.
	 *  @param node FunctionCall node that gets visited.
	 */
	virtual void visit(FunctionCall &node);

	/** Visit a ConstInteger node.
	 *  @param node ConstInteger node that gets visited.
	 */
	virtual void visit(ConstInteger &node);

	/** Visit a ConstReal node.
	 *  @param node ConstReal node that gets visited.
	 */
	virtual void visit(ConstReal &node);

	/** Visit an Aggregate node.
	 *  @param node Aggregate node that gets visited.
	 */
	virtual void visit(Aggregate &node);

	/** Visit an ElementAssociation node.
	 *  @param node ElementAssociation node that gets visited.
	 */
	virtual void visit(ElementAssociation &node);

	/** Visit an Others node.
	 *  @param node Others node that gets visited.
	 */
	virtual void visit(Others &node);

	/** Visit a Slice node.
	 *  @param node Slice node that gets visited.
	 */
	virtual void visit(Slice &node);

	/** Visit a TemporaryName node.
	 *  @param node TemporaryName node that gets visited.
	 */
	virtual void visit(TemporaryName &node);

	/** visit a ReturnStat
         *  @param node node that get's visited.
         */
	virtual void visit(ReturnStat &node);

	/** visit a RangeConstraintType
         *  @param node node that get's visited.
         */
	virtual void visit(RangeConstraintType &node);

	/** visit a PhysicalType
         *  @param node node that get's visited.
         */
	virtual void visit(PhysicalType &node);

	/** visit a RecordType
         *  @param node node that get's visited.
         */
	virtual void visit(RecordType &node);

	/** visit a FunctionDeclaration
         *  @param node node that get's visited.
         */
	virtual void visit(FunctionDeclaration &node);

	/** visit a ProcCallStat
         *  @param node node that get's visited.
         */
	virtual void visit(ProcCallStat &node);

	/** visit a SubtypeIndication
         *  @param node node that get's visited.
         */
	virtual void visit(SubtypeIndication &node);

	/** Visit a ForLoopStat
	 *  @param node ForLoopStat node that get's visited.
	 */
	virtual void visit(ForLoopStat &node);

	/** Visit a WhileLoopStat
	 *  @param node WhileLoopStat node that get's visited.
	 */
	virtual void visit(WhileLoopStat &node);

	/** Visit an AssertStat
	 *  @param node AssertStat node that get's visited.
	 */
	virtual void visit(AssertStat &node);

	/** Visit a CaseStat
	 *  @param node CaseStat node that get's visited.
	 */
	virtual void visit(CaseStat &node);

	/** Visit a WaitStat
	 *  @param node WaitStat node that get's visited.
	 */
	virtual void visit(WaitStat &node);

	/** Visit a Process node.
	 *  @param node Process node that get's visited.
	 */
	virtual void visit(Process& node);

	/** Visit a WaveFormElem
	 *  @param node WaveFormElem node that get's visited.
	 */
	virtual void visit(WaveFormElem &node);

	/** Visit an AttributeSpecification node.
	 *  @param node AttributeSpecification node that gets visited.
	 */
	virtual void visit(AttributeSpecification &node);

public:
	/** do both TypeDeclarations have the same base type?
	 *  @param t1 first TypeDeclaration to check.
	 *  @param t2 second TypeDeclaration to check.
	 *  @return true, if t1 and t2 have the same base type,
	 *          false otherwise.
	 */
	static bool 
	baseTypeEqual(
		const TypeDeclaration &t1,
		const TypeDeclaration &t2);

	/** find the base type of t and return it.
	 *  @param t type, for which the base type should get looked up.
	 *  @return base type of t (or t, if it's no subtype). NULL on error.
	 */
	static const TypeDeclaration*
	findBaseType(const TypeDeclaration *t);

	/** pick up all index constraints from constrainedArray and store it
	 *  in indexConstraints.
	 *  @param constrainedArray type referring to a constrained array.
	 *  @param indexConstraint list to which indexConstraints should 
	 *         get added.
	 *  @return base type definition of the array.
	 */
	static const UnconstrainedArrayType*
	pickupIndexConstraint(
		const TypeDeclaration *constrainedArray,
		std::list<DiscreteRange*> &indexConstraint
	);

	/** find out, if type is a constraint array.
	  * @param type type to check
	  * @return true, if it is a constraint array, false otherwise.
	  */
	static bool isConstraintArray(const TypeDeclaration *type);

private:
	using TopDownVisitor::process;

	/** process a subtype indiction that is denoted as resolved.
	 *  @param type SubtypeIndication marked as resolved.
	 *  @param resolver name of the resolution function.
	 */
	void 
	processResolutionFunction(
		SubtypeIndication &type, 
		SimpleName &resolver);

	/** process a CaseAlternative node.
	 *  @param node CaseAlternative node to process
	 */
	void processAlternative(CaseAlternative &node);

	/** process a generic ValDeclaration node.
	 *  @param node node to process
	 */
	virtual void process(ValDeclaration &node);

	/** process a generic LibUnit node.
	 *  @param node node to process.
	 */
	virtual void process(LibUnit &node);

	/** process a generic ConditionedStat node.
	 *  @param node node to process.
	 */
	virtual void process(ConditionedStat &node);

	/** process a generic SeqStat node.
	 *  @param node node to process.
	 */
	virtual void process(SeqStat &node);

	/** process array aggregates
	 *  @param node Array Aggregate to process */
	void processArrayAgg(Aggregate &node);

	/** process an ElementAssociation that's part of an Array
	 *  Aggregate.
	 */
	void processArrayAssoc(ElementAssociation &node);

	/** process a DiscreteRange that is defined by a 
	 *  range attribute name.
	 *  @param node DiscreteRange node.
	 */
	void processDRByName(DiscreteRange &node);

	/** process a range attribute name 
	 *  @param node range attribute name node.
	 */
	void processRangeAttr(AttributeName &node);

	/** process a left attribute name 
	 *  @param node left attribute name node.
	 */
	void processLeftAttr(AttributeName &node);

	/** process a right attribute name 
	 *  @param node right attribute name node.
	 */
	void processRightAttr(AttributeName &node);

	/** process an event attribute name.
	 *  @param node event attribute name node.
	 */
	void processEventAttr(AttributeName &node);
	
	/** determine the TypeDeclaration resulting when stripping the first
	 *  nIdx off.
	 *  @param array TypeDeclaration referring to an array type.
	 *  @param nIdx number of indices to strip off (flat model:
	 *         a multidimensional index counts with the number of
	 *         indices. This way it's possible to subscribe to 
	 *         a single array dimension of a multidimensional array
	 *         (which results in an anonymous type).
	 *  @param loc Location to use in case an anonymous type should
	 *         get created.
	 */
	static const TypeDeclaration *
	subscribedType(
		const TypeDeclaration &array, 
		unsigned int nIdx,
		Location loc
	);

	/** process either a physical type or a range constraint type. 
	 *  @param node node to process */
	template <typename T>
	void processConstraintType(T &node);

	/** process a ConstInteger or ConstReal (if it's not physical type.).
	 *  In case there are no candidates, put directMatch on the stack. 
	 *  If candidates are there, check if there are compatible types to
	 *  directMatch and return these.
	 *  If there aren't filter on possible types that are built from 
	 *  implicit conversations based on icCompatible.
	 *  
	 *  @param node ConstInteger or ConstReal node.
	 *  @param directMatch native type of the node.
	 *  @param icCompatible base type, to which fallback implicit 
	 *         conversions can be done.
	 */
	void 
	processUniversal(
		Expression &node, 
		const TypeDeclaration *directMatch,
		enum BaseType icCompatible);

	/** process a generic Subprogram call.
	 *  @param node ProcedureCall statement or FunctionCall statement.
	 *  @param mustSingle must the result be unambiguous?
	 */
	template <typename T>
	void processSubprogCall(T &node, bool mustSingle);

	/** private type definition of the type declaration list */
	typedef std::set<const TypeDeclaration*> typeSetT;
	
	/** report an error, if the type is either ambiguous or no
	 *  type candidates are left.
	 *  @param node errorenous node
	 *  @return true if exactly one type is in TypeCandidates.
	 */
	bool needUniqueType(AstNode &node) const;

	/** report an error, if the type is either ambiuous or no 
	 *  type candidates are left.
	 *  Also set the single type candidate as the type of the 
	 *  Expression.
	 *  @param node Expression to which the type should get applied.
	 */
	bool needUniqueType(Expression &node) const;

	/** FIXME interface ...
	 * report an error, if no type candidates are there.
	 * @param loc location of the error.
	 */
	void needNotEmpty(Location loc);

	/** transform the base type of a range, register an error if 
	 *  given type is not transformable.
	 *  @param rangeType the range inherits.
	 *  @param loc Location of the Range
	 *  @return the resolved base type.
	 */
	static enum BaseType 
	transformBaseType(enum BaseType rangeType, Location loc);

	/** print all types on stderr for debugging purposes
	 *  @param types set of types to print.
	 */
	static void
	debugPrintTypes(const typeSetT &types);

public:
	/** determine the first index range for a type an array aggregate 
	 *  which has the tpye at and the associations assocs.
	 *  @param at array type.
	 *  @param assocs list of association elements.
	 */
	static DiscreteRange *
	determineIndexRangeAgg(
		const UnconstrainedArrayType *at, 
		const std::list<ElementAssociation*> &assocs);

private:
	/** determine the range constraint of the range type.
	 *  @param rangeType range type in question.
	 */
	static DiscreteRange *
	findRange(const TypeDeclaration *rangeType);

	/** possible types.
	 *  When entering a node from its parent node:
	 *  - an empty list means to return all possible types
	 *  - a list with one element means that this is the wanted type
	 *    and that types should get nailed down on this very type.
	 *  - a list with several elements means that these are the possible
	 *    types and that this list should get reduced by possible types
	 *    of this node.
	 */
	typeSetT typeCandidates;

	/** symbol table instance. */
	SymbolTable &symbolTable;

	/** nested class to perform type filtering.
	 *  The TypeFilter can be used to reduce a set of symbol candidates to
	 *  the ones matching any given wanted types.
	 *
	 *  If the wanted types set is empty, it can also fill it with all 
	 *  possible types that the candidate symbols can provide.
	 */
	template <typename T>
	class TypeFilter {
	private:
		/** reference to list of candidate symbols. */
		T &candidates;
		/** reference list with wanted types */
		typeSetT &wantedTypes;
	public:
		/** c'tor 
		 *  @param cands list of candidate types.
		 *  @param wantTypes list of wanted types.
		 */
		TypeFilter(
			T &cands,
			typeSetT &wantTypes
		);

		/** virtual d'tor */
		virtual ~TypeFilter() {}

		/** apply type filtering. */
		void apply(void);

	private:
		/** operation to map a candidate to a type.
		 *  @param element candidate symbol
		 *  @return associated type or NULL if no type could get
		 *          determined.
		 */
		virtual const TypeDeclaration*
		operator()(typename T::value_type element) const = 0;

		/** check if given type is compatible to one of 
		 *  possibleTypes
		 *  @param t type to check.
		 *  @return true, if the type is compatible.
		 */
		bool checkType(const TypeDeclaration *t) const;
	};

	/** class for filtering on list of Symbols. Default implementation does
	 *  a type lookup via LookupTypes of a symbol.
	 */
	class SymbolFilter : public TypeFilter< std::list<Symbol*> > {
	public:
		/** c'tor 
		 *  @param cands list of candidate types.
		 *  @param wantTypes list of wanted types.
		 */
		SymbolFilter(
			std::list<Symbol*> &cands,
			typeSetT &wantTypes
			) : 	TypeFilter< std::list<Symbol*> >(cands, 
							     wantTypes) {}

		/** operation to map a candidate to a type.
		 *  @param element candidate symbol
		 *  @return associated type or NULL if no type could get
		 *          determined.
		 */
		virtual const TypeDeclaration*
		operator()(Symbol *element) const;
	};

	/** symbol filter which projects the n-ths argument of an 
	 *  association list to a type.
	 */
	class ProjectPositionalArg : public SymbolFilter {
	public:
		/** c'tor
		 *  @param cands candidate symbols.
		 *  @param wantTypes wanted types for the n'ths argument
		 *  @param n position of the argument.
		 */
		ProjectPositionalArg(
			std::list<Symbol*> &cands,
			typeSetT &wantTypes,
			unsigned int n
			) :	SymbolFilter(cands, wantTypes),
				position(n) {}

		/** position of the argument. */
		unsigned int position;
	private:
		/** operation to map a candidate to a type.
		 *  @param element candidate symbol
		 *  @return associated type or NULL if no type could get
		 *          determined.
		 */
		virtual const TypeDeclaration*
		operator()(Symbol *element) const;

	};

	//! compare the subscripted type to wanted type
	/** subscribe to wantTypes and compare the result with
	 *  candidates, filtering all non-matching types.
	 */
	class SubscriptFilter : public TypeFilter< typeSetT > {
	public:
		/** c'tor
		 *  @param cands type candidates, that will get subscribed.
		 *  @param wantTypes wanted types 
		 */
		SubscriptFilter(
			typeSetT &cands,
			typeSetT &wantTypes
			) : 	TypeFilter< typeSetT >(cands, wantTypes) {}
	protected:

		/** resolve the type of the source to a subscribed
		 *  type.
		 *  @param element source type 
		 *  @return subscribed type.
		 */
		virtual const TypeDeclaration*
		operator()(typeSetT::value_type element) const;

	}; /* nested class SubscriptFilter */

	//! transform/filter cands into an index type.
	/** take the first SubtypeIndication of indexTypes and check
	 *  against it.
	 */
	class IndexTypeFilter : public TypeFilter< typeSetT > {
	public:
		/** c'tor
		 *  @param cands type candidates, for which the index will
		 *         get looked up.
		 *  @param wantTypes wanted types (will get filled or reduced
		 *  @param idx number of the index to filter (1=1st).
		 */
		IndexTypeFilter(
			typeSetT &cands, 
			typeSetT &wantTypes,
			unsigned int idx
			) :	TypeFilter< typeSetT >(cands, wantTypes),
				nIdx(idx) {}

	private:
		/** resolve the type of the source to an index type of 
		 *  an array.
		 *  @param element source type
		 *  @return index type or NULL if not applicable.
		 */
		virtual const TypeDeclaration*
		operator()(typeSetT::value_type element) const;

		/** number of the index to filter */
		unsigned int nIdx;
	};

	// needs the ability to preload typeCandidates.
	friend class NodeFactory::TypeDeclHelper;
};

}; /* namespace ast */

#endif /* __RESOLVE_TYPES_HPP_INCLUDED */
