/*
 * This file has been developed at the University of Munich, Chair for Programming & Software Engineering.
 * 
 * This file is licensed under the Eclipse Public License (EPL) 1.0
 * 
 */
package eu.mdd4soa.trans.smm2bpel.impl.util;

import java.util.Calendar;
import java.util.Map;

import org.eclipse.emf.common.util.EList;

import eu.mdd4soa.smm.data.Expression;
import eu.mdd4soa.smm.data.LeftHandSideExpression;
import eu.mdd4soa.smm.data.Literal;
import eu.mdd4soa.smm.data.Operation;
import eu.mdd4soa.smm.data.OperationType;
import eu.mdd4soa.smm.data.PropertyReference;
import eu.mdd4soa.smm.data.Variable;
import eu.mdd4soa.smm.data.VariableReference;
import eu.mdd4soa.smm.exception.TransformationException;
import eu.mdd4soa.smm.statik.BooleanType;
import eu.mdd4soa.smm.statik.DateType;
import eu.mdd4soa.smm.statik.IntegerType;
import eu.mdd4soa.smm.statik.MessageProperty;
import eu.mdd4soa.smm.statik.MessageType;
import eu.mdd4soa.smm.statik.NullType;
import eu.mdd4soa.smm.statik.PrimitiveType;
import eu.mdd4soa.smm.statik.RealType;
import eu.mdd4soa.smm.statik.SMMType;
import eu.mdd4soa.smm.statik.StringType;

public class ExpressionHelper {

	public static final String XPATH_SEPARATOR= "/";

	public static final String ARGUMENT_SEPARATOR= ", ";

	public static final String OPEN_BRACKET= "(";

	public static final String CLOSE_BRACKET= ")";

	private Map<Variable, org.eclipse.bpel.model.Variable> fVariables;

	public ExpressionHelper(Map<Variable, org.eclipse.bpel.model.Variable> variableMap) {
		fVariables= variableMap;
	}

	public String convertSMMExpression(Expression source) throws TransformationException {

		String expression= "";

		if (source instanceof VariableReference) {
			expression= "$" + fVariables.get( ((VariableReference) source).getVariable()).getName();
		} else if (source instanceof PropertyReference) {

			LeftHandSideExpression left= (LeftHandSideExpression) source;
			String access= "";
			while (left instanceof PropertyReference) {
				if (!access.isEmpty())
					access= "/" + access;

				MessageProperty property= ((PropertyReference) left).getProperty();
				// if (property.getType() instanceof PrimitiveType)
				// access= property.getIdentifier() + access;
				// else
				access= "types:" + property.getIdentifier() + access;

				left= ((PropertyReference) left).getParent();
			}

			expression= "$" + fVariables.get( ((VariableReference) left).getVariable()).getName() + "/" + access;

		} else if (source instanceof Literal) {

			SMMType type= ((Literal) source).getType();
			Object value= ((Literal) source).getValue();

			if (type instanceof BooleanType) {
				if ((Boolean) value) {
					expression= "true()";
				} else {
					expression= "false()";
				}
			} else if (type instanceof StringType) {
				expression= "\"" + value + "\"";
			} else if (type instanceof NullType) {
				// empty string is treated as null
				expression= "\"\"";
			} else {
				expression= String.valueOf(value);
			}

		} else if (source instanceof Operation) {
			Operation op= (Operation) source;
			expression= convertSMMExpression(op);
		}

		// TODO cast?

		return expression;
	}

	public String convertSMMCondition(Expression enterCondition) throws TransformationException {
		// TODO maybe add some checking.
		return convertSMMExpression(enterCondition);
	}

	private String convertSMMExpression(Operation op) throws TransformationException {
		String returnValue= "";
		switch (op.getOperationType()) {
			case ADDITION:
				if (op.getType() instanceof StringType) {
					returnValue= getJoinedOperands(op, ARGUMENT_SEPARATOR, "concat");
				} else {
					// TODO: special handling for dates
					returnValue= getJoinedOperands(op, " + ");
				}
				break;
			case SUBTRACTION:
				returnValue+= getJoinedOperands(op, " - ");
				break;
			case MULTIPLICATION:
				returnValue+= getJoinedOperands(op, " * ");
				break;
			case DIVISION:
				returnValue+= getJoinedOperands(op, " / ");
				break;
			case MODULO:
				returnValue+= getJoinedOperands(op, " mod ");
				break;

			case EQUALS:
			case NOT_EQUALS:

				String operator= " = ";
				if (op.getOperationType().equals(OperationType.NOT_EQUALS)) {
					operator= " != ";
				}
				// comparison to null is possible:
				if (op.getOperands().size() == 2
						&& (op.getOperands().get(0).getType() instanceof NullType || op.getOperands().get(1).getType() instanceof NullType)) {
					// comparison of null to null is not allowed and was
					// excluded before
					// (see )
					Expression expression= op.getOperands().get(0);
					if (expression.getType() instanceof NullType) {
						expression= op.getOperands().get(1);
					}
					// apply no further cast to the expression here, one cast to
					// string is what we need:
					returnValue+= addFunction("string", convertSMMExpression(expression));
					returnValue+= operator + "\"\"";
					// the string function collects the values in a node if the
					// node is empty (null) an empty string will be returned
				} else {
					returnValue+= getJoinedOperands(op, operator);
				}
				break;
			case GREATER:
				returnValue+= getJoinedOperands(op, " > ");
				break;
			case GREATER_EQUAL:
				returnValue+= getJoinedOperands(op, " >= ");
				break;
			case LESS:
				returnValue+= getJoinedOperands(op, " < ");
				break;
			case LESS_EQUAL:
				returnValue+= getJoinedOperands(op, " <= ");
				break;
			case AND:
				returnValue+= getJoinedOperands(op, " and ");
				break;
			case OR:
				returnValue+= getJoinedOperands(op, " or ");
				break;
			case NEGATION:
				returnValue+= "-" + convertSMMExpression(op.getOperands().get(0));
				break;
			case NOT:
				returnValue= addFunction("not", convertSMMExpression(op.getOperands().get(0)));
				break;
		}
		return returnValue;
	}

	private String getJoinedOperands(Operation op, String operator) throws TransformationException {
		return getJoinedOperands(op, operator, null);
	}

	private String getJoinedOperands(Operation op, String operator, String functionName) throws TransformationException {
		String returnValue= "";
		for (int i= 0; i < op.getOperands().size(); i++) {
			if (i > 0) {
				returnValue+= operator;
			}
			returnValue+= convertSMMExpression(op.getOperands().get(i));
		}
		if (functionName != null) {
			returnValue= addFunction(functionName, returnValue);
		}

		return returnValue;
	}

	public String addFunction(String xPathFunction, String argument) {
		StringBuffer buffer= new StringBuffer(xPathFunction);
		buffer.append(OPEN_BRACKET);
		buffer.append(argument);
		buffer.append(CLOSE_BRACKET);
		return buffer.toString();
	}

	public String createPrimitiveLiteral(PrimitiveType type) {

		if (type instanceof BooleanType)
			return "true()";
		if (type instanceof StringType)
			return "\"\"";
		if (type instanceof IntegerType)
			return "0";
		if (type instanceof RealType)
			return "0.0";
		if (type instanceof DateType)
			return Calendar.getInstance().toString();

		return null;
	}

	public String createLiteralInitialisation(MessageType type) {

		StringBuilder b= new StringBuilder();
		b.append("<types:" + type.getName() + ">");

		EList<MessageProperty> properties= type.getProperties();
		for (MessageProperty messageProperty : properties) {

			// Do not initialise lists or sets.
			if (messageProperty.getMax() > 1)
				continue;

			b.append("<types:" + messageProperty.getIdentifier() + ">");

			// Literal?
			if (messageProperty.getType() instanceof PrimitiveType)
				b.append(createPrimitiveLiteral((PrimitiveType) messageProperty.getType()));

			b.append("</types:" + messageProperty.getIdentifier() + ">");
		}

		b.append("</types:" + type.getName() + ">");

		return b.toString();
	}

}
