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

import org.momut.ooas.ast.expressions.BinaryOperator;
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.expressions.TupleConstructor;
import org.momut.ooas.ast.expressions.UnaryOperator;
import org.momut.ooas.ast.expressions.ValueExpression;
import org.momut.ooas.ast.identifiers.EnumIdentifier;
import org.momut.ooas.ast.identifiers.IdentifierKind;
import org.momut.ooas.ast.types.EnumType;
import org.momut.ooas.ast.types.TypeKind;
import org.momut.ooas.ast.types.Type;
import org.momut.ooas.ast.types.ValuedEnumType;
import org.momut.ooas.codegen.OoasCodeEmitter;
import org.momut.ooas.codegen.prolog.OoaPrologExpression;
import org.momut.ooas.codegen.prolog.OoaPrologIdentifier;
import org.momut.ooas.codegen.prolog.OoaPrologType;
import org.momut.ooas.codegen.prolog.Scratchbook;
import org.momut.ooas.utils.exceptions.ArgumentException;
import org.momut.ooas.utils.exceptions.NotImplementedException;

public class OoaPrologSymbolicExpression extends OoaPrologExpression
{
	public static class Factory extends OoaPrologExpression.Factory
	{
		@Override
		public /*override*/ OoaPrologExpression create(OoaPrologIdentifier.Factory idFactory, OoaPrologType.Factory typeFactory, Scratchbook scratchbook, boolean lhs)
		{ return new OoaPrologSymbolicExpression(idFactory, typeFactory, scratchbook, this, lhs); }
	}

	/*Also treats bools and enums as ints..*/
	@Override
	protected boolean isNumericBinary(Expression expression)
	{
		boolean result = false;
		if (expression instanceof BinaryOperator)
		{
			final TypeKind leftKind = ((BinaryOperator)expression).left().type().kind();
			final TypeKind rightKind = ((BinaryOperator)expression).right().type().kind();
			result = (leftKind == TypeKind.IntType || leftKind == TypeKind.BoolType || leftKind == TypeKind.EnumeratedType) &&
					(rightKind == TypeKind.IntType || rightKind == TypeKind.BoolType || rightKind == TypeKind.EnumeratedType);
		}
		else if (expression instanceof UnaryOperator)
		{
			final TypeKind childKind = ((UnaryOperator)expression).child().type().kind();
			result = childKind == TypeKind.IntType || childKind == TypeKind.BoolType || childKind == TypeKind.EnumeratedType;
		}
		else throw new NotImplementedException();
		return result;
	}

	// we need to map all other things to Prolog constructs
	private String operatorString(Expression expression, Type resultingType)
	{
		switch (expression.kind())
		{
		case abs:    // T_ABS:
		case card:   // T_CARD:
		case dom:    // T_DOM:
		case range:  // T_RNG:
		case merge:  // T_MERGE:
		case elems:  // T_ELEMS:
		case head:   // T_HEAD:
		case tail:   // T_TAIL:
		case conc:   // T_CONC:
		case inds:   // T_INDS:
		case dinter: // T_DINTER:
		case dunion: // T_DUNION:
		case domresby:   // T_DOMRESBY:
		case domresto:   // T_DOMRESTO:
		case rngresby:   // T_RNGRESBY:
		case rngresto:   // T_RNGRESTO:
		case inter:  // T_INTER:
		case union:  // T_UNION:
		case diff:   // T_DIFF:
		case munion: // T_MUNION:
		case seqmod_mapoverride: // T_SEQMOD_MAPOVERRIDE:
		case subset:
		case elemin:
		case notelemin:
		case implies:    // T_IMPLIES:
		case biimplies:  // T_BIIMPLIES:
		case Primed:
		case len:    // T_LEN:
			throw new NotImplementedException();

		case div:    // T_DIV:
			return "/";
		case idiv:   // T_IDIV:
			return "/";
		case mod:    // T_MOD:
			return "mod";
		case prod:   // T_PROD:
			return "*";
		case sum:    // T_SUM:
			return "+";
		case minus:  // T_MINUS:
			return "-";
		case less:
			return !isNumericBinary(expression) ? "<" : "#<";
		case lessequal:
			return !isNumericBinary(expression) ? "=<" : "#=<";
		case greater:
			return !isNumericBinary(expression) ? ">" : "#>";
		case greaterequal:
			return !isNumericBinary(expression) ? ">=" : "#>=";
		case equal:
			return "#=";
		case notequal:
			return !isNumericBinary(expression) ? "\\=" : "#\\=";
		case and:    // T_AND:
			throw new NotImplementedException(); // implemented in binaryoperator
		case or:     // T_OR:
			throw new NotImplementedException(); // implemented in binaryoperator
		case not:
			throw new NotImplementedException(); // implemented in binaryoperator

		case Cast:
			return "";

		default:
			return expression.kind().name(); //Enum.GetName(typeof(ExpressionKind), expression.kind);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> void visit(ValueExpression<T> valueExpression)
	{
		assert(m_tmpVars.size() == 0);
		final String tmpVar = NewTempVariable();
		m_emitter.Append(String.format("%s ", tmpVar));


		if (valueExpression.value() == null)
			m_emitter.Append("= 'null'");
		else if (Boolean.class.isAssignableFrom(valueExpression.m_clazz))
			m_emitter.Append(((ValueExpression<Boolean>)valueExpression).value() ? " = true" : " = false");
		else if (Character.class.isAssignableFrom(valueExpression.m_clazz))
			m_emitter.Append(String.format("= '%s'", valueExpression.toString()));
		else if (Integer.class.isAssignableFrom(valueExpression.m_clazz))
			m_emitter.Append(String.format("= %s", valueExpression.toString()));
		else
			m_emitter.Append(String.format("= %s", valueExpression.value().toString()));

		m_emitter.AppendLine(",");
		assert(m_tmpVars.size() == 1);
	}

	@Override
	public void visit(IdentifierExpression identifierExpression)
	{
		if (identifierExpression.identifier().kind() == IdentifierKind.EnumIdentifier)
		{
			final EnumIdentifier enumid = (EnumIdentifier)identifierExpression.identifier();
			final EnumType enumType = (EnumType)enumid.type();
			//m_emitter.Append(enumType.listOfEnumSymbols.IndexOf(enumid));
			if (enumType instanceof ValuedEnumType)
				m_emitter.AppendLine(String.format("%s = %s,", NewTempVariable(), enumid.Value()));
			else
				m_emitter.AppendLine(String.format("%s = %s,", NewTempVariable(), enumType.listOfEnumSymbols().indexOf(enumid)));
		}
		else if (identifierExpression.isSelf())
		{
			m_emitter.AppendLine(String.format("%s = %s,", NewTempVariable(), GetIdentifierString(identifierExpression.identifier())));
		}
		else
		{
			m_tmpVars.add(GetIdentifierString(identifierExpression.identifier()));
		}
	}

	@Override
	public void visit(UnaryOperator unaryOperator)
	{
		assert(m_tmpVars.size() == 0);
		VisitSub(unaryOperator.child(), unaryOperator);

		if (unaryOperator.kind() == ExpressionKind.Cast)
			return;

		final String childresult = m_tmpVars.get(0); m_tmpVars.remove(0);
		assert(m_tmpVars.size() == 0);

		final String tmpresult = NewTempVariable();

		switch (unaryOperator.kind())
		{
		case head:
			m_emitter.AppendLine(String.format("ulyssesListHead(%s,%s),", childresult, tmpresult));
			break;

		case tail:
			m_emitter.AppendLine(String.format("ulyssesListTail(%s,%s),", childresult, tmpresult));
			break;

		case len:    // T_LEN:
			m_emitter.AppendLine(String.format("ulyssesListLength(%s,%s),", childresult, tmpresult));
			break;

		case not:
			m_emitter.Append(String.format(" %s = ( #\\", tmpresult));
			m_emitter.Append("(");
			m_emitter.Append(childresult);
			m_emitter.AppendLine(")),");
			break;

		case Cast:
			// todo
			break;

		default:
			//do not use #= for assignments in the symbolic backend
			m_emitter.Append(String.format(" %s = (%s", tmpresult, operatorString(unaryOperator, unaryOperator.child().type())));
			m_emitter.Append("(");
			m_emitter.Append(childresult);
			m_emitter.AppendLine(")),");
			break;
		}
		assert(m_tmpVars.size() == 1);
	}

	@Override
	public void visit(BinaryOperator binaryOperator)
	{
		// eval stack must be empty
		assert(m_tmpVars.size() == 0);

		final OoasCodeEmitter origEmitter = m_emitter;
		final OoasCodeEmitter leftcode = new OoasCodeEmitter();
		final OoasCodeEmitter rightcode = new OoasCodeEmitter();


		// traverse left
		m_emitter = leftcode;
		VisitSub(binaryOperator.left(), binaryOperator);
		final String leftresult = m_tmpVars.get(0); m_tmpVars.remove(0);
		assert(m_tmpVars.size() == 0);

		// traverse right
		m_emitter = rightcode;
		VisitSub(binaryOperator.right(), binaryOperator);
		final String rightresult = m_tmpVars.get(0); m_tmpVars.remove(0);
		// eval stack must be empty
		assert(m_tmpVars.size() == 0);

		// restore original emitter and get tmp.var
		m_emitter = origEmitter;
		final String tmpVar = NewTempVariable();

		switch (binaryOperator.kind())
		{
		case elemin:
			m_emitter.Append(leftcode.toString());
			m_emitter.Append(rightcode.toString());
			m_emitter.AppendLine(String.format(" %s = member(%s,%s),",
					tmpVar, leftresult, rightresult));
			break;
		case and:    // T_AND:
			m_emitter.Append(leftcode.toString());
			m_emitter.Append(rightcode.toString());
			m_emitter.AppendLine(String.format(" %s = (%s %s %s), ",
					tmpVar, leftresult, "#/\\", rightresult));
			break;
		case or:     // T_OR:
			m_emitter.Append(leftcode.toString());
			m_emitter.Append(rightcode.toString());
			m_emitter.AppendLine(String.format(" %s = (%s %s %s), ",
					tmpVar, leftresult, "#\\/", rightresult));
			break;

		case implies:
			m_emitter.Append(leftcode.toString());
			m_emitter.AppendLine(String.format(" %s = (%s -> (%s%s); true), ", // {0} = (({1} -> ({2}{3}); true))
					tmpVar, leftresult, rightcode.toString(), rightresult));
			break;

		case equal:
			m_emitter.Append(leftcode.toString());
			m_emitter.Append(rightcode.toString());
			/*check if we have tupleconstructors as matchers*/
			TupleConstructor matcher = null;
			String aTuple = null;
			if (binaryOperator.left().kind() == ExpressionKind.TupleConstr &&
					((TupleConstructor)binaryOperator.left()).isMatcher())
			{
				matcher = (TupleConstructor)binaryOperator.left();
				aTuple = rightresult;
			}
			else if (binaryOperator.right().kind() == ExpressionKind.TupleConstr &&
					((TupleConstructor)binaryOperator.right()).isMatcher())
			{
				matcher = (TupleConstructor)binaryOperator.right();
				aTuple = leftresult;
			}

			if (matcher == null)
				m_emitter.AppendLine(String.format(" %s = (%s %s %s), ",
				tmpVar, leftresult, operatorString(binaryOperator, binaryOperator.left().type()), rightresult));
			else
			{
				m_emitter.Append(String.format("%s = unify(%s = [", tmpVar, aTuple));
				int cntr = 0;
				for (final Expression x: matcher.values())
				{
					if (cntr != 0)
						m_emitter.Append(", ");
					else
						cntr++;

					if (x.kind() != ExpressionKind.Identifier)
						throw new ArgumentException();

					final IdentifierExpression ident = (IdentifierExpression)x;
					final OoaPrologIdentifier avisitor = createIdentifierVisitor();
					ident.Accept(avisitor);
					m_emitter.Append(avisitor.toString());
				}
				m_emitter.AppendLine("]),");
			}
			break;



		case conc:
			m_emitter.Append(leftcode.toString());
			m_emitter.Append(rightcode.toString());
			m_emitter.AppendLine(String.format("ulyssesListConc(%s,%s,%s),", leftresult, rightresult, tmpVar));
			break;

		default:
			m_emitter.Append(leftcode.toString());
			m_emitter.Append(rightcode.toString());

			//do not use #= for assignments in the symbolic backend
			m_emitter.AppendLine(String.format(" %s = (%s %s %s), ",
					tmpVar, leftresult, operatorString(binaryOperator, binaryOperator.left().type()), rightresult));
			break;
		}
		assert(m_tmpVars.size() == 1);
	}



	protected OoaPrologSymbolicExpression(
			OoaPrologIdentifier.Factory idFactory,
			OoaPrologType.Factory typeFactory,
			Scratchbook scratchbook,
			OoaPrologExpression.Factory expressionFactory,
			boolean lhs)
	{
		super (idFactory, typeFactory, scratchbook, expressionFactory,  lhs);
	}

}
