/**
  *
  *                      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.codegen.java;

import java.util.ArrayList;
import java.util.HashSet;

import org.momut.ooas.ast.AstNodeTypeEnum;
import org.momut.ooas.ast.IAst;
import org.momut.ooas.ast.expressions.Expression;
import org.momut.ooas.ast.identifiers.AttributeIdentifier;
import org.momut.ooas.ast.identifiers.EnumIdentifier;
import org.momut.ooas.ast.identifiers.Identifier;
import org.momut.ooas.ast.identifiers.IdentifierKind;
import org.momut.ooas.ast.identifiers.MainModule;
import org.momut.ooas.ast.types.EnumType;
import org.momut.ooas.ast.types.OoActionSystemInstance;
import org.momut.ooas.ast.types.OoActionSystemType;
import org.momut.ooas.ast.types.TupleType;
import org.momut.ooas.ast.types.TypeKind;
import org.momut.ooas.ast.types.Type;
import org.momut.ooas.codegen.OoasCodeEmitter;
import org.momut.ooas.parser.ParserError;
import org.momut.ooas.parser.ParserMessage;
import org.momut.ooas.parser.ParserState;
import org.momut.ooas.visitors.OoaCompleteAstTraversalVisitor;

public final class JavaInitVisitor extends OoaCompleteAstTraversalVisitor
{
	private final String SimulationNameSpace = "org.momut.ooas.codegen.java.simulation";
	private final OoasCodeEmitter emitter;
	private final ArrayList<Type> hiddenTypesStillToEmit = new ArrayList<Type>();
	private final Scratchbook scratchbook = new Scratchbook();
	private StringBuilder helpers = new StringBuilder();



	private final ArrayList<OoActionSystemType> autoConsList = new ArrayList<OoActionSystemType>();

	private int m_maxObjectNameLength = 0;
	private final ArrayList<String> m_objectNames = new ArrayList<String>();
	private final ArrayList<OoActionSystemInstance> m_initialObjectWorkingSet = new ArrayList<OoActionSystemInstance>();



	public int maxObjectNameLength() { return m_maxObjectNameLength; }
	public ArrayList<String> objectNames() { return m_objectNames; }
	public ArrayList<OoActionSystemInstance> initialObjectWorkingSet() { return m_initialObjectWorkingSet; }
	public HashSet<String> givenObjectNames = new HashSet<String>();


	public void Error(int line, int pos, String p)
	{
		final ParserError error = new ParserError(m_ParserState.filename, line, pos, p);
		m_ParserState.AddErrorMessage(error);
	}


//	private void Error(OoActionSystemInstance id, String p)
//	{
//		int line = id.Type.identifier().line();
//		int pos = id.Type.identifier().column();
//
//		ParserError error = new ParserError(m_ParserState.filename, line, pos, p);
//		m_ParserState.AddErrorMessage(error);
//	}
//	private void Warning(OoActionSystemType atype, String p)
//	{
//		ParserWarning warn = new ParserWarning(m_ParserState.filename, atype.identifier().line(),
//				atype.identifier().column(), p);
//		m_ParserState.AddWarningMessage(warn);
//	}

	private String GetIdentifierString(String anIdentifier)
	{
		return JavaIdentifier.GetIdentifierString(anIdentifier);
	}
	private final ArrayList<OoActionSystemType> AstTypeLookup = new ArrayList<OoActionSystemType>();

	/// <summary>
	/// here we are called when a new object has been created
	/// </summary>
	void visit(ActionSystemClass actionSystemClass, int ConstrNum)
	{
//		OoActionSystemType aParentType = actionSystemClass.parent != null
//			? AstTypeLookup.get(actionSystemClass.parent._AstTypeLookupIndex)
//			: null;
		OoActionSystemType atype = AstTypeLookup.get(actionSystemClass._AstTypeLookupIndex);
		final OoActionSystemInstance aParentInstance = actionSystemClass.parent != null
			? actionSystemClass.parent.objectInstance
			: null;

		final HashSet<Type> seenTypes = new HashSet<Type>();
		final StringBuilder instantiationPath = new StringBuilder();
		if (aParentInstance != null)
		{
			OoActionSystemInstance tmp = aParentInstance;
			while (tmp != null)
			{
				instantiationPath.append(String.format("<-%s", tmp.Name));
				seenTypes.add(tmp.Type);
				tmp = tmp.ParentObject;
			}
		}

		Type tmpType = atype;
		while (tmpType != null)
		{
			if (seenTypes.contains(tmpType))
			{
				throw new JavaInitVisitorCompileException(String.format("Cycle in object creation. %s", instantiationPath.toString()));
			}
			else
				tmpType = ((OoActionSystemType)tmpType).baseType();
		}

		actionSystemClass.objectInstance =
				new OoActionSystemInstance(actionSystemClass.objectName, atype, actionSystemClass, aParentInstance);

		// update max length of object name
		m_maxObjectNameLength = actionSystemClass.objectName.length() > m_maxObjectNameLength
			? actionSystemClass.objectName.length()
			: m_maxObjectNameLength;

		// add to list of object names
		m_objectNames.add(actionSystemClass.objectName);

		// add to global list of initial objects
		m_initialObjectWorkingSet.add(actionSystemClass.objectInstance);

		m_ParserState.AddMessage(new ParserMessage(m_ParserState.filename,
			atype.identifier().line(), atype.identifier().column(), String.format("Creating object: %s", actionSystemClass.objectName)));

		atype.objects().add(actionSystemClass.objectInstance);
		// also add the object to the base types
		atype = atype.baseType();
		while (atype != null)
		{
			atype.derivedObjects().add(actionSystemClass.objectInstance);
			atype = atype.baseType();
		}
		atype = null;

		if (ConstrNum >= 0)
			scratchbook.constructors().get(ConstrNum).AddInstance(actionSystemClass.objectInstance);
	}
	private String GetCSharpType(Type aType)
	{
		if (aType.isAnonymousType())
			hiddenTypesStillToEmit.add(aType);

		final JavaType sharptype = new JavaType();
		aType.Accept(sharptype);
		return sharptype.toString();
	}
	private String GetCSharpExpression(Expression anExpression)
	{
		final JavaExpression sharpexpression = new JavaExpression(helpers, scratchbook);
		anExpression.Accept(sharpexpression);
		return sharpexpression.toString();
	}

	private void StartSimulation(String p)
	{
		try
		{
			// set reference
			//ActionSystemClass.objectInstantiator = this;
			// compile
			final Class<?> simulationCode = JavaCodeCompiler.compileCode(p, "Wrapper", SimulationNameSpace, m_ParserState);
			// execute system initialization
			JavaCodeCompiler.ExecuteCode(simulationCode, "Start", this, m_ParserState);
		}
		catch (final JavaInitVisitorCompileException e)
		{
			final ParserError error = new ParserError("ObjectInstantiationVisitor.cs", 0, 0, e.getMessage());
			m_ParserState.AddErrorMessage(error);
		}
	}

	/// <summary>
	/// generates C# code that describes initialization of the action systems
	/// this is used to determine the initial working set of classes.
	/// </summary>
	private void Emit(OoActionSystemType ooActionSystemType)
	{
		final ArrayList<AttributeIdentifier> initializers = new ArrayList<AttributeIdentifier>();
		helpers = new StringBuilder();
		if (ooActionSystemType.baseType() == null)
			emitter.AppendLineIncIndent(String.format("public class %s extends org.momut.ooas.codegen.java.ActionSystemClass {", GetIdentifierString(ooActionSystemType.identifier().tokenText())));
		else
			emitter.AppendLineIncIndent(String.format("public class %s extends %s {", GetIdentifierString(ooActionSystemType.identifier().tokenText()), GetIdentifierString(ooActionSystemType.baseType().identifier().tokenText())));

		for (final Identifier attr: ooActionSystemType.symbols().symbolList())
		{
			if (attr.kind() == IdentifierKind.AttributeIdentifier)
			{
				final String staticdecl = ((AttributeIdentifier)attr).isStatic() ? "static" : "";
				emitter.AppendLine(String.format("public %s %s %s;",
						staticdecl,
						GetCSharpType(attr.type()),
						GetIdentifierString(attr.tokenText())));
				initializers.add(((AttributeIdentifier)attr));
			}
		}

		// emit internal constructor that initializes object
		emitter.AppendLineIncIndent(String.format("protected %s (org.momut.ooas.codegen.java.ActionSystemClass parent, int constrNum, int LookupIndex, String aGivenName) {", GetIdentifierString(ooActionSystemType.identifier().tokenText())));
		emitter.AppendLine("this(parent, constrNum, LookupIndex, aGivenName, m_visitor);");
		emitter.AppendLineDecIndent("}");

		emitter.AppendLineIncIndent(String.format("protected %s (org.momut.ooas.codegen.java.ActionSystemClass parent, int constrNum, int LookupIndex, String aGivenName, org.momut.ooas.codegen.java.JavaInitVisitor visitor) {", GetIdentifierString(ooActionSystemType.identifier().tokenText())));
		emitter.AppendLine("super(parent, constrNum, LookupIndex, aGivenName, visitor);");
		for (final AttributeIdentifier attr: initializers)
			emitter.AppendLine(String.format("%s = %s;", GetIdentifierString(attr.tokenText()), GetCSharpExpression(attr.initializer())));
		emitter.AppendLineDecIndent("}");


		// emit public constructor
		emitter.AppendLine(String.format("public %s (org.momut.ooas.codegen.java.ActionSystemClass parent, int constrNum) {this(parent, constrNum, %s, \"\");}", GetIdentifierString(ooActionSystemType.identifier().tokenText()), AstTypeLookup.size()));

		// emit public constructor with fixed name
		emitter.AppendLine(String.format("public %s (org.momut.ooas.codegen.java.ActionSystemClass parent, int constrNum, String aName) {this(parent, constrNum, %s, aName);}", GetIdentifierString(ooActionSystemType.identifier().tokenText()), AstTypeLookup.size()));


		AstTypeLookup.add(ooActionSystemType);

		// emit helpers
		emitter.Append(helpers.toString());

		emitter.AppendLineDecIndent("}");

		if (ooActionSystemType.autoConstruction())
			autoConsList.add(ooActionSystemType);
	}

	/// <summary>
	/// Emits type definition for tuple
	/// </summary>
	private void Emit(TupleType atupleType)
	{
		emitter.AppendLineIncIndent(String.format("public static class %s {", GetIdentifierString(atupleType.identifier().tokenText())));
		int i = 0;
		for (final Type it: atupleType.innerTypes())
		{
			emitter.AppendLine(String.format("public %s elem_%s;", GetCSharpType(it), Integer.toString(i)));
			i++;
		}

		emitter.AppendLine(" /*constr*/ ");
		emitter.Append(String.format("public %s (", GetIdentifierString(atupleType.identifier().tokenText())));
		i = 0;
		for (final Type it: atupleType.innerTypes())
		{
			if (i != 0)
				emitter.Append(",");
			emitter.Append(String.format("%s arg_%s", GetCSharpType(it), Integer.toString(i)));
			i++;
		}
		emitter.AppendLineIncIndent(") {");
		for (i = 0; i < atupleType.innerTypes().size(); i++)
			emitter.AppendLine(String.format("elem_%s = arg_%s;", i,i));

		emitter.AppendLineDecIndent("}");

		emitter.AppendLineDecIndent("}");
	}

	/// <summary>
	/// Emits type definition for enum
	/// </summary>
	private void Emit(EnumType anEnum)
	{
		emitter.Append(String.format("public enum %s {", GetIdentifierString(anEnum.identifier().tokenText())));
		int i = 0;
		for (final EnumIdentifier it: anEnum.listOfEnumSymbols())
		{
			if (i != 0)
				emitter.Append(", ");
			else
				i++;
			emitter.Append(GetIdentifierString(it.tokenText()));
		}
		emitter.AppendLine("}");
	}

	@Override
	protected void VisitAstElement(IAst element, IAst parent)
	{
		if (element.nodeType() == AstNodeTypeEnum.type &&
				((Type)element).kind() == TypeKind.OoActionSystemType)
		{
			/*OoActionSystemType asystem = (OoActionSystemType)element;
			if (asystem.autoConstruction)
                CheckForRecursiveNew(asystem);*/
			Emit((OoActionSystemType)element);
		}
		else if (element.nodeType() == AstNodeTypeEnum.type &&
				((Type)element).kind() == TypeKind.TupleType)
		{
			Emit((TupleType)element);
		}
		else if (element.nodeType() == AstNodeTypeEnum.type &&
				((Type)element).kind() == TypeKind.EnumeratedType)
		{
			Emit((EnumType)element);
		}
		else
			super.VisitAstElement(element, parent);
	}

	@Override
	public void visit(MainModule mainModule)
	{
		scratchbook.constructors().clear();
		emitter.AppendLine(String.format("package %s;",SimulationNameSpace));
//		emitter.AppendLine("import org.momut.ooas.codegen.java.*;");
		emitter.AppendLine("");

		emitter.AppendLineIncIndent("public final class Wrapper {");
		emitter.AppendLine("private org.momut.ooas.codegen.java.JavaInitVisitor m_visitor;");

		super.visit(mainModule);

		emitter.AppendLine(" /* == anonymous types == */ ");
		for (final Type x: hiddenTypesStillToEmit)
		{
			// we only need to define tuples and enums..
			if (x.kind() == TypeKind.TupleType)
				Emit((TupleType)x);
			else if (x.kind() == TypeKind.EnumeratedType)
				Emit((EnumType)x);
		}

		emitter.AppendLine(" /* == create autocons objects == */ ");
		emitter.AppendLineIncIndent("public void start(org.momut.ooas.codegen.java.JavaInitVisitor visitor){");
		emitter.AppendLine("m_visitor = visitor;");
		for (final OoActionSystemType x: autoConsList)
			emitter.AppendLine(String.format("new %s(null,-1 /*autocons*/);", GetIdentifierString(x.identifier().tokenText())));
		emitter.AppendLine("m_visitor = null;");
		emitter.AppendLineDecIndent("}");

		emitter.AppendLine(" /* == setup wrapper class, init object and run the code == */ ");
		emitter.AppendLineIncIndent("public static void Start(org.momut.ooas.codegen.java.JavaInitVisitor visitor){");
		emitter.AppendLine("final Wrapper wrapper = new Wrapper();");
		emitter.AppendLine("wrapper.start(visitor);");
		emitter.AppendLineDecIndent("}");

		emitter.AppendLineDecIndent("}"); // wrapper

		StartSimulation(emitter.toString());
	}


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

	public JavaInitVisitor(ParserState state)
	{
		super (state);
		emitter = new OoasCodeEmitter();
	}

}
