/**
  *
  *                      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 org.momut.ooas.CompilerConfiguration.LabelCompression;
import org.momut.ooas.ast.IScope;
import org.momut.ooas.ast.identifiers.AttributeIdentifier;
import org.momut.ooas.ast.identifiers.FunctionIdentifier;
import org.momut.ooas.ast.identifiers.Identifier;
import org.momut.ooas.ast.identifiers.IdentifierKind;
import org.momut.ooas.ast.identifiers.IdentifierList;
import org.momut.ooas.ast.identifiers.MainModule;
import org.momut.ooas.ast.identifiers.MethodIdentifier;
import org.momut.ooas.ast.identifiers.NamedActionIdentifier;
import org.momut.ooas.ast.identifiers.SelfTypeIdentifier;
import org.momut.ooas.ast.identifiers.TypeIdentifier;
import org.momut.ooas.ast.statements.Block;
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.Statement;
import org.momut.ooas.ast.types.ActionSystem;
import org.momut.ooas.ast.types.FunctionType;
import org.momut.ooas.ast.types.OoActionSystemInstance;
import org.momut.ooas.ast.types.OoActionSystemType;
import org.momut.ooas.ast.types.TypeKind;
import org.momut.ooas.codegen.java.JavaInitVisitor;
import org.momut.ooas.parser.ParserError;
import org.momut.ooas.parser.ParserState;
import org.momut.ooas.parser.SymbolTable;
import org.momut.ooas.utils.OoasCompilerHashMap;
import org.momut.ooas.utils.exceptions.NotImplementedException;

public final class OoaObjectInstantiationVisitor extends OoaCompleteAstTraversalVisitor
{
	private final JavaInitVisitor m_simulator;
	private final OoaIdentifierCompressVisitor m_compressor;
	private ActionSystem m_actionSystem;
	private Identifier self;
	private OoasCompilerHashMap<Object, Object> m_toReplace = new OoasCompilerHashMap<Object, Object>();

	/// <summary>
	/// for a given object and type, the method duplicates the attributes and adds them to the resulting
	/// instantiated action system.
	/// </summary>
	private void DuplicateAttribute(AttributeIdentifier attributeIdentifier,
			OoActionSystemInstance obj, OoActionSystemType aType, ActionSystem m_actionSystem)
	{
		final AttributeIdentifier newAttribute = new AttributeIdentifier(attributeIdentifier);
		newAttribute.SetTokenText(String.format("%s_%s", obj.Name, attributeIdentifier.tokenText()));
		m_toReplace.putSafe(attributeIdentifier, newAttribute);

		newAttribute.SetInitializer(newAttribute.initializer().Clone());
		m_toReplace.putSafe(attributeIdentifier.initializer(), newAttribute.initializer());
		final OoaDeepCloneVisitor cloner = new OoaDeepCloneVisitor(m_toReplace, self);
		newAttribute.initializer().Accept(cloner);

		m_actionSystem.symbols().AddIdentifier(newAttribute);
	}

	public String getInstanceFunctionName(OoActionSystemInstance obj, FunctionIdentifier functionIdentifier)
	{
		final FunctionType functionType = (FunctionType) functionIdentifier.type();
		String objectName = "";
		switch(functionType.functionType()) {
		case Controllable:
		case Observable:
			objectName = m_compressor.originalName(obj.Name);
			break;
		case Internal:
		default:
			objectName = obj.Name;
		}

		return String.format("%s_%s", objectName, functionIdentifier.tokenText());
	}

	private void DuplicateNamedAction(NamedActionIdentifier namedActionIdentifier,
			OoActionSystemInstance obj, OoActionSystemType aType, ActionSystem m_actionSystem)
	{
		final NamedActionIdentifier newAction = new NamedActionIdentifier(namedActionIdentifier);
		final String actionName = getInstanceFunctionName(obj, namedActionIdentifier);
		newAction.SetTokenText(actionName);
		m_toReplace.putSafe(namedActionIdentifier, newAction);
		final OoaDeepCloneVisitor cloner = new OoaDeepCloneVisitor(m_toReplace, self);
		newAction.Accept(cloner);

		m_actionSystem.symbols().AddIdentifier(newAction);
	}

	private void DuplicateMethod(MethodIdentifier methodIdentifier, OoActionSystemInstance obj,
			OoActionSystemType aType, ActionSystem m_actionSystem)
	{
		final MethodIdentifier newMethod = new MethodIdentifier(methodIdentifier);
		final String methodName = getInstanceFunctionName(obj, methodIdentifier);
		newMethod.SetTokenText(methodName);
		m_toReplace.putSafe(methodIdentifier, newMethod);
		final Statement newBlock = newMethod.body().Clone();
		if (newBlock instanceof IScope)
			((IScope)newBlock).SetParentScope(newMethod);
		m_toReplace.putSafe(newMethod.body(), newBlock);
		newMethod.SetBody(newBlock);
		final OoaDeepCloneVisitor cloner = new OoaDeepCloneVisitor(m_toReplace, self);
		newMethod.body().Accept(cloner);

		m_actionSystem.symbols().AddIdentifier(newMethod);
	}


	/*Duplicates the do-od block (incl. any do-od blocks of parent classes)*/
	private void DuplicateDoOdBlock(OoActionSystemInstance obj, OoActionSystemType aType, ActionSystem m_actionSystem, Block result)
	{
//		OoActionSystemType rootType = aType;
		Block block;
		while (aType != null)
		{
			block = aType.doOdBlock();
			if (block != null)
			{
				final Block clone = (Block)block.Clone();
				m_toReplace.putSafe(block, clone);
				clone.SetParentScope(result);
				final OoaDeepCloneVisitor cloner = new OoaDeepCloneVisitor(m_toReplace, self);
				clone.Accept(cloner);
				result.AddStatement(clone);
			}
			aType = aType.baseType();
		}
	}

	OoasCompilerHashMap<Object, Object> objectsdone = new OoasCompilerHashMap<Object, Object>();

	/// <summary>
	/// Generates an OoActionSystemType that is the instantiation of all the objects found in the System Assembling Block
	/// </summary>
	private void InstantiateObjects(ActionSystem m_actionSystem, MainModule mainModule, IdentifierList sysdescr, Block parent)
	{
		Block newBlock;
		switch (sysdescr.listType())
		{
		case nondeterministic:
			newBlock = new NondetBlock(0, 0);
			break;
		case prioritized:
			newBlock = new PrioBlock(0, 0);
			break;
		case seqential:
			newBlock = new SeqBlock(0, 0);
			break;

		case normal:
		default:
			throw new NotImplementedException();
		}

		if (parent == null)
			m_actionSystem.SetDoOdBlock(newBlock);
		else
			parent.AddStatement(newBlock);

		for (final Identifier id: sysdescr.identifiers())
		{
			Identifier ident = id;
			while (ident.kind() == IdentifierKind.List && ((IdentifierList)ident).identifiers().size() == 1)
			{
				ident = ((IdentifierList)ident).identifiers().peekFirst();
			}

			if (ident.kind() == IdentifierKind.List)
				InstantiateObjects(m_actionSystem, mainModule, (IdentifierList)ident, newBlock);
			else
			{
				if (ident.kind() == IdentifierKind.TypeIdentifier &&
						((TypeIdentifier)ident).type().kind() == TypeKind.OoActionSystemType)
				{

					final OoActionSystemType aType = (OoActionSystemType)((TypeIdentifier)ident).type();
					objectsdone.putSafe(aType, m_actionSystem);


					Block NondetBlock = null; // holds parallel comp of all objects
					if (aType.doOdBlock() != null)
					{
						NondetBlock = new NondetBlock(0, 0);
						newBlock.AddStatement(NondetBlock);
					}


					for (final OoActionSystemInstance obj: aType.objects())
					{
						m_toReplace = new OoasCompilerHashMap<Object, Object>(objectsdone);
						addParentClassMappings(m_toReplace, aType, m_actionSystem);
						self = new SelfTypeIdentifier(obj.Name, aType, null);
						final SymbolTable table = aType.getAllSymbols();
						for (final Identifier x: table.symbolList())
						{
							// resolve attributes first
							if (x.kind() == IdentifierKind.AttributeIdentifier)
								DuplicateAttribute((AttributeIdentifier)x, obj, aType, m_actionSystem);
						}
						for (final Identifier x: table.symbolList())
						{
							// now do methods and other stuff
							if (x.kind() == IdentifierKind.MethodIdentifier)
								DuplicateMethod((MethodIdentifier)x, obj, aType, m_actionSystem);
							else if (x.kind() == IdentifierKind.NamedActionIdentifier)
								DuplicateNamedAction((NamedActionIdentifier)x, obj, aType, m_actionSystem);
						}
						// construct parallel composition of all the objects of one class.
						if (aType.doOdBlock() != null)
						{
							DuplicateDoOdBlock(obj, aType, m_actionSystem, NondetBlock);
						}
					}

					// add an error, if a type does not get instantiated and the do-od block is filled..
					// !!!!!!!!!!!!!!! note, if new is also allowed in normal actions, this needs to be disabled (or become a warning) !!!!!!!!!!!!!!!!!!!!!!!!!!
					if (aType.objects().size() == 0 && aType.doOdBlock() != null)
					{
						m_ParserState.AddErrorMessage(new ParserError(m_ParserState.filename, aType.identifier().line(), aType.identifier().column(),
								String.format("Type '%s' is never instantiated but has a non-empty do-od block", aType.toString())));
					}
				}
				else
					throw new NotImplementedException();
			}
		}
	}

	private void addParentClassMappings(OoasCompilerHashMap<Object, Object> m_toReplace, OoActionSystemType aType, ActionSystem m_actionSystem)
	{
		while (aType != null)
		{
			if (!m_toReplace.containsKey(aType))
				m_toReplace.putSafe(aType, m_actionSystem); // add base classes to mapping...
			aType = aType.baseType();
		}
	}

	/// <summary>
	/// Adds those objects of types that are not found in the SAB.
	/// </summary>
	private void InstantiatePassiveObjects(ActionSystem m_actionSystem, MainModule mainModule)
	{
		for (final Identifier x: mainModule.symbolTable().symbolList())
		{
			if (x.type().kind() == TypeKind.OoActionSystemType && !objectsdone.containsKey(x.type()))
				if (((OoActionSystemType)x.type()).autoConstruction())
				{
					final OoActionSystemType aType = (OoActionSystemType)x.type();
					m_ParserState.AddErrorMessage(new ParserError(m_ParserState.filename, aType.identifier().line(), aType.identifier().column(),
							String.format("Type '%s' marked autocons but omitted in system description!", aType.toString())));
				}
				else
				{
					final OoActionSystemType aType = (OoActionSystemType)x.type();
					for (final OoActionSystemInstance obj: aType.objects())
					{
						m_toReplace = new OoasCompilerHashMap<Object, Object>(objectsdone);
						addParentClassMappings(m_toReplace, aType, m_actionSystem);
						self = new SelfTypeIdentifier(obj.Name, aType, null);
						final SymbolTable table = aType.getAllSymbols();
						for (final Identifier y: table.symbolList())
						{
							// resolve attributes first
							if (y.kind() == IdentifierKind.AttributeIdentifier)
								DuplicateAttribute((AttributeIdentifier)y, obj, aType, m_actionSystem);
						}
						for (final Identifier y: table.symbolList())
						{
							// now do methods
							if (y.kind() == IdentifierKind.MethodIdentifier)
								DuplicateMethod((MethodIdentifier)y, obj, aType, m_actionSystem);
							//else if (y.kind == IdentifierKind.NamedActionIdentifier)
							//    m_ParserState.AddErrorMessage(new ParserError(m_ParserState.filename, aType.identifier.line, aType.identifier.column,
							//        String.Format("Type '%s' has named actions but was omitted in system description!", aType.ToString())));
						}
						if (aType.doOdBlock() != null)
							m_ParserState.AddErrorMessage(new ParserError(m_ParserState.filename, aType.identifier().line(), aType.identifier().column(),
									String.format("Type '%s' has do-od block but was omitted in system description!", aType.toString())));
					}

				}
		}
	}





	@Override
	public void visit(MainModule mainModule)
	{
		if (mainModule.systemDescription() == null)
			return;

		// accept compression visitor
		if (m_compressor.compression() != LabelCompression.NoCompression)
			mainModule.Accept(m_compressor);


		/* determine names of objects */
		mainModule.Accept(m_simulator);

		final boolean shortObjectNames = m_compressor.compression() == LabelCompression.All
									  || m_compressor.compression() == LabelCompression.ActionsMethodsObjects;
		if (shortObjectNames) {
			// compress object names
			for (final OoActionSystemInstance instance: m_simulator.initialObjectWorkingSet()) {
				instance.Name = m_compressor.labelFor(instance);
			}
		}

		/* add information about all the objects to the AST */
		// mainModule.SetObjects(m_simulator.initialObjectWorkingSet);
		m_actionSystem = new ActionSystem();
		m_actionSystem.SetParentScope(mainModule);
		InstantiateObjects(m_actionSystem, mainModule, mainModule.systemDescription(), m_actionSystem.doOdBlock());
		InstantiatePassiveObjects(m_actionSystem, mainModule);

		mainModule.SetInstance(m_actionSystem);

		m_ParserState.mappingInformation = m_compressor.mappingInformation();
	}

	@Override
	public String toString()
	{
		return m_simulator.toString();
	}


	public OoaObjectInstantiationVisitor(ParserState aState, LabelCompression compression)
	{
		super (aState);
		m_simulator = new JavaInitVisitor(aState);
		m_compressor = new OoaIdentifierCompressVisitor(aState, compression);
	}

}
