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

import org.momut.ooas.ast.expressions.AccessExpression;
import org.momut.ooas.ast.expressions.CallExpression;
import org.momut.ooas.ast.expressions.Expression;
import org.momut.ooas.ast.expressions.ExpressionKind;
import org.momut.ooas.ast.expressions.IdentifierExpression;
import org.momut.ooas.ast.identifiers.Identifier;
import org.momut.ooas.ast.identifiers.IdentifierKind;
import org.momut.ooas.ast.identifiers.NamedActionIdentifier;
import org.momut.ooas.ast.statements.AbortStatement;
import org.momut.ooas.ast.statements.Assignment;
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.FunctionType;
import org.momut.ooas.ast.types.TypeKind;
import org.momut.ooas.ast.types.FunctionType.FunctionTypeEnum;
import org.momut.ooas.codegen.OoasCodeEmitter;
import org.momut.ooas.utils.exceptions.NotImplementedException;
import org.momut.ooas.visitors.OoaStatementVisitor;

public class OoaPrologStatement extends OoaStatementVisitor
{
	public static class Factory
	{
		public OoaPrologStatement create(
				OoaPrologExpression.Factory exprFactory,
				OoaPrologIdentifier.Factory idFactory,
				OoaPrologType.Factory typeFactory,
				Scratchbook scratchbook)
		{
			return new OoaPrologStatement(exprFactory, idFactory, typeFactory, scratchbook);
		}
	}

	protected final Scratchbook m_scratchbook;
	protected OoasCodeEmitter m_emitter = new OoasCodeEmitter();

	@Override
	public void visit(NondetBlock nondetBlock)
	{
		int y = 0;
		for (final Statement x: nondetBlock.statements())
		{
			if (y != 0)
				m_emitter.Append("; ");
			else
				y++;
			m_emitter.Append("(");
			VisitSub(x, nondetBlock);
			m_emitter.Append(")");
		}
	}

	@Override
	public void visit(SeqBlock seqBlock)
	{
		int y = 0;
		if (seqBlock.symbols().symbolList().size() > 0)
		{
			m_emitter.Append("([");
			for (final Identifier sym: seqBlock.symbols().symbolList())
			{
				if (y != 0)
					m_emitter.AppendLine(", ");
				else
					y++;

				final OoaPrologType atype = createTypeVisitor();
				sym.type().Accept(atype);
				final OoaPrologIdentifier anIdent = createIdentifierVisitor();
				sym.Accept(anIdent);
				m_emitter.Append(String.format("%s:%s", anIdent.toString(), atype.toString()));
			}
			m_emitter.Append("]: ");
		}

		y = 0;
		for (final Statement x: seqBlock.statements())
		{
			if (y != 0)
				m_emitter.AppendLine(", ");
			else
				y++;
			m_emitter.Append("(");
			VisitSub(x, seqBlock);
			m_emitter.Append(")");
		}

		if (seqBlock.symbols().symbolList().size() > 0)
			m_emitter.Append(")");
	}

	@Override
	public void visit(PrioBlock prioBlock)
	{
		int y = 0;
		for (final Statement x: prioBlock.statements())
		{
			if (y != 0)
				m_emitter.Append("// ");
			else
				y++;
			m_emitter.Append("(");
			VisitSub(x, prioBlock);
			m_emitter.Append(")");
		}
	}

	@Override
	public void visit(GuardedCommand guardedCommand)
	{
		final OoaPrologExpression expr = createExpressionVisitor();
		guardedCommand.guard().Accept(expr);

		m_emitter.AppendLineIncIndent(String.format("(%s%s) %s ",
				expr.toString(),
				expr.tmpVariables().get(0),
				guardedCommand.isQualitative() ? "~>" : "=>"));
		m_emitter.AppendLineIncIndent("(");
		VisitSub(guardedCommand.body(), guardedCommand);
		m_emitter.DecIndent();
		m_emitter.AppendLine("");
		m_emitter.Append(")");
	}

	@Override
	public void visit(Assignment assignment)
	{
		if (assignment.nondetExpression() != null)
			throw new NotImplementedException();

		final Iterator<Expression> aPlaceIt = assignment.places().iterator();
		final Iterator<Expression> aValueIt = assignment.values().iterator();
		final ArrayList<String> assignments = new ArrayList<String>();

		while (aPlaceIt.hasNext())
		{
			final Expression aPlace = aPlaceIt.next();
			final Expression aValue = aValueIt.next();

			final OoaPrologExpression prologPlace = createExpressionVisitor(true);
			aPlace.Accept(prologPlace);
			assert(prologPlace.tmpVariables().size() == 1);

			final OoaPrologExpression prologValue = createExpressionVisitor();
			aValue.Accept(prologValue);
			assert(prologValue.tmpVariables().size() == 1);

			m_emitter.Append(prologValue.toString());

			if (aPlace.kind() == ExpressionKind.Access &&
					((AccessExpression)aPlace).right().kind() == ExpressionKind.Identifier &&
					((IdentifierExpression)((AccessExpression)aPlace).right()).identifier().kind() == IdentifierKind.AttributeIdentifier)
			{
				//access to attribute is always 'self.XY'...
				assignments.add(String.format("%s := %s", prologPlace.tmpVariables().get(0), prologValue.tmpVariables().get(0)));
			}
			else
				if (prologPlace.tmpVariables().get(0).equals("RESULT"))
					assignments.add(String.format("unify( RESULT = %s)", prologValue.tmpVariables().get(0)));
				else if (aPlace.type().kind() == TypeKind.IntType)
					assignments.add(String.format("%s #= %s", prologPlace.tmpVariables().get(0), prologValue.tmpVariables().get(0)));
				else
					assignments.add(String.format("%s = %s", prologPlace.tmpVariables().get(0), prologValue.tmpVariables().get(0)));

			if (prologPlace.toString().length() > 0)
			{
				String place = prologPlace.toString();
				place = place.trim();
				if (place.endsWith(","))
					place = place.substring(0, place.length() - 1);

				assignments.add(place);
			}
		}

		int pos = 0;
		for (final String s: assignments)
		{
			if (pos != 0)
				m_emitter.Append(",");
			pos++;
			m_emitter.Append(s);
		}
	}

	@Override
	public void visit(Call call)
	{
		final Expression ce = call.callExpression();
		String callstatement = "";
		final StringBuilder parameter = new StringBuilder();
		boolean hideCall = false;

		if (ce.kind() == ExpressionKind.Identifier)
			callstatement = ((IdentifierExpression)ce).identifier().tokenText();
		else if (ce.kind() == ExpressionKind.Call)
		{
			final CallExpression parens = (CallExpression)ce;

			final OoaPrologExpression prologexpr = createExpressionVisitor();
			parens.child().Accept(prologexpr);

			if (parens.child().kind() == ExpressionKind.Identifier &&
					((IdentifierExpression)parens.child()).identifier().kind() == IdentifierKind.NamedActionIdentifier)
			{
				// we need to hide internal actions
				final NamedActionIdentifier namedactionid = (NamedActionIdentifier)((IdentifierExpression)parens.child()).identifier();
				hideCall = ((FunctionType)namedactionid.type()).functionType() == FunctionTypeEnum.Internal;
			}

			m_emitter.Append(prologexpr.toString());
			callstatement = prologexpr.tmpVariables().get(0);
			int i = 0;
			for (final Expression arg: parens.arguments())
			{
				if (i != 0)
					parameter.append(", ");
				else
					i++;
				final OoaPrologExpression paramexpr = createExpressionVisitor();
				arg.Accept(paramexpr);
				m_emitter.Append(paramexpr.toString());
				parameter.append(paramexpr.tmpVariables().get(0));
			}
		}
		else
			throw new NotImplementedException();
		if (hideCall)
			m_emitter.Append("i(");
		if (parameter.length() > 0)
			m_emitter.AppendLine(String.format("%s(%s)", callstatement, parameter));
		else
			m_emitter.AppendLine(String.format("%s", callstatement, parameter));
		if (hideCall)
			m_emitter.Append(")");
	}

	@Override
	public void visit(SkipStatement skipStatement)
	{
		m_emitter.Append("skip");
	}

	@Override
	public void visit(AbortStatement abortStatement)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(KillStatement killStatement)
	{
		throw new NotImplementedException();
	}

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

	protected OoaPrologExpression createExpressionVisitor() { return m_exprFactory.create(m_idFactory, m_typeFactory, m_scratchbook); }
	protected OoaPrologExpression createExpressionVisitor(boolean lhs) { return m_exprFactory.create(m_idFactory, m_typeFactory, m_scratchbook, lhs); }
	protected OoaPrologIdentifier createIdentifierVisitor() { return m_idFactory.create(); }
	protected OoaPrologType createTypeVisitor() { return m_typeFactory.create(m_idFactory, m_scratchbook); }

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

	protected OoaPrologStatement(
			OoaPrologExpression.Factory exprFactory,
			OoaPrologIdentifier.Factory idFactory,
			OoaPrologType.Factory typeFactory,
			Scratchbook scratchbook)
	{
		super();
		m_exprFactory = exprFactory;
		m_idFactory = idFactory;
		m_typeFactory = typeFactory;
		m_scratchbook = scratchbook;
	}
}

