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

import org.eclipse.emf.common.util.EList;
import org.eclipse.gmt.modisco.java.AbstractTypeDeclaration;
import org.eclipse.gmt.modisco.java.AnonymousClassDeclaration;
import org.eclipse.gmt.modisco.java.Assignment;
import org.eclipse.gmt.modisco.java.BodyDeclaration;
import org.eclipse.gmt.modisco.java.BooleanLiteral;
import org.eclipse.gmt.modisco.java.CatchClause;
import org.eclipse.gmt.modisco.java.CharacterLiteral;
import org.eclipse.gmt.modisco.java.ClassInstanceCreation;
import org.eclipse.gmt.modisco.java.Comment;
import org.eclipse.gmt.modisco.java.CompilationUnit;
import org.eclipse.gmt.modisco.java.EnumConstantDeclaration;
import org.eclipse.gmt.modisco.java.EnumDeclaration;
import org.eclipse.gmt.modisco.java.Expression;
import org.eclipse.gmt.modisco.java.ExpressionStatement;
import org.eclipse.gmt.modisco.java.FieldDeclaration;
import org.eclipse.gmt.modisco.java.ImportDeclaration;
import org.eclipse.gmt.modisco.java.InfixExpression;
import org.eclipse.gmt.modisco.java.InfixExpressionKind;
import org.eclipse.gmt.modisco.java.LineComment;
import org.eclipse.gmt.modisco.java.MethodDeclaration;
import org.eclipse.gmt.modisco.java.MethodInvocation;
import org.eclipse.gmt.modisco.java.Modifier;
import org.eclipse.gmt.modisco.java.NumberLiteral;
import org.eclipse.gmt.modisco.java.ParameterizedType;
import org.eclipse.gmt.modisco.java.PrefixExpression;
import org.eclipse.gmt.modisco.java.PrefixExpressionKind;
import org.eclipse.gmt.modisco.java.ReturnStatement;
import org.eclipse.gmt.modisco.java.SingleVariableAccess;
import org.eclipse.gmt.modisco.java.SingleVariableDeclaration;
import org.eclipse.gmt.modisco.java.Statement;
import org.eclipse.gmt.modisco.java.StringLiteral;
import org.eclipse.gmt.modisco.java.SynchronizedStatement;
import org.eclipse.gmt.modisco.java.ThisExpression;
import org.eclipse.gmt.modisco.java.ThrowStatement;
import org.eclipse.gmt.modisco.java.TryStatement;
import org.eclipse.gmt.modisco.java.Type;
import org.eclipse.gmt.modisco.java.TypeAccess;
import org.eclipse.gmt.modisco.java.TypeDeclaration;
import org.eclipse.gmt.modisco.java.VariableDeclaration;
import org.eclipse.gmt.modisco.java.VariableDeclarationFragment;
import org.eclipse.gmt.modisco.java.VisibilityKind;
import org.eclipse.gmt.modisco.java.emf.JavaFactory;

import eu.mdd4soa.trans.smm2java.impl.TypeTracker;

/**
 * This class contains helper methods for working with the MoDisco Java model.
 * 
 * Some notes about the MoDisco model:
 * <ul>
 * <li>A field declaration must have fragments. FieldDeclaration.setName() has
 * no effect.</li>
 * <li>A modifier is single-use - always create a new one, because it is owned
 * by its parent.</li>
 * <li>Cannot add parameterized types as imports (which is quite logical...), so
 * the base type has to be added.
 * </ul>
 * 
 * @author Philip Mayer
 * 
 */
public class JMHelper {

	public static void ensureImport(CompilationUnit cUnit, Type type) {

		assert cUnit != null;
		assert type != null;

		EList<ImportDeclaration> imports= cUnit.getImports();
		for (ImportDeclaration importDeclaration : imports) {
			if (importDeclaration.getImportedElement().equals(type))
				return;
		}

		ImportDeclaration d= JavaFactory.eINSTANCE.createImportDeclaration();
		d.setImportedElement(type);
		cUnit.getImports().add(d);
	}

	public static TypeAccess createTypeAccessTo(Type type) {

		TypeAccess ta= JavaFactory.eINSTANCE.createTypeAccess();
		ta.setType(type);

		return ta;
	}

	public static Modifier createModifier(String kind) {
		Modifier modifier= JavaFactory.eINSTANCE.createModifier();
		if (kind.contains("private"))
			modifier.setVisibility(VisibilityKind.PRIVATE);
		if (kind.contains("public"))
			modifier.setVisibility(VisibilityKind.PUBLIC);
		if (kind.contains("protected"))
			modifier.setVisibility(VisibilityKind.PROTECTED);
		if (kind.contains("static"))
			modifier.setStatic(true);
		if (kind.contains("synchronized"))
			modifier.setSynchronized(true);

		return modifier;
	}

	public static MethodDeclaration createMethod(String modifier, String methodName) {
		return createMethod(modifier, null, methodName);
	}

	public static MethodDeclaration createMethod(String modifier, Type returnType, String methodName) {

		MethodDeclaration d= JavaFactory.eINSTANCE.createMethodDeclaration();
		d.setName(methodName);
		d.setBody(JavaFactory.eINSTANCE.createBlock());
		d.setModifier(createModifier(modifier));
		if (returnType != null)
			d.setReturnType(createTypeAccessTo(returnType));

		return d;
	}

	public static MethodInvocation createMethodInvocation(MethodDeclaration md) {
		MethodInvocation inv= JavaFactory.eINSTANCE.createMethodInvocation();
		inv.setMethod(md);
		return inv;
	}

	public static Statement createMethodInvocationStatement(MethodDeclaration md) {
		ExpressionStatement st= JavaFactory.eINSTANCE.createExpressionStatement();
		st.setExpression(createMethodInvocation(md));
		return st;
	}

	public static ExpressionStatement createMethodInvocationStatement(Expression on, MethodDeclaration methodToInvoke, Expression... parameters) {

		ExpressionStatement st= JavaFactory.eINSTANCE.createExpressionStatement();
		st.setExpression(createMethodInvocation(on, methodToInvoke, parameters));
		return st;
	}

	public static MethodInvocation createMethodInvocation(Expression on, MethodDeclaration methodToInvoke, Expression... parameters) {
		MethodInvocation inv= JavaFactory.eINSTANCE.createMethodInvocation();
		if (on != null)
			inv.setExpression(on);
		inv.setMethod(methodToInvoke);


		for (Expression param : parameters) {
			inv.getArguments().add(param);
		}
		return inv;
	}

	public static SingleVariableAccess createFieldAccess(FieldDeclaration barrierRunnables) {
		SingleVariableAccess acc= JavaFactory.eINSTANCE.createSingleVariableAccess();
		acc.setVariable(barrierRunnables.getFragments().get(0));
		// acc.setQualifier(JavaFactory.eINSTANCE.createThisExpression());
		return acc;
	}

	public static Expression createLocalVariableAccess(VariableDeclaration singleVariableDeclaration) {
		SingleVariableAccess acc= JavaFactory.eINSTANCE.createSingleVariableAccess();
		acc.setVariable(singleVariableDeclaration);
		return acc;
	}

	public static ExpressionStatement createAssignmentToField(FieldDeclaration theField, SingleVariableDeclaration theLocalVariable) {
		SingleVariableAccess acc2= JavaFactory.eINSTANCE.createSingleVariableAccess();
		acc2.setVariable(theLocalVariable);
		return createAssignmentToField(theField, acc2);
	}

	public static ExpressionStatement createAssignmentToField(FieldDeclaration theField, Expression expression) {
		ThisExpression te= JavaFactory.eINSTANCE.createThisExpression();
		SingleVariableAccess acc= JavaFactory.eINSTANCE.createSingleVariableAccess();
		acc.setVariable(theField.getFragments().get(0));
		acc.setQualifier(te);

		Assignment ass= JavaFactory.eINSTANCE.createAssignment();
		ass.setLeftHandSide(acc);

		ass.setRightHandSide(expression);

		ExpressionStatement st= JavaFactory.eINSTANCE.createExpressionStatement();
		st.setExpression(ass);
		return st;
	}

	public static ExpressionStatement createAssignmentToFieldWOThis(FieldDeclaration theField, Expression expression) {
		SingleVariableAccess acc= JavaFactory.eINSTANCE.createSingleVariableAccess();
		acc.setVariable(theField.getFragments().get(0));

		Assignment ass= JavaFactory.eINSTANCE.createAssignment();
		ass.setLeftHandSide(acc);

		ass.setRightHandSide(expression);

		ExpressionStatement st= JavaFactory.eINSTANCE.createExpressionStatement();
		st.setExpression(ass);
		return st;
	}

	public static MethodDeclaration findMethod(AbstractTypeDeclaration typeDeclaration, String methodName) {

		EList<BodyDeclaration> bodyDeclarations= typeDeclaration.getBodyDeclarations();
		for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
			if (bodyDeclaration instanceof MethodDeclaration) {
				MethodDeclaration dec= (MethodDeclaration) bodyDeclaration;
				if (dec.getName().equals(methodName))
					return dec;
			}
		}

		EList<TypeAccess> superInterfaces= typeDeclaration.getSuperInterfaces();
		for (TypeAccess typeAccess : superInterfaces) {
			Type type= typeAccess.getType();
			MethodDeclaration method= findMethod((AbstractTypeDeclaration) type, methodName);
			if (method != null)
				return method;
		}

		return null;
	}

	public static FieldDeclaration findField(AbstractTypeDeclaration typeDeclaration, String fieldName) {

		EList<BodyDeclaration> bodyDeclarations= typeDeclaration.getBodyDeclarations();
		for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
			if (bodyDeclaration instanceof FieldDeclaration) {
				FieldDeclaration dec= (FieldDeclaration) bodyDeclaration;
				VariableDeclarationFragment firstFragment= dec.getFragments().get(0);
				if (firstFragment.getName().equals(fieldName))
					return dec;
			}
		}
		return null;
	}

	public static FieldDeclaration createField(String modifiers, String name, Type type) {

		FieldDeclaration fd= JavaFactory.eINSTANCE.createFieldDeclaration();
		Modifier priv= createModifier(modifiers);
		fd.setModifier(priv);

		VariableDeclarationFragment frag= JavaFactory.eINSTANCE.createVariableDeclarationFragment();
		frag.setName(name);
		fd.getFragments().add(frag);

		TypeAccess leftHandSide= JavaFactory.eINSTANCE.createTypeAccess();
		leftHandSide.setType(type);
		fd.setType(leftHandSide);

		return fd;
	}

	public static SingleVariableDeclaration createLocalVariable(Type varType, String varName) {
		SingleVariableDeclaration decOfIf= JavaFactory.eINSTANCE.createSingleVariableDeclaration();
		decOfIf.setName(varName);
		decOfIf.setType(createTypeAccessTo(varType));
		return decOfIf;
	}

	public static EnumDeclaration createEnumDeclaration(String name) {
		EnumDeclaration actDec= JavaFactory.eINSTANCE.createEnumDeclaration();
		actDec.setName(name);
		return actDec;
	}

	public static EnumConstantDeclaration createEnumConstant(EnumDeclaration enumDeclaration, String nameOfConstant) {
		EnumConstantDeclaration dec= JavaFactory.eINSTANCE.createEnumConstantDeclaration();
		dec.setName(nameOfConstant);
		enumDeclaration.getEnumConstants().add(dec);
		return dec;
	}

	public static NumberLiteral createNumberLiteral(int i) {
		NumberLiteral nl= JavaFactory.eINSTANCE.createNumberLiteral();
		nl.setTokenValue("" + i);
		return nl;
	}

	public static Expression createBooleanLiteral(Boolean value) {
		BooleanLiteral bl= JavaFactory.eINSTANCE.createBooleanLiteral();
		bl.setValue(value);
		return bl;
	}

	public static Expression createCharacterLiteral(String value) {
		CharacterLiteral bl= JavaFactory.eINSTANCE.createCharacterLiteral();
		bl.setEscapedValue(value);
		return bl;
	}


	public static SingleVariableAccess createEnumAccess(EnumDeclaration enumDeclaration, EnumConstantDeclaration enumConstrant) {
		SingleVariableAccess accX= JavaFactory.eINSTANCE.createSingleVariableAccess();
		accX.setVariable(enumConstrant);
		accX.setQualifier(createTypeAccessTo(enumDeclaration));
		return accX;
	}

	public static ExpressionStatement createFieldInitializationStatement(FieldDeclaration varField, ParameterizedType varType) {
		ClassInstanceCreation cc= JavaFactory.eINSTANCE.createClassInstanceCreation();
		cc.setType(createTypeAccessTo(varType));
		ExpressionStatement varSetupField= createAssignmentToField(varField, cc);
		return varSetupField;
	}

	/**
	 * Creates an anonymous class declaration wrapped in a class instance
	 * creation statement. The first parameter defines the type of the anonymous
	 * class; the second is a list of method declarations which are directly
	 * added to the newly created anonymous class.
	 * 
	 * @param classType
	 * @param methods
	 * @return
	 */
	public static ClassInstanceCreation createAnonymousClassCreation(Type classType, MethodDeclaration... methods) {

		AnonymousClassDeclaration acd= JavaFactory.eINSTANCE.createAnonymousClassDeclaration();

		for (MethodDeclaration methodDeclaration : methods) {
			acd.getBodyDeclarations().add(methodDeclaration);
		}

		ClassInstanceCreation cic= JavaFactory.eINSTANCE.createClassInstanceCreation();
		cic.setType(createTypeAccessTo(classType));
		cic.setAnonymousClassDeclaration(acd);

		return cic;
	}

	public static ClassInstanceCreation createConstructorInvocation(Type theType, Expression... params) {

		ClassInstanceCreation cic2= JavaFactory.eINSTANCE.createClassInstanceCreation();
		cic2.setType(createTypeAccessTo(theType));

		for (Expression expression : params) {
			cic2.getArguments().add(expression);
		}

		return cic2;
	}

	public static String toFirstUpper(String identifier) {

		if (identifier.length() <= 1)
			return identifier;

		return identifier.substring(0, 1).toUpperCase() + identifier.substring(1);
	}

	public static String toLowerCase(String part) {
		return part.toLowerCase();
	}

	/**
	 * The FieldDeclaration must have ONE variable fragment with a name; the
	 * field must have a type.
	 * 
	 * @param fd
	 * @return
	 */
	public static MethodDeclaration createGetterMethod(FieldDeclaration fd) {
		VariableDeclarationFragment fieldFragment= fd.getFragments().get(0);
		String fieldName= fieldFragment.getName();
		Type fieldType= fd.getType().getType();

		MethodDeclaration d= JMHelper.createMethod("public", fieldType, "get" + toFirstUpper(fieldName));

		ReturnStatement st= JavaFactory.eINSTANCE.createReturnStatement();
		st.setExpression(JMHelper.createFieldAccess(fd));

		d.getBody().getStatements().add(st);
		return d;
	}

	/**
	 * The FieldDeclaration must have ONE variable fragment with a name; the
	 * field must have a type.
	 * 
	 * @param fd
	 * @return
	 */
	public static MethodDeclaration createSetterMethod(TypeTracker tracker, FieldDeclaration fd) {
		VariableDeclarationFragment fieldFragment= fd.getFragments().get(0);
		String fieldName= fieldFragment.getName();
		Type fieldType= fd.getType().getType();

		MethodDeclaration d= JMHelper.createMethod("public", tracker.getType("void"), "set" + toFirstUpper(fieldName));

		SingleVariableDeclaration svd= JMHelper.createLocalVariable(fieldType, fieldName);
		d.getParameters().add(svd);

		ExpressionStatement st= JMHelper.createAssignmentToField(fd, svd);
		d.getBody().getStatements().add(st);
		return d;
	}

	public static void addField(TypeDeclaration defType, FieldDeclaration fd) {
		defType.getBodyDeclarations().add(0, fd);
	}

	public static StringLiteral createStringLiteral(String name) {
		StringLiteral l= JavaFactory.eINSTANCE.createStringLiteral();
		l.setEscapedValue("\"" + name + "\"");
		return l;
	}

	public static ThrowStatement createThrowStatement(Expression expression) {

		ThrowStatement statementInCatch= JavaFactory.eINSTANCE.createThrowStatement();
		statementInCatch.setExpression(expression);

		return statementInCatch;
	}

	public static TryStatement createTryCatchStatement(Type typeToCatch, Statement howToCatch, Statement... toTry) {
		TryStatement trySt= JavaFactory.eINSTANCE.createTryStatement();

		CatchClause cc= JavaFactory.eINSTANCE.createCatchClause();
		cc.setException(createLocalVariable(typeToCatch, "e"));
		cc.setBody(JavaFactory.eINSTANCE.createBlock());

		if (howToCatch != null)
			cc.getBody().getStatements().add(howToCatch);

		trySt.getCatchClauses().add(cc);
		trySt.setBody(JavaFactory.eINSTANCE.createBlock());

		for (Statement statement : toTry) {
			trySt.getBody().getStatements().add(statement);
		}

		return trySt;
	}

	public static Expression createThisExpression() {
		return JavaFactory.eINSTANCE.createThisExpression();
	}

	public static Comment createComment(String string) {
		LineComment cc= JavaFactory.eINSTANCE.createLineComment();
		cc.setContent(string);

		return cc;
	}

	public static Expression createNullLiteral() {
		return JavaFactory.eINSTANCE.createNullLiteral();
	}

	public static Expression createInfixExpression(InfixExpressionKind op, Expression left, Expression right) {

		InfixExpression expr= JavaFactory.eINSTANCE.createInfixExpression();
		expr.setOperator(op);
		expr.setLeftOperand(left);
		expr.setRightOperand(right);

		return expr;
	}

	public static Expression createPrefixExpression(PrefixExpressionKind kind, Expression expression) {

		PrefixExpression expr= JavaFactory.eINSTANCE.createPrefixExpression();
		expr.setOperator(kind);
		expr.setOperand(expression);

		return expr;
	}


	public static Statement createExpressionStatemenet(Expression assignToTimer) {

		ExpressionStatement expressionStatement= JavaFactory.eINSTANCE.createExpressionStatement();
		expressionStatement.setExpression(assignToTimer);

		return expressionStatement;
	}

	public static SynchronizedStatement createSynchronizedBlock(FieldDeclaration fieldDeclaration) {

		SynchronizedStatement sync= JavaFactory.eINSTANCE.createSynchronizedStatement();
		sync.setExpression(createFieldAccess(fieldDeclaration));
		sync.setBody(JavaFactory.eINSTANCE.createBlock());

		return sync;
	}


}
