/**
  *
  *                      OOAS Compiler
  *
  *       Copyright 2015, AIT Austrian Institute of Technology.
  * This code is based on the C# Version of the OOAS Compiler, which is
  * copyright 2015 by the Institute of Software Technology, Graz University
  * of Technology with portions copyright by the AIT Austrian Institute of
  * Technology. All rights reserved.
  *
  * SEE THE "LICENSE" FILE FOR THE TERMS UNDER WHICH THIS FILE IS PROVIDED.
  *
  * If you modify the file please update the list of contributors below to in-
  * clude your name. Please also stick to the coding convention of using TABs
  * to do the basic (block-level) indentation and spaces for anything after
  * that. (Enable the display of special chars and it should be pretty obvious
  * what this means.) Also, remove all trailing whitespace.
  *
  * Contributors:
  *               Willibald Krenn (AIT)
  *               Stephan Zimmerer (AIT)
  *               Markus Demetz (AIT)
  *               Christoph Czurda (AIT)
  *
  */


package org.momut.ooas.visitors;

import java.util.HashSet;

import org.momut.ooas.ast.IAst;
import org.momut.ooas.ast.IAstVisitor;
import org.momut.ooas.ast.expressions.AccessExpression;
import org.momut.ooas.ast.expressions.BinaryOperator;
import org.momut.ooas.ast.expressions.CallExpression;
import org.momut.ooas.ast.expressions.ExistsQuantifier;
import org.momut.ooas.ast.expressions.Expression;
import org.momut.ooas.ast.expressions.ExpressionKind;
import org.momut.ooas.ast.expressions.ForallQuantifier;
import org.momut.ooas.ast.expressions.IdentifierExpression;
import org.momut.ooas.ast.expressions.ListConstructor;
import org.momut.ooas.ast.expressions.MapConstructor;
import org.momut.ooas.ast.expressions.ObjectConstructor;
import org.momut.ooas.ast.expressions.SetConstructor;
import org.momut.ooas.ast.expressions.TernaryOperator;
import org.momut.ooas.ast.expressions.TupleConstructor;
import org.momut.ooas.ast.expressions.TupleMapAccessExpression;
import org.momut.ooas.ast.expressions.TypeExpression;
import org.momut.ooas.ast.expressions.UnaryOperator;
import org.momut.ooas.ast.expressions.UnresolvedIdentifierExpression;
import org.momut.ooas.ast.expressions.ValueExpression;
import org.momut.ooas.ast.expressions.MapConstructor.MapItem;
import org.momut.ooas.ast.identifiers.AttributeIdentifier;
import org.momut.ooas.ast.identifiers.ConstantIdentifier;
import org.momut.ooas.ast.identifiers.EnumIdentifier;
import org.momut.ooas.ast.identifiers.ExpressionVariableIdentifier;
import org.momut.ooas.ast.identifiers.Identifier;
import org.momut.ooas.ast.identifiers.LocalVariableIdentifier;
import org.momut.ooas.ast.identifiers.MainModule;
import org.momut.ooas.ast.identifiers.MethodIdentifier;
import org.momut.ooas.ast.identifiers.Module;
import org.momut.ooas.ast.identifiers.NamedActionIdentifier;
import org.momut.ooas.ast.identifiers.NondetIdentifierList;
import org.momut.ooas.ast.identifiers.ParameterIdentifier;
import org.momut.ooas.ast.identifiers.PrioIdentifierList;
import org.momut.ooas.ast.identifiers.SelfTypeIdentifier;
import org.momut.ooas.ast.identifiers.SeqIdentifierList;
import org.momut.ooas.ast.identifiers.TypeIdentifier;
import org.momut.ooas.ast.identifiers.UnspecIdentifierList;
import org.momut.ooas.ast.statements.AbortStatement;
import org.momut.ooas.ast.statements.Assignment;
import org.momut.ooas.ast.statements.BreakStatement;
import org.momut.ooas.ast.statements.Call;
import org.momut.ooas.ast.statements.GuardedCommand;
import org.momut.ooas.ast.statements.KillStatement;
import org.momut.ooas.ast.statements.NondetBlock;
import org.momut.ooas.ast.statements.PrioBlock;
import org.momut.ooas.ast.statements.SeqBlock;
import org.momut.ooas.ast.statements.SkipStatement;
import org.momut.ooas.ast.statements.Statement;
import org.momut.ooas.ast.types.AnyType;
import org.momut.ooas.ast.types.BoolType;
import org.momut.ooas.ast.types.CharType;
import org.momut.ooas.ast.types.EnumType;
import org.momut.ooas.ast.types.FloatType;
import org.momut.ooas.ast.types.FunctionType;
import org.momut.ooas.ast.types.IntType;
import org.momut.ooas.ast.types.ListType;
import org.momut.ooas.ast.types.MapType;
import org.momut.ooas.ast.types.MetaType;
import org.momut.ooas.ast.types.NullType;
import org.momut.ooas.ast.types.OoActionSystemType;
import org.momut.ooas.ast.types.OpaqueType;
import org.momut.ooas.ast.types.TupleType;
import org.momut.ooas.ast.types.Type;
import org.momut.ooas.parser.ParserState;

public abstract class OoaCompleteAstTraversalVisitor implements IAstVisitor
{
	protected HashSet<Object> m_visited = new HashSet<Object>();
	protected ParserState m_ParserState;
	protected boolean m_DuplicateVisit;

	protected void VisitSub(IAst subElement, IAst parent)
	{
		if (subElement == null)
			return;
		if (m_visited.contains(subElement))
			return;
		if (!(subElement instanceof OpaqueType) && !m_DuplicateVisit)
			m_visited.add(subElement);
		VisitAstElement(subElement, parent);
	}

	/// <summary>
	///  Is called once per Ast element. Makes sense to override it in a
	///  subclass..
	/// </summary>
	/// <param name="element">to be visited</param>
	/// <param name="parent">parent of the element in the ast</param>
	protected void VisitAstElement(IAst element, IAst parent)
	{
		element.Accept(this);
	}

	public OoaCompleteAstTraversalVisitor(ParserState aState)
	{
		m_ParserState = aState;
	}

	public OoaCompleteAstTraversalVisitor(ParserState aState, boolean completeTraversal)
	{
		this(aState);
		m_DuplicateVisit = completeTraversal;
	}

	@Override
	public String returnVisitorName()
	{
		return this.getClass().getSimpleName();
	}

	@Override
	public void done() {
	}

	@Override
	public /*override*/ String toString()
	{
		return returnVisitorName();
	}

	@Override
	public void visit(EnumIdentifier enumIdentifier)
	{
		VisitSub(enumIdentifier.type(), enumIdentifier);
	}

	@Override
	public void visit(ConstantIdentifier constantIdentifier)
	{
		if (constantIdentifier.type() != null)
			VisitSub(constantIdentifier.type(), constantIdentifier);
		VisitSub(constantIdentifier.Value(), constantIdentifier);
	}

	@Override
	public void visit(AttributeIdentifier attributeIdentifier)
	{
		VisitSub(attributeIdentifier.type(), attributeIdentifier);
		VisitSub(attributeIdentifier.initializer(), attributeIdentifier);
	}

	@Override
	public void visit(ExpressionVariableIdentifier expressionVariableIdentifier)
	{
		VisitSub(expressionVariableIdentifier.type(), expressionVariableIdentifier);
	}

	@Override
	public void visit(ParameterIdentifier parameterIdentifier)
	{
		VisitSub(parameterIdentifier.type(), parameterIdentifier);
	}

	@Override
	public void visit(LocalVariableIdentifier localVariableIdentifier)
	{
		VisitSub(localVariableIdentifier.type(), localVariableIdentifier);
	}

	@Override
	public void visit(TypeIdentifier typeIdentifier)
	{
		VisitSub(typeIdentifier.type(), typeIdentifier);
	}

	@Override
	public void visit(SelfTypeIdentifier aself)
	{
		visit((TypeIdentifier)aself);
	}

	@Override
	public void visit(MethodIdentifier methodIdentifier)
	{
		VisitSub(methodIdentifier.type(), methodIdentifier);
		for (final Identifier sym: methodIdentifier.symbolTable().symbolList())
			VisitSub(sym, methodIdentifier);
		for (final ParameterIdentifier par: methodIdentifier.parameter())
			VisitSub(par, methodIdentifier);
		VisitSub(methodIdentifier.body(), methodIdentifier);
	}

	@Override
	public void visit(NamedActionIdentifier namedActionIdentifier)
	{
		VisitSub(namedActionIdentifier.type(), namedActionIdentifier);
		for (final Identifier sym : namedActionIdentifier.symbolTable().symbolList())
			VisitSub(sym, namedActionIdentifier);
		for (final ParameterIdentifier par: namedActionIdentifier.parameter())
			VisitSub(par, namedActionIdentifier);
		VisitSub(namedActionIdentifier.body(), namedActionIdentifier);
	}

	@Override
	public void visit(MainModule mainModule)
	{
		for (final Identifier sym: mainModule.symbolTable().symbolList())
			VisitSub(sym, mainModule);
		final boolean haveInstance = mainModule.instance() != null;
		if (haveInstance)
			VisitSub(mainModule.instance(), mainModule);
		else
			VisitSub(mainModule.systemDescription(), mainModule);
	}

	@Override
	public void visit(Module module)
	{
		for (final Identifier sym: module.symbolTable().symbolList())
			VisitSub(sym, module);
	}

	@Override
	public void visit(NondetIdentifierList nondetIdentifierList)
	{
		for (final Identifier id: nondetIdentifierList.identifiers())
			VisitSub(id, nondetIdentifierList);
	}

	@Override
	public void visit(SeqIdentifierList seqIdentifierList)
	{
		for (final Identifier id: seqIdentifierList.identifiers())
			VisitSub(id, seqIdentifierList);
	}

	@Override
	public void visit(PrioIdentifierList prioIdentifierList)
	{
		for (final Identifier id: prioIdentifierList.identifiers())
			VisitSub(id, prioIdentifierList);
	}

	@Override
	public void visit(UnspecIdentifierList unspecIdentifierList)
	{
		for (final Identifier id: unspecIdentifierList.identifiers())
			VisitSub(id, unspecIdentifierList);
	}

	@Override
	public void visit(NondetBlock nondetBlock)
	{
		for (final Identifier id: nondetBlock.symbols().symbolList())
			VisitSub(id, nondetBlock);

		for (final Statement stmnt: nondetBlock.statements())
			VisitSub(stmnt, nondetBlock);
	}

	@Override
	public void visit(SeqBlock seqBlock)
	{
		for (final Identifier id: seqBlock.symbols().symbolList())
			VisitSub(id, seqBlock);

		if (seqBlock.filter() != null)
			VisitSub(seqBlock.filter(), seqBlock);

		for (final Statement stmnt: seqBlock.statements())
			VisitSub(stmnt, seqBlock);
	}

	@Override
	public void visit(PrioBlock prioBlock)
	{
		for (final Identifier id: prioBlock.symbols().symbolList())
			VisitSub(id, prioBlock);

		for (final Statement stmnt: prioBlock.statements())
			VisitSub(stmnt, prioBlock);
	}

	@Override
	public void visit(GuardedCommand guardedCommand)
	{
		VisitSub(guardedCommand.guard(), guardedCommand);
		VisitSub(guardedCommand.body(), guardedCommand);
	}

	@Override
	public void visit(Assignment assignment)
	{
		for (final Expression vexp: assignment.places())
			VisitSub(vexp, assignment);
		for (final Expression expr: assignment.values())
			VisitSub(expr, assignment);

		VisitSub(assignment.nondetExpression(), assignment);
	}

	@Override
	public void visit(Call call)
	{
		VisitSub(call.callExpression(), call);
	}

	@Override
	public void visit(SkipStatement skipStatement)
	{ }

	@Override
	public void visit(BreakStatement breakStatement)
	{ }

	@Override
	public void visit(AbortStatement abortStatement)
	{ }

	@Override
	public void visit(KillStatement killStatement)
	{
		VisitSub(killStatement.someOne, killStatement);
	}

	@Override
	public <T>void visit(ValueExpression<T> valueExpression)
	{ }

	@Override
	public void visit(UnresolvedIdentifierExpression unresolvedIdentifierExpression)
	{
		VisitSub(unresolvedIdentifierExpression.type(), unresolvedIdentifierExpression);
	}

	@Override
	public void visit(IdentifierExpression identifierExpression)
	{
		VisitSub(identifierExpression.type(), identifierExpression);
		VisitSub(identifierExpression.identifier(), identifierExpression);
	}

	@Override
	public void visit(TypeExpression typeExpression)
	{
		VisitSub(typeExpression.referredType(), typeExpression);
		VisitSub(typeExpression.type(), typeExpression);
	}

	@Override
	public void visit(ListConstructor listConstructor)
	{
		for (final Identifier lvar: listConstructor.comprehensionVariables().symbolList())
			VisitSub(lvar, listConstructor);

		for (final Expression elems: listConstructor.elements())
			VisitSub(elems, listConstructor);

		if (listConstructor.comprehension() != null)
			VisitSub(listConstructor.comprehension(), listConstructor);
	}

	@Override
	public void visit(SetConstructor setConstructor)
	{
		for (final Identifier svar: setConstructor.comprehensionVariables().symbolList())
			VisitSub(svar, setConstructor);

		for (final Expression elems: setConstructor.items())
			VisitSub(elems, setConstructor);

		VisitSub(setConstructor.comprehension(), setConstructor);
	}

	@Override
	public void visit(MapConstructor mapConstructor)
	{
		/*foreach (var lvar in mapConstructor.localVariables.symbolList)
			VisitSub(lvar, mapConstructor);*/

		for (final MapItem elem: mapConstructor.items())
		{
			VisitSub(elem.key, mapConstructor);
			VisitSub(elem.value, mapConstructor);
		}
	}

	@Override
	public void visit(TupleConstructor tupleConstructor)
	{
		/*foreach (var lvar in tupleConstructor.localVariables.symbolList)
			VisitSub(lvar, tupleConstructor);*/

		for (final Expression elem: tupleConstructor.values())
			VisitSub(elem, tupleConstructor);
	}

	@Override
	public void visit(AccessExpression accessExpression)
	{
		VisitSub(accessExpression.left(), accessExpression);
		VisitSub(accessExpression.right(), accessExpression);
	}

	@Override
	public void visit(BinaryOperator binaryOperator)
	{
		VisitSub(binaryOperator.left(), binaryOperator);
		VisitSub(binaryOperator.right(), binaryOperator);
	}

	@Override
	public void visit(TernaryOperator ternaryOperator)
	{
		VisitSub(ternaryOperator.left(), ternaryOperator);
		VisitSub(ternaryOperator.mid(), ternaryOperator);
		VisitSub(ternaryOperator.right(), ternaryOperator);
	}

	@Override
	public void visit(TupleMapAccessExpression tupleMapAccessExpression)
	{
		VisitSub(tupleMapAccessExpression.argument(), tupleMapAccessExpression);
		VisitSub(tupleMapAccessExpression.child(), tupleMapAccessExpression);
	}

	@Override
	public void visit(CallExpression callExpression)
	{
		if (callExpression.arguments() != null)
			for (final Expression arg: callExpression.arguments())
				VisitSub(arg, callExpression);
		VisitSub(callExpression.child(), callExpression);
	}

	@Override
	public void visit(ForallQuantifier quantifier)
	{
		for (final Identifier lvar: quantifier.symbols().symbolList())
			VisitSub(lvar, quantifier);

		VisitSub(quantifier.child(), quantifier);
	}

	@Override
	public void visit(ExistsQuantifier quantifier)
	{
		for (final Identifier lvar: quantifier.symbols().symbolList())
			VisitSub(lvar, quantifier);

		VisitSub(quantifier.child(), quantifier);
	}

	@Override
	public void visit(UnaryOperator unaryOperator)
	{
		VisitSub(unaryOperator.child(), unaryOperator);
		if (unaryOperator.kind() == ExpressionKind.Cast)
			VisitSub(unaryOperator.type(), unaryOperator);
	}



	@Override
	public void visit(CharType charType)
	{
	}

	@Override
	public void visit(IntType intType)
	{
	}

	@Override
	public void visit(BoolType boolType)
	{
	}

	@Override
	public void visit(FloatType floatType)
	{
	}

	@Override
	public void visit(MetaType metaType) {
		VisitSub(metaType.Type(), metaType);
	}

	@Override
	public void visit(EnumType enumType)
	{
		for (final Identifier sym: enumType.listOfEnumSymbols())
			VisitSub(sym, enumType);
	}

	@Override
	public void visit(ListType listType)
	{
		VisitSub(listType.innerType(), listType);
	}

	@Override
	public void visit(MapType mapType)
	{
		VisitSub(mapType.fromType(), mapType);
		VisitSub(mapType.toType(), mapType);
	}

	@Override
	public void visit(TupleType tupleType)
	{
		for (final Type t: tupleType.innerTypes())
			VisitSub(t, tupleType);
	}

	@Override
	public void visit(FunctionType functionType)
	{
		for (final Type a: functionType.parameter())
			VisitSub(a, functionType);
		VisitSub(functionType.returnType(), functionType);
	}

	@Override
	public void visit(OoActionSystemType ooActionSystemType)
	{
		for (final Identifier s: ooActionSystemType.symbols().symbolList())
			VisitSub(s, ooActionSystemType);

		VisitSub(ooActionSystemType.doOdBlock(), ooActionSystemType);
	}

	public @Override
	void visit(OpaqueType opaqueType)
	{
		VisitSub(opaqueType.resolvedType(), opaqueType);
	}

	@Override
	public void visit(NullType nullType)
	{ }

	@Override
	public void visit(AnyType anyType)
	{ }

	@Override
	public void visit(ObjectConstructor objectConstructor)
	{
		VisitSub(objectConstructor.type(), objectConstructor);
	}
}

