/*******************************************************************************
 * Copyright (c) 2011, 2013 GK Software AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Stephan Herrmann - [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
 *     IBM Corporation - bug fixes
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;

import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;

import org.eclipse.swt.graphics.Image;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;

import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.NullAnnotationsFix;
import org.eclipse.jdt.internal.corext.fix.NullAnnotationsRewriteOperations.ChangeKind;

import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;

import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.fix.NullAnnotationsCleanUp;
import org.eclipse.jdt.internal.ui.text.correction.proposals.ExtractToNullCheckedLocalProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;

/**
 * Quick Fixes for null-annotation related problems.
 */
public class NullAnnotationsCorrectionProcessor {

	// pre: changeKind != OVERRIDDEN
	public static void addReturnAndArgumentTypeProposal(IInvocationContext context, IProblemLocation problem, ChangeKind changeKind, 
			Collection<ICommandAccess> proposals) {
		CompilationUnit astRoot= context.getASTRoot();
		ASTNode selectedNode= problem.getCoveringNode(astRoot);

		boolean isArgumentProblem= NullAnnotationsFix.isComplainingAboutArgument(selectedNode);
		if (isArgumentProblem || NullAnnotationsFix.isComplainingAboutReturn(selectedNode))
			addNullAnnotationInSignatureProposal(context, problem, proposals, changeKind, isArgumentProblem);
	}

	public static void addNullAnnotationInSignatureProposal(IInvocationContext context, IProblemLocation problem,
			Collection<ICommandAccess> proposals, ChangeKind changeKind, boolean isArgumentProblem) {
		NullAnnotationsFix fix= NullAnnotationsFix.createNullAnnotationInSignatureFix(context.getASTRoot(), problem,
				changeKind, isArgumentProblem);

		if (fix != null) {
			Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
			Map<String, String> options= new Hashtable<String, String>();
			if (fix.getCu() != context.getASTRoot()) {
				// workaround: adjust the unit to operate on, depending on the findings of RewriteOperations.createAddAnnotationOperation(..)
				final CompilationUnit cu= fix.getCu();
				final IInvocationContext originalContext= context;
				context= new IInvocationContext() {
					public int getSelectionOffset() {
						return originalContext.getSelectionOffset();
					}

					public int getSelectionLength() {
						return originalContext.getSelectionLength();
					}

					public ASTNode getCoveringNode() {
						return originalContext.getCoveringNode();
					}

					public ASTNode getCoveredNode() {
						return originalContext.getCoveredNode();
					}

					public ICompilationUnit getCompilationUnit() {
						return (ICompilationUnit) cu.getJavaElement();
					}

					public CompilationUnit getASTRoot() {
						return cu;
					}
				};
			}
			int relevance= (changeKind == ChangeKind.OVERRIDDEN) ? IProposalRelevance.CHANGE_NULLNESS_ANNOTATION_IN_OVERRIDDEN_METHOD : IProposalRelevance.CHANGE_NULLNESS_ANNOTATION; //raise local change above change in overridden method
			FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), relevance, image, context);
			proposals.add(proposal);
		}
	}

	public static void addRemoveRedundantAnnotationProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
		NullAnnotationsFix fix= NullAnnotationsFix.createRemoveRedundantNullAnnotationsFix(context.getASTRoot(), problem);
		if (fix == null)
			return;

		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
		Map<String, String> options= new Hashtable<String, String>();
		FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new NullAnnotationsCleanUp(options, problem.getProblemId()), IProposalRelevance.REMOVE_REDUNDANT_NULLNESS_ANNOTATION, image, context);
		proposals.add(proposal);
	}

	/**
	 * Fix for {@link IProblem#NullableFieldReference}
	 * @param context context
	 * @param problem problem to be fixed
	 * @param proposals accumulator for computed proposals
	 */
	public static void addExtractCheckedLocalProposal(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals) {
		CompilationUnit compilationUnit = context.getASTRoot();
		ICompilationUnit cu= (ICompilationUnit) compilationUnit.getJavaElement();
	
		ASTNode selectedNode= problem.getCoveringNode(compilationUnit);

		SimpleName name= findProblemFieldName(selectedNode, problem.getProblemId());
		if (name == null)
			return;

		ASTNode method= ASTNodes.getParent(selectedNode, MethodDeclaration.class);
		if (method == null)
			method= ASTNodes.getParent(selectedNode, Initializer.class);
		if (method == null)
			return;
		
		proposals.add(new ExtractToNullCheckedLocalProposal(cu, compilationUnit, name, method));
	}
	private static SimpleName findProblemFieldName(ASTNode selectedNode, int problemID) {
		// if a field access occurs in an compatibility situation (assignment/return/argument)
		// with expected type declared @NonNull we first need to find the SimpleName inside:
		if (selectedNode instanceof FieldAccess)
			selectedNode= ((FieldAccess) selectedNode).getName();
		else if (selectedNode instanceof QualifiedName)
			selectedNode= ((QualifiedName) selectedNode).getName();

		if (selectedNode instanceof SimpleName) {
			SimpleName name= (SimpleName) selectedNode;
			if (problemID == IProblem.NullableFieldReference)
				return name;
			// not field dereference, but compatibility issue - is value a field reference?
			IBinding binding= name.resolveBinding();
			if ((binding instanceof IVariableBinding) && ((IVariableBinding) binding).isField())
				return name;
		}
		return null;
	}
}
