package org.eclipse.xtext.xbase.ui.quickfix;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmFeature;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.edit.ICompositeModification;
import org.eclipse.xtext.ui.editor.model.edit.ICompositeModificationContext;
import org.eclipse.xtext.ui.editor.model.edit.IModification;
import org.eclipse.xtext.ui.editor.model.edit.IModificationContext;
import org.eclipse.xtext.ui.editor.model.edit.ISemanticModification;
import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider;
import org.eclipse.xtext.ui.editor.quickfix.Fix;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor;
import org.eclipse.xtext.ui.editor.quickfix.ReplaceModification;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.util.concurrent.CancelableUnitOfWork;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.Issue;
import org.eclipse.xtext.xbase.XAbstractFeatureCall;
import org.eclipse.xtext.xbase.XAssignment;
import org.eclipse.xtext.xbase.XBinaryOperation;
import org.eclipse.xtext.xbase.XCasePart;
import org.eclipse.xtext.xbase.XCastedExpression;
import org.eclipse.xtext.xbase.XCatchClause;
import org.eclipse.xtext.xbase.XConstructorCall;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.XIfExpression;
import org.eclipse.xtext.xbase.XInstanceOfExpression;
import org.eclipse.xtext.xbase.XMemberFeatureCall;
import org.eclipse.xtext.xbase.XSwitchExpression;
import org.eclipse.xtext.xbase.XTryCatchFinallyExpression;
import org.eclipse.xtext.xbase.XVariableDeclaration;
import org.eclipse.xtext.xbase.XbasePackage;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.scoping.featurecalls.OperatorMapping;
import org.eclipse.xtext.xbase.typesystem.references.ITypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;
import org.eclipse.xtext.xbase.typesystem.references.StandardTypeReferenceOwner;
import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices;
import org.eclipse.xtext.xbase.ui.contentassist.ReplacingAppendable;
import org.eclipse.xtext.xbase.ui.imports.OrganizeImportsHandler;
import org.eclipse.xtext.xbase.ui.util.InsertionOffsets;
import org.eclipse.xtext.xbase.util.TypesOrderUtil;
import org.eclipse.xtext.xbase.validation.IssueCodes;

import com.google.inject.Inject;

public class XbaseQuickfixProvider extends DefaultQuickfixProvider {

	@Inject
	private OrganizeImportsHandler organizeImportsHandler;

	@Inject
	protected JavaTypeQuickfixes javaTypeQuickfixes;

	@Inject
	private CreateJavaTypeQuickfixes createJavaTypeQuickfixes;

	@Inject
	private CommonTypeComputationServices services;

	@Inject
	private TypesOrderUtil typesOrderUtil;

	@Inject
	private ReplacingAppendable.Factory appendableFactory;

	@Inject
	private InsertionOffsets insertionOffsets;
	
	@Inject
	private OperatorMapping operatorMapping;

	@Fix(IssueCodes.IMPORT_DUPLICATE)
	public void fixDuplicateImport(final Issue issue, IssueResolutionAcceptor acceptor) {
		organizeImports(issue, acceptor);
	}

	@Fix(IssueCodes.EQUALS_WITH_NULL)
	public void fixEqualsWithNull(final Issue issue, IssueResolutionAcceptor acceptor) {
		String message = "Replace '==' with '===' and '!=' with '!=='";
		acceptor.acceptMulti(issue, message, message, null, new ICompositeModification<XBinaryOperation>() {
			@Override
			public void apply(XBinaryOperation element, ICompositeModificationContext<XBinaryOperation> context) {
				context.setUpdateCrossReferences(false);
				context.setUpdateRelatedFiles(false);
				context.addModification(element, (object) -> {
					replaceOperator(object, OperatorMapping.EQUALS, OperatorMapping.TRIPLE_EQUALS);
					replaceOperator(object, OperatorMapping.NOT_EQUALS, OperatorMapping.TRIPLE_NOT_EQUALS);
				});
			}
		});
	}
	
	private void replaceOperator(XBinaryOperation operation, QualifiedName from, QualifiedName to) {
		JvmIdentifiableElement feature = operation.getFeature();
		String fromMethodName = operatorMapping.getMethodName(from).toString();
		if (fromMethodName.equals(feature.getSimpleName())) {
			JvmDeclaredType declaringType = ((JvmOperation) feature).getDeclaringType();
			String toMethodName = operatorMapping.getMethodName(to).toString();
			for (JvmFeature f : declaringType.findAllFeaturesByName(toMethodName)) {
				if (f instanceof JvmOperation) {
					operation.setFeature(f);
					break;
				}
			}
		}
	}

	@Fix(IssueCodes.OPERATION_WITHOUT_PARENTHESES)
	public void fixMissingParentheses(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Add parentheses", "Add parentheses", null, new ISemanticModification() {
			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				ReplacingAppendable appendable = appendableFactory.create(context.getXtextDocument(),
						(XtextResource) element.eResource(), issue.getOffset() + issue.getLength(), 0);
				appendable.append("()");
				appendable.commitChanges();
			}
		});
	}

	@Fix(IssueCodes.IMPORT_UNUSED)
	public void fixUnusedImport(final Issue issue, IssueResolutionAcceptor acceptor) {
		organizeImports(issue, acceptor);
	}

	@Fix(IssueCodes.IMPORT_WILDCARD_DEPRECATED)
	public void fixDuplicateWildcardUse(final Issue issue, IssueResolutionAcceptor acceptor) {
		organizeImports(issue, acceptor);
	}

	@Fix(IssueCodes.AMBIGUOUS_FEATURE_CALL)
	public void fixAmbiguousMethodCall(final Issue issue, IssueResolutionAcceptor acceptor) {
		String[] data = issue.getData();
		if (data == null || data.length == 0) {
			return;
		}
		for (String replacement : data) {
			String replaceLabel = "Change to '" + replacement + "'";
			acceptor.accept(issue, replaceLabel, replaceLabel, null, new ReplaceModification(issue, replacement));
		}
	}

	@Fix(IssueCodes.INVALID_TYPE_ARGUMENTS_ON_TYPE_LITERAL)
	public void fixTypeArguments(final Issue issue, IssueResolutionAcceptor acceptor) {
		String message = issue.getMessage();
		String fixup = "Remove invalid type arguments";
		if (message.contains("argument.")) {
			fixup = "Remove invalid type argument";
		}
		acceptor.accept(issue, fixup, fixup, null, new IModification() {
			@Override
			public void apply(IModificationContext context) throws Exception {
				IXtextDocument document = context.getXtextDocument();
				document.replace(issue.getOffset(), issue.getLength(), "");
			}
		});
	}

	@Fix(IssueCodes.OBSOLETE_CAST)
	public void fixObsoletCast(final Issue issue, IssueResolutionAcceptor acceptor) {
		String fixup = "Remove unnecessary cast";
		acceptor.accept(issue, fixup, fixup, null, new IModification() {
			@Override
			public void apply(IModificationContext context) throws Exception {
				final IXtextDocument document = context.getXtextDocument();
				ReplaceRegion replacement = document.tryReadOnly(new IUnitOfWork<ReplaceRegion, XtextResource>() {

					@Override
					public ReplaceRegion exec(XtextResource state) throws Exception {
						EObject type = state.getEObject(issue.getUriToProblem().fragment());
						XCastedExpression cast = EcoreUtil2.getContainerOfType(type, XCastedExpression.class);
						INode castNode = NodeModelUtils.findActualNodeFor(cast);
						INode targetNode = NodeModelUtils.findActualNodeFor(cast.getTarget());
						return new ReplaceRegion(castNode.getTotalTextRegion(), targetNode.getText());
					}
				});
				if (replacement != null) {
					document.replace(replacement.getOffset(), replacement.getLength(), replacement.getText());
				}
			}
		});
	}
	
	@Fix(IssueCodes.FEATURE_NOT_VISIBLE)
	public void fixInvisibleFeature(final Issue issue, IssueResolutionAcceptor acceptor) {
		if (issue.getData() != null && issue.getData().length >= 1 && "subclass-context".equals(issue.getData()[0])) {
			String fixup;
			if (issue.getData().length == 1)
				fixup = "Add type cast.";
			else
				fixup = "Add cast to " + issue.getData()[1] + ".";
			acceptor.accept(issue, fixup, fixup, null,
					new ISemanticModification() {

						@Override
						public void apply(EObject element, IModificationContext context) throws Exception {
							if (!(element instanceof XAbstractFeatureCall))
								return;
							XAbstractFeatureCall featureCall = (XAbstractFeatureCall) element;
							if (!(featureCall.getFeature() instanceof JvmMember))
								return;
							JvmType declaringType = ((JvmMember) featureCall.getFeature()).getDeclaringType();
							if (featureCall instanceof XMemberFeatureCall) {
								addTypeCastToExplicitReceiver(featureCall, context, declaringType,
										XbasePackage.Literals.XMEMBER_FEATURE_CALL__MEMBER_CALL_TARGET);
							} else if (featureCall instanceof XAssignment) {
								addTypeCastToExplicitReceiver(featureCall, context, declaringType,
										XbasePackage.Literals.XASSIGNMENT__ASSIGNABLE);
							} else if (featureCall instanceof XFeatureCall) {
								addTypeCastToImplicitReceiver((XFeatureCall) featureCall, context, declaringType);
							}
						}

					});
		}
	}
	
	@Fix(IssueCodes.UNUSED_LOCAL_VARIABLE)
	public void removeUnusedLocalVariable(final Issue issue, IssueResolutionAcceptor acceptor) {
		// use the same label, description and image
		// to be able to use the quickfixes (issue resolution) in batch mode
		String label = "Remove local variable.";
		String description = "Remove the unused local variable.";
		String image = "delete_edit.png";
		acceptor.acceptMulti(issue, label, description, image, (ICompositeModification<XVariableDeclaration>) (element, ctx) -> {
			ctx.setUpdateCrossReferences(false);
			ctx.setUpdateRelatedFiles(false);
			ctx.addModification(element, ele -> EcoreUtil.remove(ele));
		});
	}

	private void addTypeCastToExplicitReceiver(XAbstractFeatureCall featureCall, IModificationContext context,
			JvmType declaringType, EReference structuralFeature) throws BadLocationException {
		List<INode> nodes = NodeModelUtils.findNodesForFeature(featureCall, structuralFeature);
		if (nodes.isEmpty())
			return;
		INode firstNode = IterableExtensions.head(nodes);
		INode lastNode = IterableExtensions.last(nodes);
		int offset = firstNode.getOffset();
		int length = lastNode.getEndOffset() - offset;
		ReplacingAppendable appendable = appendableFactory.create(context.getXtextDocument(),
				(XtextResource) featureCall.eResource(), offset, length);
		appendable.append("(");
		ListIterator<INode> nodeIter = nodes.listIterator();
		while (nodeIter.hasNext()) {
			String text = nodeIter.next().getText();
			if (nodeIter.previousIndex() == 0)
				appendable.append(Strings.removeLeadingWhitespace(text));
			else if (nodeIter.nextIndex() == nodes.size())
				appendable.append(Strings.trimTrailingLineBreak(text));
			else
				appendable.append(text);
		}
		appendable.append(" as ");
		appendable.append(declaringType);
		appendable.append(")");
		appendable.commitChanges();
	}
	
	private void addTypeCastToImplicitReceiver(XFeatureCall featureCall, IModificationContext context,
			JvmType declaringType) throws BadLocationException {
		String receiver;
		if (featureCall.getImplicitReceiver() instanceof XAbstractFeatureCall)
			receiver = ((XAbstractFeatureCall) featureCall.getImplicitReceiver()).getFeature().getSimpleName();
		else return;
		List<INode> nodes = NodeModelUtils.findNodesForFeature(featureCall,
				XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE);
		if (nodes.isEmpty())
			return;
		INode firstNode = IterableExtensions.head(nodes);
		int offset = firstNode.getOffset();
		ReplacingAppendable appendable = appendableFactory.create(context.getXtextDocument(),
				(XtextResource) featureCall.eResource(), offset, 0);
		appendable.append("(");
		appendable.append(receiver);
		appendable.append(" as ");
		appendable.append(declaringType);
		appendable.append(").");
		appendable.commitChanges();
	}

	@Fix(IssueCodes.REDUNDANT_CASE)
	public void fixRedundantCase(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Remove redundant case.", "Remove redundant case.", null, new ReplaceModification(issue,
				""));
		acceptor.accept(issue, "Assign empty expression.", "Assign empty expression.", null,
				new ISemanticModification() {

					@Override
					public void apply(EObject element, IModificationContext context) throws Exception {
						XSwitchExpression switchExpression = EcoreUtil2.getContainerOfType(element,
								XSwitchExpression.class);
						if (switchExpression == null) {
							return;
						}
						XCasePart casePart = IterableExtensions.last(switchExpression.getCases());
						if (casePart == null || !(casePart.isFallThrough() && casePart.getThen() == null)) {
							return;
						}
						List<INode> nodes = NodeModelUtils.findNodesForFeature(casePart,
								XbasePackage.Literals.XCASE_PART__FALL_THROUGH);
						if (nodes.isEmpty()) {
							return;
						}
						INode firstNode = IterableExtensions.head(nodes);
						INode lastNode = IterableExtensions.last(nodes);
						int offset = firstNode.getOffset();
						int length = lastNode.getEndOffset() - offset;
						ReplacingAppendable appendable = appendableFactory.create(context.getXtextDocument(),
								(XtextResource) element.eResource(), offset, length);
						appendable.append(": {");
						appendable.increaseIndentation().newLine();
						appendable.decreaseIndentation().newLine().append("}");
						appendable.commitChanges();
					}

				});
	}

	@Fix(IssueCodes.INCOMPLETE_CASES_ON_ENUM)
	public void fixIncompleteCasesOnEnum(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Add 'default' case", "Add 'default' case", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				XSwitchExpression switchExpression = EcoreUtil2.getContainerOfType(element, XSwitchExpression.class);
				if (switchExpression == null) {
					return;
				}
				int insertOffset = getInsertOffset(switchExpression);
				IXtextDocument document = context.getXtextDocument();
				ReplacingAppendable appendable = appendableFactory.create(document,
						(XtextResource) element.eResource(), insertOffset, 0);
				if (switchExpression.getCases().isEmpty()) {
					appendable.increaseIndentation();
				}
				appendable.newLine();
				appendable.append("default: {");
				appendable.newLine().append("}");
				appendable.commitChanges();
			}

		});
		acceptor.accept(issue, "Add missing cases", "Add missing cases", null, new ISemanticModification() {
			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				XSwitchExpression switchExpression = EcoreUtil2.getContainerOfType(element, XSwitchExpression.class);
				if (switchExpression == null) {
					return;
				}
				int insertOffset = getInsertOffset(switchExpression);
				IXtextDocument document = context.getXtextDocument();
				ReplacingAppendable appendable = appendableFactory.create(document,
						(XtextResource) element.eResource(), insertOffset, 0);
				if (switchExpression.getCases().isEmpty()) {
					appendable.increaseIndentation();
				}
				for (String expectedEnumerationLiteral : issue.getData()) {
					appendable.newLine().append("case ").append(expectedEnumerationLiteral).append(": {");
					appendable.newLine().append("}");
				}
				appendable.commitChanges();
			}

		});
	}

	private int getInsertOffset(XSwitchExpression switchExpression) {
		EList<XCasePart> cases = switchExpression.getCases();
		if (cases.isEmpty()) {
			return insertionOffsets.inEmpty(switchExpression);
		}
		XCasePart casePart = IterableExtensions.last(cases);
		return insertionOffsets.after(casePart);
	}

	@Fix(IssueCodes.UNREACHABLE_CASE)
	public void fixUnreachableCase(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Remove case", "Remove case", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				remove(element, XCasePart.class, context);
			}

		});
		acceptor.accept(issue, "Move case up", "Move case up", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				XCasePart casePart = EcoreUtil2.getContainerOfType(element, XCasePart.class);
				if (casePart == null) {
					return;
				}
				ICompositeNode caseNode = NodeModelUtils.findActualNodeFor(casePart);
				if (caseNode == null) {
					return;
				}
				XSwitchExpression switchExpression = EcoreUtil2.getContainerOfType(casePart, XSwitchExpression.class);
				if (switchExpression == null) {
					return;
				}
				ITypeReferenceOwner owner = new StandardTypeReferenceOwner(services, switchExpression);
				LightweightTypeReference actualTypeReference = owner.toLightweightTypeReference(casePart.getTypeGuard());
				for (XCasePart previousCasePart : switchExpression.getCases()) {
					if (previousCasePart == casePart) {
						return;
					}
					JvmTypeReference typeGuard = previousCasePart.getTypeGuard();
					if (typeGuard == null || previousCasePart.getCase() != null) {
						continue;
					}
					LightweightTypeReference previousTypeReference = owner.toLightweightTypeReference(typeGuard);
					if (typesOrderUtil.isHandled(actualTypeReference, previousTypeReference)) {
						ICompositeNode previousCaseNode = NodeModelUtils.findActualNodeFor(previousCasePart);
						if (previousCaseNode == null) {
							return;
						}
						moveUp(caseNode, previousCaseNode, context);
						return;
					}
				}
			}

		});
	}

	@Fix(IssueCodes.UNREACHABLE_CATCH_BLOCK)
	public void fixUnreachableCatchBlock(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Remove catch block", "Remove catch block", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				remove(element, XCatchClause.class, context);
			}

		});
		acceptor.accept(issue, "Move catch block up", "Move catch block up", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				XCatchClause catchClause = EcoreUtil2.getContainerOfType(element, XCatchClause.class);
				if (catchClause == null) {
					return;
				}
				ICompositeNode node = NodeModelUtils.findActualNodeFor(catchClause);
				if (node == null) {
					return;
				}
				XTryCatchFinallyExpression tryCatchFinallyExpression = EcoreUtil2.getContainerOfType(catchClause,
						XTryCatchFinallyExpression.class);
				if (tryCatchFinallyExpression == null) {
					return;
				}
				ITypeReferenceOwner owner = new StandardTypeReferenceOwner(services, tryCatchFinallyExpression);
				LightweightTypeReference actualTypeReference = owner.toLightweightTypeReference(catchClause
						.getDeclaredParam().getParameterType());
				for (XCatchClause previousCatchClause : tryCatchFinallyExpression.getCatchClauses()) {
					if (previousCatchClause == catchClause) {
						return;
					}
					LightweightTypeReference previousTypeReference = owner
							.toLightweightTypeReference(previousCatchClause.getDeclaredParam().getParameterType());
					if (typesOrderUtil.isHandled(actualTypeReference, previousTypeReference)) {
						ICompositeNode previousNode = NodeModelUtils.findActualNodeFor(previousCatchClause);
						if (previousNode == null) {
							return;
						}
						moveUp(node, previousNode, context);
						return;
					}
				}
			}

		});
	}

	@Fix(IssueCodes.UNREACHABLE_IF_BLOCK)
	public void fixUnreachableIfBlock(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Remove if block", "Remove if block", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				XIfExpression ifExpression = EcoreUtil2.getContainerOfType(element, XIfExpression.class);
				if (ifExpression == null) {
					return;
				}
				ICompositeNode node = NodeModelUtils.findActualNodeFor(ifExpression);
				if (node == null) {
					return;
				}
				int[] offsetAndLength = getOffsetAndLength(ifExpression, node);
				context.getXtextDocument().replace(offsetAndLength[0], offsetAndLength[1], "");
			}

		});
		acceptor.accept(issue, "Move if block up", "Move if block up", null, new ISemanticModification() {

			@Override
			public void apply(EObject element, IModificationContext context) throws Exception {
				XIfExpression ifExpression = EcoreUtil2.getContainerOfType(element, XIfExpression.class);
				if (ifExpression == null) {
					return;
				}
				ICompositeNode node = NodeModelUtils.findActualNodeFor(ifExpression);
				if (node == null) {
					return;
				}
				XIfExpression firstIfExpression = getFirstIfExpression(ifExpression);
				if (firstIfExpression == null) {
					return;
				}
				XInstanceOfExpression actualIfPart = (XInstanceOfExpression) ifExpression.getIf();
				XAbstractFeatureCall actualFeatureCall = (XAbstractFeatureCall) actualIfPart.getExpression();
				JvmIdentifiableElement actualFeature = actualFeatureCall.getFeature();
				ITypeReferenceOwner owner = new StandardTypeReferenceOwner(services, firstIfExpression);
				LightweightTypeReference actualTypeReference = owner.toLightweightTypeReference(actualIfPart.getType());
				List<XExpression> ifParts = collectIfParts(firstIfExpression, new ArrayList<XExpression>());
				for (XExpression previousIfPart : ifParts) {
					if (actualIfPart == previousIfPart) {
						return;
					}
					if (!(previousIfPart instanceof XInstanceOfExpression)) {
						continue;
					}
					XInstanceOfExpression instanceOfExpression = (XInstanceOfExpression) previousIfPart;
					if (!(instanceOfExpression.getExpression() instanceof XAbstractFeatureCall)) {
						continue;
					}
					XAbstractFeatureCall previousFeatureCall = (XAbstractFeatureCall) instanceOfExpression
							.getExpression();
					if (previousFeatureCall.getFeature() != actualFeature) {
						continue;
					}
					LightweightTypeReference previousTypeReference = owner
							.toLightweightTypeReference(instanceOfExpression.getType());
					if (typesOrderUtil.isHandled(actualTypeReference, previousTypeReference)) {
						ICompositeNode previousNode = NodeModelUtils.findActualNodeFor(instanceOfExpression
								.eContainer());
						if (previousNode == null) {
							return;
						}
						int[] offsetAndLength = getOffsetAndLength(ifExpression, node);
						int offset = offsetAndLength[0];
						int length = offsetAndLength[1];
						int endOffset = offset + length;
						String text = node.getRootNode().getText().substring(offset, endOffset).trim();
						if (text.startsWith("else")) {
							text = text.substring("else".length()).trim();
						}
						if (!text.endsWith("else")) {
							text = text + " else ";
						} else {
							text = text + " ";
						}

						IXtextDocument document = context.getXtextDocument();
						document.replace(offset, length, "");
						document.replace(previousNode.getOffset(), 0, text);
						return;
					}
				}
			}

			private XIfExpression getFirstIfExpression(XIfExpression ifExpression) {
				EObject container = ifExpression.eContainer();
				if (container instanceof XIfExpression) {
					XIfExpression parentIfExpression = (XIfExpression) container;
					if (parentIfExpression.getElse() == ifExpression) {
						return getFirstIfExpression(parentIfExpression);
					}
				}
				return ifExpression;
			}

			private List<XExpression> collectIfParts(XIfExpression expression, List<XExpression> result) {
				result.add(expression.getIf());
				if (expression.getElse() instanceof XIfExpression) {
					collectIfParts((XIfExpression) expression.getElse(), result);
				}
				return result;
			}

		});
	}

	protected int[] getOffsetAndLength(XIfExpression ifExpression, ICompositeNode node) {
		int offset = node.getOffset();
		int length = node.getLength();
		if (ifExpression.getElse() != null) {
			ICompositeNode elseNode = NodeModelUtils.findActualNodeFor(ifExpression.getElse());
			if (elseNode != null) {
				length = elseNode.getOffset() - offset;
			}
		} else {
			XIfExpression parentIfExpression = EcoreUtil2.getContainerOfType(ifExpression.eContainer(),
					XIfExpression.class);
			if (parentIfExpression != null && parentIfExpression.getElse() == ifExpression) {
				ICompositeNode thenNode = NodeModelUtils.findActualNodeFor(parentIfExpression.getThen());
				if (thenNode != null) {
					int endOffset = thenNode.getEndOffset();
					length = length + (offset - endOffset);
					offset = endOffset;
				}
			}
		}
		return new int[] { offset, length };
	}

	protected void moveUp(ICompositeNode node, ICompositeNode previousNode, IModificationContext context)
			throws BadLocationException {
		IXtextDocument document = context.getXtextDocument();
		String text = node.getText() + previousNode.getText();
		text = text.trim();
		remove(document, node);
		document.replace(previousNode.getOffset(), previousNode.getLength(), text);
	}

	protected <T extends EObject> void remove(EObject element, Class<T> type, IModificationContext context)
			throws BadLocationException {
		T container = EcoreUtil2.getContainerOfType(element, type);
		if (container == null) {
			return;
		}
		ICompositeNode node = NodeModelUtils.findActualNodeFor(container);
		if (node == null) {
			return;
		}
		remove(context.getXtextDocument(), node);
	}

	protected void remove(IXtextDocument document, ICompositeNode node) throws BadLocationException {
		int offset = node.getOffset();
		int length = node.getLength();
		if (node.hasPreviousSibling()) {
			INode previousSibling = node.getPreviousSibling();
			int endOffset = previousSibling.getEndOffset();
			length = length + (offset - endOffset);
			offset = endOffset;
		}
		document.replace(offset, length, "");
	}

	protected void organizeImports(final Issue issue, IssueResolutionAcceptor acceptor) {
		acceptor.accept(issue, "Organize imports",
				"Organizes the whole import section. Removes wildcard imports as well as duplicates and unused ones.",
				getOrganizeImportsImage(), new IModification() {
					@Override
					public void apply(IModificationContext context) throws Exception {
						organizeImportsHandler.doOrganizeImports(context.getXtextDocument());
					}
				});
	}

	protected String getOrganizeImportsImage() {
		return "impc_obj.gif";
	}

	/**
	 * Filter quickfixes for types and constructors.
	 */
	@Override
	public void createLinkingIssueResolutions(final Issue issue, final IssueResolutionAcceptor issueResolutionAcceptor) {
		final IModificationContext modificationContext = getModificationContextFactory().createModificationContext(
				issue);
		final IXtextDocument xtextDocument = modificationContext.getXtextDocument();
		if (xtextDocument != null) {
			xtextDocument.tryReadOnly(new CancelableUnitOfWork<Void, XtextResource>() {
				@Override
				public java.lang.Void exec(XtextResource state, CancelIndicator cancelIndicator) throws Exception {
					try {
						EObject target = state.getEObject(issue.getUriToProblem().fragment());
						EReference reference = getUnresolvedEReference(issue, target);
						if (reference != null && reference.getEReferenceType() != null) {
							createLinkingIssueQuickfixes(issue,
									getCancelableAcceptor(issueResolutionAcceptor, cancelIndicator), xtextDocument,
									state, target, reference);
						}
					} catch (WrappedException e) {
						// issue information seems to be out of sync, e.g. there is no
						// EObject with the given fragment
					}
					return null;
				}
			});
		}
	}

	protected void createLinkingIssueQuickfixes(Issue issue, IssueResolutionAcceptor issueResolutionAcceptor,
			IXtextDocument xtextDocument, XtextResource state, EObject target, EReference reference) throws Exception {
		javaTypeQuickfixes.addQuickfixes(issue, issueResolutionAcceptor, xtextDocument, state, target, reference);
		createJavaTypeQuickfixes.addQuickfixes(issue, issueResolutionAcceptor, xtextDocument, state, target, reference);
	}

	@Override
	protected EReference getUnresolvedEReference(Issue issue, EObject target) {
		EReference unresolvedEReference = super.getUnresolvedEReference(issue, target);
		if (unresolvedEReference == null && target instanceof XConstructorCall)
			return XbasePackage.Literals.XCONSTRUCTOR_CALL__CONSTRUCTOR;
		else
			return unresolvedEReference;
	}
}