/**
  *
  *                      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 org.momut.ooas.ast.expressions.AccessExpression;
import org.momut.ooas.ast.expressions.BinaryOperator;
import org.momut.ooas.ast.expressions.CallExpression;
import org.momut.ooas.ast.expressions.ExistsQuantifier;
import org.momut.ooas.ast.expressions.Expression;
import org.momut.ooas.ast.expressions.ForallQuantifier;
import org.momut.ooas.ast.expressions.IdentifierExpression;
import org.momut.ooas.ast.expressions.ListConstructor;
import org.momut.ooas.ast.expressions.MapConstructor;
import org.momut.ooas.ast.expressions.ObjectConstructor;
import org.momut.ooas.ast.expressions.SetConstructor;
import org.momut.ooas.ast.expressions.TernaryOperator;
import org.momut.ooas.ast.expressions.TupleConstructor;
import org.momut.ooas.ast.expressions.TupleMapAccessExpression;
import org.momut.ooas.ast.expressions.TypeExpression;
import org.momut.ooas.ast.expressions.UnaryOperator;
import org.momut.ooas.ast.expressions.UnresolvedIdentifierExpression;
import org.momut.ooas.ast.expressions.ValueExpression;
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.types.TypeKind;
import org.momut.ooas.ast.types.Type;
import org.momut.ooas.codegen.OoasCodeEmitter;
import org.momut.ooas.utils.UnsignedHelper;
import org.momut.ooas.utils.exceptions.NotImplementedException;
import org.momut.ooas.utils.exceptions.OoasCompilerRuntimeException;
import org.momut.ooas.visitors.OoaExpressionVisitor;

public final class JavaExpression extends OoaExpressionVisitor
{
	private final OoasCodeEmitter m_emitter = new OoasCodeEmitter();
	private final Scratchbook m_data;
	private final StringBuilder m_helpers;

	private String GetIdentifierString(String anIdentifier)
	{
		return JavaIdentifier.GetIdentifierString(anIdentifier);
	}

	public ArrayList<ObjectConstructor> constructors() { return m_data.m_constructors; }

	// following calls must not happen
	@Override
	public void visit(UnresolvedIdentifierExpression unresolvedIdentifierExpression)
	{
		throw new OoasCompilerRuntimeException();
	}
	@Override
	public void visit(CallExpression callExpression)
	{
		throw new OoasCompilerRuntimeException();
//		System.Diagnostics.Debug.Assert(false);
	}


	// we need to map all other things to Java constructs
	private String OperatorString(Expression expression)
	{
		switch (expression.kind())
		{
		case abs:    // T_ABS:
		case card:   // T_CARD:
		case dom:    // T_DOM:
		case range:  // T_RNG:
		case merge:  // T_MERGE:
		case len:    // T_LEN:
		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:
			throw new NotImplementedException();
		case div:    // T_DIV:
			return "/";
		case idiv:   // T_IDIV:
			return "/";
		case mod:    // T_MOD:
			return "%";
		case prod:   // T_PROD:
			return "*";
		case sum:    // T_SUM:
			return "+";
		case minus:  // T_MINUS:
			return "-";
		case less:
			return "<";
		case lessequal:
			return "<=";
		case greater:
			return ">";
		case greaterequal:
			return ">=";
		case equal:
			return "==";
		case notequal:
			return "!=";
		case and:    // T_AND:
			return "&&";
		case or:     // T_OR:
			return "||";
		case not:
			return "!";

		case Cast:
			final JavaType ctype = new JavaType();
			expression.type().Accept(ctype);
			return String.format("(%s)", ctype.toString());

		default:
			return expression.kind().name(); //Enum.GetName(typeof(ExpressionKind), expression.kind);
		}
	}
	@Override
	public <T> void visit(ValueExpression<T> valueExpression)
	{
		if (valueExpression.value() == null)
			m_emitter.Append("null");
		else if (valueExpression.m_clazz.equals(Boolean.class))
			m_emitter.Append(valueExpression.value().toString().toLowerCase());
		else
			m_emitter.Append(valueExpression.value().toString());
	}
	@Override
	public void visit(IdentifierExpression identifierExpression)
	{
		if (identifierExpression.identifier().kind() == IdentifierKind.EnumIdentifier)
		{
			m_emitter.Append(GetIdentifierString(((EnumIdentifier)identifierExpression.identifier()).type().identifier().tokenText()));
			m_emitter.Append(".");
		}

		m_emitter.Append(GetIdentifierString(identifierExpression.identifier().tokenText()));
	}

	@Override
	public void visit(TypeExpression typeExpression)
	{
		if (typeExpression.referredType().kind() != TypeKind.EnumeratedType)
			m_emitter.Append(GetIdentifierString(typeExpression.referredType().identifier().tokenText()));
	}


	@Override
	public void visit(ObjectConstructor objectConstructor)
	{
		final int num = m_data.m_constructors.size();
		m_data.m_constructors.add(objectConstructor);
		if (objectConstructor.givenObjectName() != null)
			m_emitter.Append(String.format("new %s(this,%s,\"%s\")",
					GetIdentifierString(objectConstructor.type().identifier().tokenText()),
					num,
					objectConstructor.givenObjectName()));
		else
			m_emitter.Append(String.format("new %s(this,%s)",
					GetIdentifierString(objectConstructor.type().identifier().tokenText()),
					num));
	}

	@Override
	public void visit(ListConstructor listConstructor)
	{
		if (listConstructor.hasComprehension())
		{
			final String helpername = String.format("list_constr_helper_%s", UnsignedHelper.toString(listConstructor.hashCode()));
			m_emitter.Append(String.format("%s(new org.momut.ooas.codegen.java.runtime.CustomList<Object>())",
					helpername));
			final OoasCodeEmitter helperEmitter = new OoasCodeEmitter();
			helperEmitter.AppendLine(String.format(
					"public org.momut.ooas.codegen.java.runtime.CustomList<Object> %s (org.momut.ooas.codegen.java.runtime.CustomList<Object> newList){",
					helpername));


			for (final Identifier sym: listConstructor.comprehensionVariables().symbolList())
			{
				final JavaType atype = new JavaType();
				sym.type().Accept(atype);

				if (sym.type().IsNumeric())
					helperEmitter.AppendLine(String.format("for (%s %s = %s; %s <= %s; %s++) {",
							atype.toString(),
							JavaIdentifier.GetIdentifierString(sym.tokenText()),
							Type.Low(sym.type()),
							JavaIdentifier.GetIdentifierString(sym.tokenText()),
							Type.High(sym.type()),
							JavaIdentifier.GetIdentifierString(sym.tokenText())));
				else
					throw new NotImplementedException();
			}

			final StringBuilder helperhelper = new StringBuilder();


			String guardexpr = "true";
			if (listConstructor.comprehension() != null)
			{
				final JavaExpression expr = new JavaExpression(helperhelper, m_data);
				listConstructor.comprehension().Accept(expr);
				guardexpr = expr.toString();
			}

			final JavaExpression expr2 = new JavaExpression(helperhelper, m_data);
			listConstructor.elements().get(0).Accept(expr2);

			helperEmitter.AppendLine(String.format("if (%s) {", guardexpr));
			helperEmitter.AppendLine(String.format("newList.Add(%s);", expr2.toString()));
			helperEmitter.AppendLine("}");

			final int count = listConstructor.comprehensionVariables().symbolList().size();
			for (int i = 0; i< count; i++)
				helperEmitter.AppendLine("}");

			helperEmitter.AppendLineIncIndent("return newList;");
			helperEmitter.AppendLine("}");
			m_helpers.append(helperEmitter.toString());
			m_helpers.append(helperhelper.toString());
		}
		else
		{
			m_emitter.Append("new org.momut.ooas.codegen.java.runtime.CustomList<Object>(");
			int i = 0;
			if (listConstructor.elements().size() > 0)
			{
				m_emitter.Append("new Object[] { ");
				for (final Expression x: listConstructor.elements())
				{
					if (i != 0)
						m_emitter.Append(",");
					else
						i++;
					x.Accept(this);
				}
				m_emitter.Append(" }");
			}
			m_emitter.Append(")");
		}
	}

	@Override
	public void visit(SetConstructor setConstructor)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(MapConstructor mapConstructor)
	{
		throw new NotImplementedException();
	}

	@Override
	public void visit(TupleConstructor tupleConstructor)
	{
		m_emitter.Append(String.format("new %s(", GetIdentifierString(tupleConstructor.tupleType().tokenText())));
		int i = 0;
		for (final Expression arg: tupleConstructor.values())
		{
			if (i != 0)
				m_emitter.Append(",");
			else
				i++;
			arg.Accept(this);
		}
		m_emitter.Append(")");
	}

	@Override
	public void visit(AccessExpression accessExpression)
	{
		if (accessExpression.left().type().kind() != TypeKind.MetaType)
//		if (accessExpression.left().type().kind() != TypeKind.EnumeratedType)
		{
			// enums and qr types are directly (statically) converted to nums...
			VisitSub(accessExpression.left(), accessExpression);
			m_emitter.Append(".");
		}
		VisitSub(accessExpression.right(), accessExpression);
	}

	@Override
	public void visit(BinaryOperator binaryOperator)
	{
		m_emitter.Append("(");
		VisitSub(binaryOperator.left(), binaryOperator);
		m_emitter.Append(") ");
		m_emitter.Append(OperatorString(binaryOperator));
		m_emitter.Append(" (");
		VisitSub(binaryOperator.right(), binaryOperator);
		m_emitter.Append(")");
	}

	@Override
	public void visit(TernaryOperator ternaryOperator)
	{
		m_emitter.Append("(");
		VisitSub(ternaryOperator.left(), ternaryOperator);
		m_emitter.Append(" ? ");

		VisitSub(ternaryOperator.mid(), ternaryOperator);
		m_emitter.Append(" : ");

		VisitSub(ternaryOperator.right(), ternaryOperator);
		m_emitter.Append(")");
	}
	@Override
	public void visit(TupleMapAccessExpression tupleMapAccessExpression)
	{
		throw new NotImplementedException();
	}
	@Override
	public void visit(ForallQuantifier quantifier)
	{
		throw new NotImplementedException();
	}
	@Override
	public void visit(ExistsQuantifier quantifier)
	{
		throw new NotImplementedException();
	}
	@Override
	public void visit(UnaryOperator unaryOperator)
	{
		m_emitter.Append(" ");
		m_emitter.Append(OperatorString(unaryOperator));
		m_emitter.Append("(");
		VisitSub(unaryOperator.child(), unaryOperator);
		m_emitter.Append(")");
	}

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

	public JavaExpression(StringBuilder helpers, Scratchbook data)
	{
		super ();
		m_helpers = helpers;
		m_data = data;
	}
}
