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

import java.util.ArrayList;
import java.util.Date;

import org.momut.ooas.ast.identifiers.AttributeIdentifier;
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.identifiers.MethodIdentifier;
import org.momut.ooas.ast.identifiers.NamedActionIdentifier;
import org.momut.ooas.ast.identifiers.ParameterIdentifier;
import org.momut.ooas.ast.identifiers.TypeIdentifier;
import org.momut.ooas.ast.statements.Block;
import org.momut.ooas.ast.types.FunctionType;
import org.momut.ooas.ast.types.FunctionType.FunctionTypeEnum;
import org.momut.ooas.codegen.OoasCodeEmitter;
import org.momut.ooas.parser.ParserState;
import org.momut.ooas.utils.OoasCompilerHashMap;
import org.momut.ooas.visitors.OoaCompleteAstTraversalVisitor;

public class OoaPrologVisitor extends OoaCompleteAstTraversalVisitor
{
	protected OoasCodeEmitter m_emitter;
	protected final Scratchbook m_scratchbook = new Scratchbook();

//	private String PrologIdentifier(Identifier id)
//	{
//		OoaPrologIdentifier identifierVisitor = m_idFactory.create();
//		id.Accept(identifierVisitor);
//		return identifierVisitor.toString();
//	}

	protected void ExportMethodsAndActions(MainModule mainModule)
	{
		final StringBuilder varActions = new StringBuilder();
		m_emitter.AppendLine();
		m_emitter.AppendLine("% action system");
		m_emitter.AppendLineIncIndent("as :- ");

		/* export methods */
		m_emitter.AppendLine("methods (");
		ExportMethods(mainModule);
		m_emitter.AppendLine("), ");
		m_emitter.AppendLine("actions (");
		ExportNamedActions(mainModule, varActions);
		m_emitter.AppendLine("), ");
		m_emitter.AppendLine("dood (");

		ExportDoOdBlock(mainModule.instance().doOdBlock(), m_emitter);


		m_emitter.AppendLine("), qdes (none)");
		m_emitter.DecIndent();
		m_emitter.AppendLine(".");
		m_emitter.AppendLine("");

		m_emitter.Append(varActions.toString());
	}

	private void ExportMethods(MainModule mainModule)
	{
		int y = 0;
		for (final Identifier x: mainModule.instance().symbols().symbolList())
		{
			if (x.kind() == IdentifierKind.MethodIdentifier)
			{
				if (y != 0)
					m_emitter.AppendLine(", ");
				else
					y++;

				final MethodIdentifier theMethod = (MethodIdentifier)x;
				final OoaPrologIdentifier pIdent = m_idFactory.create();
				theMethod.Accept(pIdent);

				/* method */
				final StringBuilder parameter = new StringBuilder();
				if (theMethod.parameter().size() > 0 || ((FunctionType)theMethod.type()).returnType() != null)
				{
					parameter.append("(");
					int z = 0;
					for (final ParameterIdentifier param: theMethod.parameter())
					{
						if (z != 0)
						{
							parameter.append(", ");
						}
						else
							z++;
						final OoaPrologIdentifier pParamIdent = m_idFactory.create();
						param.Accept(pParamIdent);
						parameter.append(pParamIdent.toString());
					}


					if (((FunctionType)theMethod.type()).returnType() != null)
					{
						if (z != 0)
							parameter.append(", ");
						parameter.append("RESULT");
					}
					parameter.append(")");
				}
				m_emitter.AppendLineIncIndent(String.format("%s%s = (", pIdent.toString(), parameter.toString()));

				final OoaPrologStatement pBody = createStatementVisitor();
				((MethodIdentifier)x).body().Accept(pBody);
				m_emitter.Append(pBody.toString());
				m_emitter.Append(")");
				m_emitter.DecIndent();
			}
		}
		if (y == 0)
			m_emitter.Append(" none ");
	}

	private void ExportNamedActions(MainModule mainModule, StringBuilder varActions)
	{
		int y = 0;
		for (final Identifier x: mainModule.instance().symbols().symbolList())
		{
			if (x.kind() == IdentifierKind.NamedActionIdentifier)
			{
				if (y != 0)
					m_emitter.AppendLine(", ");
				else
					y++;
				final NamedActionIdentifier action = (NamedActionIdentifier)x;
				final OoaPrologIdentifier pIdent = m_idFactory.create();
				action.Accept(pIdent);

				if (((FunctionType)action.type()).functionType() != FunctionTypeEnum.Continuous)
				{
					/* discrete actions */
					final StringBuilder parameter = new StringBuilder();
					final StringBuilder parametertype = new StringBuilder();
					if (action.parameter().size() > 0)
					{
						parameter.append("(");
						parametertype.append("(");
						int z = 0;
						for (final ParameterIdentifier param: action.parameter())
						{
							if (z != 0)
							{
								parameter.append(", ");
								parametertype.append(", ");
							}
							else
								z++;
							final OoaPrologIdentifier pParamIdent = m_idFactory.create();
							param.Accept(pParamIdent);
							parameter.append(pParamIdent.toString());

							final OoaPrologType pParamType = createTypeVisitor();  // bugfix me!
							param.type().Accept(pParamType);
							parametertype.append(pParamType.toString());

						}
						parametertype.append(")");
						parameter.append(")");
					}
					//if (parametertype.Length > 0)
					//    varActions.AppendLine(String.format("%s%s.", pIdent.toString(), parametertype.toString()));
					m_emitter.AppendLineIncIndent(String.format("%s%s::", pIdent.toString(), parameter.toString()));
				}
				else
				{
					// we do not support continuous actions anymore.
					throw new UnsupportedOperationException();
//					/* observable state vars outside... */
//					final StringBuilder parameter = new StringBuilder();
//					int z = 0;
//					for (final Statement stmt : ((GuardedCommand)action.body().statements().peekFirst()).body().statements())
//					{
//						final QualitativeConstraintStatement qstmt = (QualitativeConstraintStatement)stmt;
//						if (qstmt.operation() == QualitativeConstraintOperation.Equal
//								&& qstmt.variable1().kind() == IdentifierKind.AttributeIdentifier
//								&& ((AttributeIdentifier)qstmt.variable1()).isObservable())
//						{
//							if (z != 0)
//								parameter.append(", ");
//							else
//								z++;
//							qstmt.tag = true;
//							final OoaPrologIdentifier pParamIdent1 = m_idFactory.create();
//							final OoaPrologIdentifier pParamIdent2 = m_idFactory.create();
//							qstmt.variable0().Accept(pParamIdent1);
//							qstmt.variable1().Accept(pParamIdent2);
//							parameter.append(String.format("%s(%s)", pParamIdent2.toString(), pParamIdent1.toString()));
//						}
//					}
//					m_emitter.AppendLineIncIndent(String.format("[%s]::", parameter.toString()));
				}

				final OoaPrologStatement pBody = createStatementVisitor();
				((NamedActionIdentifier)x).body().Accept(pBody);
				m_emitter.Append(pBody.toString());
				m_emitter.DecIndent();

			}
		}
	}

	private void ExportDoOdBlock(Block block, OoasCodeEmitter m_emitter)
	{
		final OoaPrologStatement statement = createStatementVisitor();
		block.Accept(statement);
		m_emitter.Append(statement.toString());
	}

	protected int ExportActions(MainModule mainModule, String predName, FunctionTypeEnum actionKind)
	{
		m_emitter.AppendLine("");
		m_emitter.AppendLine("%emit " + actionKind.toString() + " actions");
		m_emitter.Append(predName + "([");
		int y = 0;
		for (final Identifier x: mainModule.instance().symbols().symbolList())
		{
			if (x.kind() == IdentifierKind.NamedActionIdentifier)
			{
				if (((FunctionType)((NamedActionIdentifier)x).type()).functionType() == actionKind)
				{
					if (y != 0)
						m_emitter.Append(", ");
					else
						y++;
					final OoaPrologIdentifier pIdent = m_idFactory.create();

					((NamedActionIdentifier)x).Accept(pIdent);
					m_emitter.Append(pIdent.toString());
				}
			}
		}
		m_emitter.AppendLine("]).");
		return y;
	}

	protected int ExportControllableActions(MainModule mainModule) {
		return ExportActions(mainModule, "input", FunctionTypeEnum.Controllable);
	}

	protected int ExportObservableActions(MainModule mainModule) {
		return ExportActions(mainModule, "output", FunctionTypeEnum.Observable);
	}

	protected void ExportInitialState(MainModule mainModule)
	{
		m_emitter.AppendLine("");
		m_emitter.AppendLine("%emit initial state");
		final ArrayList<String> initPreds = new ArrayList<String>();
		final ArrayList<String> initVars = new ArrayList<String>();
		int y = 0;
		for (final Identifier x: mainModule.instance().symbols().symbolList())
		{
			if (x.kind() == IdentifierKind.AttributeIdentifier)
			{
				final OoaPrologExpression anExprVis = createExpressionVisitor();
				((AttributeIdentifier)x).initializer().Accept(anExprVis);
				assert(anExprVis.tmpVariables().size() == 1);
				final String initPredName = String.format("attr_%s_init(%s)", x.tokenText(), anExprVis.tmpVariables().get(0));
				m_emitter.AppendLine(String.format("%s :- %s 1=1.", initPredName, anExprVis.toString()));
				initPreds.add(initPredName);
				initVars.add(anExprVis.tmpVariables().get(0));
			}
		}


		m_emitter.Append("init([");
		y = 0;
		for (final String x: initVars)
		{
			if (y != 0)
				m_emitter.Append(",");
			else
				y++;
			m_emitter.Append(x);
		}
		m_emitter.Append("]) :-");
		y = 0;
		for (final String x: initPreds)
		{
			if (y != 0)
				m_emitter.Append(",");
			else
				y++;
			m_emitter.Append(x);
		}
		m_emitter.AppendLine(".");
	}

	protected int ExportState(MainModule mainModule)
	{
		m_emitter.AppendLine("");
		m_emitter.AppendLine("%emit state definition");
		m_emitter.Append("state_def([");
		int y = 0;
		for (final Identifier x: mainModule.instance().symbols().symbolList())
		{
			if (x.kind() == IdentifierKind.AttributeIdentifier)
			{
				if (y != 0)
					m_emitter.Append(", ");
				else
					y++;
				final OoaPrologIdentifier pid = m_idFactory.create();
				x.Accept(pid);
				m_emitter.Append(pid.toString());
			}
		}
		m_emitter.AppendLine("]).");
		return y;
	}

	protected void ExportVariables(MainModule mainModule)
	{
		m_emitter.AppendLine("");
		m_emitter.AppendLine("%emit type of state variables");
		final OoasCompilerHashMap<String, StringBuilder> varlist = new OoasCompilerHashMap<String, StringBuilder>();
		for (final Identifier x: mainModule.instance().symbols().symbolList())
		{
			if (x.kind() == IdentifierKind.AttributeIdentifier)
			{
				final OoaPrologIdentifier pid = m_idFactory.create();
				final OoaPrologType ptype = createTypeVisitor();
				x.Accept(pid);
				x.type().Accept(ptype);

				final String key = ptype.toString();
				if (varlist.containsKey(key))
				{
					varlist.get(key).append(String.format(", %s", pid.toString()));
				}
				else
				{
					varlist.putSafe(key, new StringBuilder(String.format("[%s", pid.toString())));
				}
			}
		}
		for (final String key: varlist.keySet())
			m_emitter.AppendLine(String.format("var(%s], %s).", varlist.get(key), key));
	}

	protected void ExportTypes(MainModule mainModule)
	{
		for (final Identifier x: mainModule.symbolTable().symbolList())
		{
			if (x.kind() == IdentifierKind.TypeIdentifier)
				OoaPrologType.EmitType(((TypeIdentifier)x).type(), m_idFactory, m_scratchbook);
		}
	}


	@Override
	public /*override*/ void visit(MainModule mainModule)
	{
		m_emitter.AppendLine("% OOAS Compiler Generated");
//		m_emitter.AppendLine(String.format("%%%s", Mogentes.Program.GetProgramVersion().toString().Replace(Environment.NewLine, Environment.NewLine + "%")));
		m_emitter.AppendLine("% Code-Gen Version: 0.2");
		m_emitter.Append("%        Generated: "); m_emitter.AppendLine(new Date().toString());
		m_emitter.AppendLine("%------------------------------------------------------------------------");
		m_emitter.AppendLine("");
		m_emitter.AppendLine(String.format(":- module(%s, [var/2, input/1, searchDepth/1, qspace/2]).", m_nameSpace));
		m_emitter.AppendLine(":- use_module(library(file_systems)).");
		m_emitter.AppendLine(":- use_module(library(process)).");
		m_emitter.AppendLine(":- use_module(library(clpfd)).");
		m_emitter.AppendLine(":- use_module(library(clpb)).");
		m_emitter.AppendLine(":- use_module(library(lists)).");
		m_emitter.AppendLine(":- use_module(library(avl)).");
		m_emitter.AppendLine(":- use_module(library(ordsets)).");
		m_emitter.AppendLine(":- use_module(library(system)).");
		m_emitter.AppendLine("% :- use_module(library(gauge)). % this is a debug lib not supported on Win.");
		m_emitter.AppendLine(":- use_module(library(terms)).");
		m_emitter.AppendLine(":- use_module(library(sets)).");
		m_emitter.AppendLine(":- use_module(library(random)).");

		m_emitter.AppendLine(":- use_module(ulysseslib, _, [ulyssesListHead/2, ulyssesListLength/2, ulyssesListTail/2, ulyssesListConc/3, ulyssesTupleAccess/3, ulyssesListAccess/3, ulyssesListWrite/4, ulyssesMapAccess/3]).");

		m_emitter.AppendLine(":- public(as/0).");
		m_emitter.AppendLine(":- dynamic(as/0).");
		m_emitter.AppendLine(":- dynamic(qstate_init/1).");
		m_emitter.AppendLine(":- dynamic(qstate_constraints/1).");
		m_emitter.AppendLine(":- public(qstate_init/1).");
		m_emitter.AppendLine(":- public(qstate_constraints/1).");

		/*definition of search depth*/
		m_emitter.AppendLine("");
		m_emitter.AppendLine("% maximal search depth (change at will)");
		// FIXME: HACK
		m_emitter.AppendLine(String.format("searchDepth(%s).", m_maxSearchDepth));
		m_emitter.AppendLine("");

		/*definition of types*/
		ExportTypes(mainModule);
		/* variable definition */
		ExportVariables(mainModule);

		/* state definition */
		ExportState(mainModule);
		/* initial state */
		ExportInitialState(mainModule);
		/* input actions (controllables) */
		ExportControllableActions(mainModule);
		/* output actions (observables) */
		ExportObservableActions(mainModule);
		/* named actions */
		ExportMethodsAndActions(mainModule);

		/* all types */
		m_emitter.AppendLine("%definition of types");
		m_emitter.AppendLine(m_scratchbook.typeDefinitions().toString());
		m_emitter.AppendLine(createTypeVisitor().GetInternalTypeDefinitions());

		for(final String line: m_ParserState.mappingInformation)
			m_emitter.AppendLine("% " + line);
	}


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

	protected OoaPrologIdentifier createIdentifierVisitor() { return m_idFactory.create(); }
	protected OoaPrologType createTypeVisitor() { return m_typeFactory.create(m_idFactory, m_scratchbook); }
	protected OoaPrologExpression createExpressionVisitor() { return m_exprFactory.create(m_idFactory, m_typeFactory, m_scratchbook); }
	protected OoaPrologStatement createStatementVisitor() { return m_stmtFactory.create(m_exprFactory, m_idFactory, m_typeFactory, m_scratchbook); }
	protected final String m_nameSpace;
	protected final int m_maxSearchDepth;


	private final  OoaPrologExpression.Factory m_exprFactory;
	private final OoaPrologIdentifier.Factory m_idFactory;
	private final OoaPrologStatement.Factory m_stmtFactory;
	private final OoaPrologType.Factory m_typeFactory;

	protected OoaPrologVisitor(
			ParserState aState,
			int maxSearchDepth,
			String nameSpace,
			OoaPrologExpression.Factory exprFactory,
			OoaPrologIdentifier.Factory idFactory,
			OoaPrologStatement.Factory  stmtFactory,
			OoaPrologType.Factory       tpeFactory)
	{
		super(aState);
		m_maxSearchDepth = maxSearchDepth;
		m_nameSpace = nameSpace;
		m_emitter = new OoasCodeEmitter();
		m_exprFactory = exprFactory;
		m_idFactory = idFactory;
		m_stmtFactory = stmtFactory;
		m_typeFactory = tpeFactory;
	}

	public OoaPrologVisitor(ParserState aState, int maxSearchDepth, String nameSpace) {
		this(
				aState,
				maxSearchDepth,
				nameSpace,
				new OoaPrologExpression.Factory(),
				new OoaPrologIdentifier.Factory(),
				new OoaPrologStatement.Factory(),
				new OoaPrologType.Factory());
	}
}
