/**
  *
  *                      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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;

import org.momut.ooas.ast.IScope;
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.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.Quantifier;
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.identifiers.AttributeIdentifier;
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.IdentifierKind;
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.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.Block;
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.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.parser.SymbolTable;
import org.momut.ooas.utils.OoasCompilerHashMap;
import org.momut.ooas.utils.exceptions.ArgumentException;
import org.momut.ooas.utils.exceptions.NotImplementedException;
import org.momut.ooas.utils.exceptions.InternalCompilerException;

public final class OoaDeepCloneVisitor extends OoaCompleteAstTraversalVisitor
{

	OoasCompilerHashMap<Object, Object> mapping;
	Identifier selfreplacement;

	public boolean quantifierState = false;

	/* we do not clone types */
	@Override
	public void visit(AnyType anyType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(BoolType boolType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(CharType charType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(EnumType enumType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(FloatType floatType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(FunctionType functionType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(IntType intType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(ListType listType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(MapType mapType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(NullType nullType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(OoActionSystemType ooActionSystemType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(TupleType tupleType)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(EnumIdentifier enumIdentifier)
	{ throw new NotImplementedException(); }
	@Override
	public void visit(ExpressionVariableIdentifier expressionVariableIdentifier)
	{ throw new NotImplementedException(); }


	@Override
	public void visit(LocalVariableIdentifier localVariableIdentifier)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(MainModule mainModule)
	{
		throw new NotImplementedException();
	}


	@Override
	public void visit(MapConstructor mapConstructor)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(MethodIdentifier methodIdentifier)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(NamedActionIdentifier namedActionIdentifier)
	{
		final Statement newBlock = SClone(namedActionIdentifier.body());
		namedActionIdentifier.SetBody(newBlock);
		// we do not clone the parameters..
	}

	@Override
	public void visit(Module module)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(NondetIdentifierList nondetIdentifierList)
	{
		throw new NotImplementedException();
	}


	@Override
	public void visit(OpaqueType opaqueType)
	{
		throw new NotImplementedException(); // must not happen
	}

	@Override
	public void visit(ParameterIdentifier parameterIdentifier)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(PrioIdentifierList prioIdentifierList)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(ObjectConstructor objectConstructor)
	{
		//base.visit(objectConstructor);
	}

	private void UpdateScope(IScope aScope)
	{
		// this requires parents to be added before their children
		if (aScope.GetParentScope() != null)
		{
			if(!mapping.containsKey(aScope.GetParentScope()))
				throw new ArgumentException();
			else
				aScope.SetParentScope((IScope)mapping.get(aScope.GetParentScope()));
		}
	}

	private void CheckScope(IScope aScope) {
		if (aScope.GetParentScope() == null)
			return;
		if (mapping.containsKey(aScope.GetParentScope()))
			throw new InternalCompilerException("Internal Error. Scope check failed.");
	}

	private Expression EClone(Expression anExpr)
	{
		Expression result = null;
		if (anExpr != null)
		{
			result = anExpr.Clone();

			if (anExpr instanceof IScope)
			{
				mapping.putSafe(anExpr, result);
				UpdateScope((IScope)result);
			}

			result.Accept(this);
		}
		return result;
	}

	private Statement SClone(Statement aStatement)
	{
		Statement result = null;
		if (aStatement != null)
		{
			result = aStatement.Clone();

			if (aStatement instanceof IScope)
			{
				mapping.putSafe(aStatement, result);
				UpdateScope((IScope)result);
			}

			result.Accept(this);
		}
		return result;
	}

	// clones symbols found in symbol table
	private void SymTabClone(SymbolTable aTable)
	{
		if (aTable != null && aTable.symbolList().size() > 0)
		{
			final List<Identifier> newList = new ArrayList<Identifier>();
			for (final Identifier x: aTable.symbolList())
			{
				if (!mapping.containsKey(x))
				{
					final Identifier newid = x.Clone();
					newList.add(newid);
					mapping.putSafe(x, newid);
				}
				else
					newList.add((Identifier)mapping.get(x));
			}
			aTable.SetSymbolList(newList);
		}
	}


	private void cloneExpressionList(ListIterator<Expression> anElem)
	{
		while (anElem.hasNext())
		{
			final Expression clone = EClone(anElem.next());
			anElem.remove();
			anElem.add(clone);
		}
	}


	/*no need to clone leafs (they are already cloned and a shallow copy is sufficient*/
	@Override
	public void visit(AbortStatement abortStatement)
	{ }
	@Override
	public <T> void visit(ValueExpression<T> valueExpression)
	{ }


	/*clone a block*/
	public void cloneBlock(Block aBlock)
	{
		SymTabClone(aBlock.symbols());
		final ListIterator<Statement> stmnt = aBlock.statements().listIterator();
		while (stmnt.hasNext())
		{
			final Statement clone = SClone(stmnt.next());
			stmnt.remove();
			stmnt.add(clone);
		}
	}
	@Override
	public void visit(NondetBlock nondetBlock)
	{
		cloneBlock(nondetBlock);
	}
	@Override
	public void visit(SeqBlock seqBlock)
	{
		seqBlock.SetFilter(EClone(seqBlock.filter()));
		cloneBlock(seqBlock);
	}
	@Override
	public void visit(PrioBlock prioBlock)
	{
		cloneBlock(prioBlock);
	}

	@Override
	public void visit(AccessExpression accessExpression)
	{
		visit((BinaryOperator)accessExpression);
	}
	@Override
	public void visit(BinaryOperator binaryOperator)
	{
		final Expression left = EClone(binaryOperator.left());
		final Expression right = EClone(binaryOperator.right());
		binaryOperator.SetLeftChild(left);
		binaryOperator.SetRightChild(right);
	}

	@Override
	public void visit(Assignment assignment)
	{
		SymTabClone(assignment.symbols());
		cloneExpressionList(assignment.places().listIterator());
		cloneExpressionList(assignment.values().listIterator());
		assignment.SetNondetExpression(EClone(assignment.nondetExpression()));
		CheckScope(assignment);
	}

	@Override
	public void visit(AttributeIdentifier attributeIdentifier)
	{
		final Expression initializer = EClone(attributeIdentifier.initializer());
		attributeIdentifier.SetInitializer(initializer);
	}
	@Override
	public void visit(Call call)
	{
		final Expression cExpr = EClone(call.callExpression());
		call.SetCallExpression(cExpr);
	}

	@Override
	public void visit(CallExpression callExpression)
	{
		final ArrayList<Expression> newargs = new ArrayList<Expression>();
		for (final Expression x: callExpression.arguments())
		{
			newargs.add(EClone(x));
		}
		callExpression.SetArguments(newargs);

		visit((UnaryOperator)callExpression);
	}

	public void CloneQuantifier(Quantifier qf)
	{
		SymTabClone(qf.symbols());
		final boolean oldQunatifierState = quantifierState;
		quantifierState = true;
		visit(qf);
		quantifierState = oldQunatifierState;
	}
	@Override
	public void visit(ExistsQuantifier quantifier)
	{
		CloneQuantifier(quantifier);
	}
	@Override
	public void visit(ForallQuantifier quantifier)
	{
		CloneQuantifier(quantifier);
	}




	@Override
	public void visit(GuardedCommand guardedCommand)
	{
		final Statement newblock = SClone(guardedCommand.body());
		final Expression newguard = EClone(guardedCommand.guard());
		guardedCommand.SetBody(newblock);
		guardedCommand.SetGuard(newguard);
	}

	@Override
	public void visit(IdentifierExpression identifierExpression)
	{

		if (identifierExpression.isSelf())
			identifierExpression.SetIdentifier(selfreplacement);
		else if (mapping.containsKey(identifierExpression.identifier())
				&& identifierExpression.identifier().kind() != IdentifierKind.MethodIdentifier)
			/* we do NOT replace method calls here, since they are prefixed by self if it's a
			 * method in the current object or by some field/method access if it's another
			 * object.
			 */
			identifierExpression.SetIdentifier((Identifier)mapping.get(identifierExpression.identifier()));
	}

	@Override
	public void visit(KillStatement killStatement)
	{
		if (mapping.containsKey(killStatement.someOne))
			killStatement.SetIdentifier((Identifier)mapping.get(killStatement.someOne));
	}


	@Override
	public void visit(ListConstructor listConstructor)
	{
		SymTabClone(listConstructor.comprehensionVariables());

		final ArrayList<Expression> newelems = new ArrayList<Expression>();
		for (final Expression x: listConstructor.elements())
			newelems.add(EClone(x));

		listConstructor.SetElements(newelems);
		listConstructor.SetComprehension(EClone(listConstructor.comprehension()));
	}

	@Override
	public void visit(SeqIdentifierList seqIdentifierList)
	{
		throw new NotImplementedException();
	}


	@Override
	public void visit(SetConstructor setConstructor)
	{
		SymTabClone(setConstructor.comprehensionVariables());
		final ArrayList<Expression> newelems = new ArrayList<Expression>();
		for (final Expression x: setConstructor.items())
			newelems.add(EClone(x));

		setConstructor.SetItems(newelems);
		setConstructor.SetComprehension(EClone(setConstructor.comprehension()));
	}


	@Override
	public void visit(SkipStatement skipStatement)
	{ }

	@Override
	public void visit(TernaryOperator ternaryOperator)
	{
		ternaryOperator.SetLeftChild(EClone(ternaryOperator.left()));
		ternaryOperator.SetMidChild(EClone(ternaryOperator.mid()));
		ternaryOperator.SetRightChild(EClone(ternaryOperator.right()));
	}

	@Override
	public void visit(TupleConstructor tupleConstructor)
	{
		final ArrayList<Expression> values = new ArrayList<Expression>();
		for (final Expression x: tupleConstructor.values())
			values.add(EClone(x));
		tupleConstructor.SetTupleValues(values);
	}

	@Override
	public void visit(TupleMapAccessExpression tupleMapAccessExpression)
	{
		tupleMapAccessExpression.SetArgument(EClone(tupleMapAccessExpression.argument()));
		visit((UnaryOperator)tupleMapAccessExpression);
	}

	@Override
	public void visit(TypeExpression typeExpression)
	{ /*we do not clone types*/ }


	@Override
	public void visit(TypeIdentifier typeIdentifier)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(UnaryOperator unaryOperator)
	{
		final Expression child = EClone(unaryOperator.child());
		unaryOperator.SetChild(child);
	}


	@Override
	public void visit(UnresolvedIdentifierExpression unresolvedIdentifierExpression)
	{
		throw new NotImplementedException(); // must not occur
	}

	@Override
	public void visit(UnspecIdentifierList unspecIdentifierList)
	{
		throw new NotImplementedException();
	}

	public OoaDeepCloneVisitor(HashMap<Object, Object> initMapping, Identifier selfreplace)
	{
		super (null);
		mapping = new OoasCompilerHashMap<Object, Object>(initMapping);
		selfreplacement = selfreplace;
	}
}
