/*******************************************************************************
 * Copyright (c) 2011 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.common.ui.marker;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jpt.common.core.internal.utility.jdt.ASTTools;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.ui.IMarkerResolution2;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.jboss.tools.common.EclipseUtil;
import org.jboss.tools.common.preferences.SeverityPreferences;
import org.jboss.tools.common.refactoring.MarkerResolutionUtils;
import org.jboss.tools.common.ui.CommonUIMessages;
import org.jboss.tools.common.ui.CommonUIPlugin;
import org.jboss.tools.common.validation.WarningNameManager;
import org.osgi.service.prefs.BackingStoreException;

/**
 * @author Daniel Azarov
 */
public class AddSuppressWarningsMarkerResolution implements
		IMarkerResolution2, IJavaCompletionProposal {
	public static final String SUPPRESS_WARNINGS_ANNOTATION = "SuppressWarnings";
	public static final String SPACE = " ";  //$NON-NLS-1$
	public static final String DOT = ".";  //$NON-NLS-1$
	public static final String AT = "@";  //$NON-NLS-1$
	private static final String PROBLEM_ID = JavaCore.COMPILER_PB_UNHANDLED_WARNING_TOKEN;
	private SP preferences = new SP();

	private IFile file;
	private IAnnotatable element;
	private String preferenceKey;
	private String label;
	private String description;
	private ICompilationUnit cu;
	
	public AddSuppressWarningsMarkerResolution(IFile file, IJavaElement element, String preferenceKey){
		this.file = file;
		this.element = getAnnatatableElement(element);
		String[] names = WarningNameManager.getInstance().getWarningNames(preferenceKey);
		if(names != null && names.length > 0){
			this.preferenceKey = names[0];
		}else{
			this.preferenceKey = null;
		}
		
		label = NLS.bind(CommonUIMessages.ADD_SUPPRESS_WARNINGS_TITLE, this.preferenceKey, element.getElementName());
		if(element instanceof IMethod){
			label += "()";
		}
		
		description = getPreview();
	}
	
	public AddSuppressWarningsMarkerResolution(IFile file, IJavaElement element, String preferenceKey, ICompilationUnit compilationUnit){
		this(file, element, preferenceKey);
		this.cu = compilationUnit;
		
		description = getPreview();
	}
	
	private String getPreview(){
		TextChange previewChange = getPreviewChange();
		
		try {
			return MarkerResolutionUtils.getPreview(previewChange);
		} catch (CoreException e) {
			CommonUIPlugin.getDefault().logError(e);
		}
		return label;
	}
	
	private IAnnotatable getAnnatatableElement(IJavaElement element){
		IJavaElement elem = element;
		while(elem != null){
			if(element instanceof IAnnotatable){
				return (IAnnotatable) element;
			}
			elem = elem.getParent();
		}
		return null;
	}

	@Override
	public String getLabel() {
		return label;
	}
	
	private TextChange getPreviewChange(){
		if(element != null && preferenceKey != null){
			try {
				ICompilationUnit original = (cu != null) ? cu : EclipseUtil.getCompilationUnit(file);
				if(original == null) {
					return null;
				}
				ICompilationUnit compilationUnit = original.getWorkingCopy(new NullProgressMonitor());
				
				TextChange change = getChange(compilationUnit);
				
				compilationUnit.discardWorkingCopy();
				return change;
			} catch (JavaModelException e) {
				CommonUIPlugin.getDefault().logError(e);
			}
		}
		return null;
	}
	
	private CompilationUnitChange getChange(ICompilationUnit compilationUnit) throws JavaModelException{
		CompilationUnit cuNode = ASTTools.buildASTRoot(compilationUnit);
		
		IJavaElement workingCopyElement = findWorkingCopy(compilationUnit, (IJavaElement)element);
		ASTNode elementNode = null;
		if(workingCopyElement instanceof JavaElement){
			elementNode = ((JavaElement) workingCopyElement).findNode(cuNode);
		}
		
		IAnnotation annotation = findAnnotation(workingCopyElement);
		CompilationUnitChange change = null;
		if(annotation != null){
			change = updateAnnotation(SUPPRESS_WARNINGS_ANNOTATION, preferenceKey, compilationUnit, annotation);
		}else{
			change = addAnnotation(SUPPRESS_WARNINGS_ANNOTATION+"(\""+preferenceKey+"\")", compilationUnit, workingCopyElement, elementNode);
		}
		return change;
	}

	@Override
	public void run(IMarker marker) {
		ICompilationUnit original = EclipseUtil.getCompilationUnit(file);
		if(original != null) {
			do_run(original, false);
		}
	}
	
	private void do_run(ICompilationUnit original, boolean leaveDirty){
		if(element != null && preferenceKey != null && original != null){
			disablePreference();
			try {
				ICompilationUnit compilationUnit = original.getWorkingCopy(new NullProgressMonitor());
				
				CompilationUnitChange change = getChange(compilationUnit);
				
				if(change != null){
					if(leaveDirty){
						change.setSaveMode(TextFileChange.LEAVE_DIRTY);
					}
					change.perform(new NullProgressMonitor());
					original.reconcile(ICompilationUnit.NO_AST, false, null, new NullProgressMonitor());
				}
				compilationUnit.discardWorkingCopy();
			} catch (JavaModelException e) {
				CommonUIPlugin.getDefault().logError(e);
			} catch (CoreException e) {
				CommonUIPlugin.getDefault().logError(e);
			}
		}
	}
	
	private IAnnotation findAnnotation(IJavaElement workingCopyElement) throws JavaModelException{
		IAnnotation annotation = ((IAnnotatable)workingCopyElement).getAnnotation(SUPPRESS_WARNINGS_ANNOTATION);
		if(annotation != null && annotation.exists()){
			return annotation;
		}
		
		return null;
	}
	
	public static String getShortName(String qualifiedName){
		int lastDot = qualifiedName.lastIndexOf(DOT);
		String name;
		if(lastDot < 0)
			name = qualifiedName;
		else
			name = qualifiedName.substring(lastDot+1);
		return name;
	}
	
	private void disablePreference(){
		String value = preferences.getProjectPreference(file.getProject(), PROBLEM_ID);
		if(!SeverityPreferences.IGNORE.equals(value)){
			
			IEclipsePreferences projectPreferences = preferences.getProjectPreferences(file.getProject());
			String projectValue = null;
			if(projectPreferences != null){
				projectValue = projectPreferences.get(PROBLEM_ID, null);
			}
			
			if(projectValue != null){
				 MessageDialog dialog = new MessageDialog(getShell(), label, null,
						 CommonUIMessages.ADD_SUPPRESS_WARNINGS_MESSAGE+
						 CommonUIMessages.ADD_SUPPRESS_WARNINGS_QUESTION1,
							MessageDialog.QUESTION_WITH_CANCEL,
							new String[]{CommonUIMessages.ADD_SUPPRESS_WARNINGS_CANCEL, CommonUIMessages.ADD_SUPPRESS_WARNINGS_DISABLE},
							0);
					int result = dialog.open();
					if(result == 1){
						IEclipsePreferences ePrefs = preferences.getProjectPreferences(file.getProject());
						ePrefs.put(PROBLEM_ID, SeverityPreferences.IGNORE);
						try {
							ePrefs.flush();
						} catch (BackingStoreException e) {
							CommonUIPlugin.getDefault().logError(e);
						}
					}
			}else{
				 MessageDialog dialog = new MessageDialog(getShell(), label, null,
						 CommonUIMessages.ADD_SUPPRESS_WARNINGS_MESSAGE+
						NLS.bind(CommonUIMessages.ADD_SUPPRESS_WARNINGS_QUESTION2, file.getProject().getName()),
						MessageDialog.QUESTION_WITH_CANCEL,
						new String[]{CommonUIMessages.ADD_SUPPRESS_WARNINGS_CANCEL, CommonUIMessages.ADD_SUPPRESS_WARNINGS_WORKSPACE, CommonUIMessages.ADD_SUPPRESS_WARNINGS_PROJECT},
						0);
				int result = dialog.open();
				if(result == 1){
					IEclipsePreferences ePrefs = preferences.getInstancePreferences();
					ePrefs.put(PROBLEM_ID, SeverityPreferences.IGNORE);
					try {
						ePrefs.flush();
					} catch (BackingStoreException e) {
						CommonUIPlugin.getDefault().logError(e);
					}
				}else if(result == 2){
					IEclipsePreferences ePrefs = preferences.getProjectPreferences(file.getProject());
					ePrefs.put(PROBLEM_ID, SeverityPreferences.IGNORE);
					try {
						ePrefs.flush();
					} catch (BackingStoreException e) {
						CommonUIPlugin.getDefault().logError(e);
					}
				}
			}
		}
	}
	
	private static Shell getShell() {
		IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (window == null) {
			IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
			if (windows.length > 0) {
				return windows[0].getShell();
			}
		}
		else {
			return window.getShell();
		}
		return null;
	}
	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public Image getImage() {
		String key = "ADD_ANNOTATION";
		ImageRegistry registry = CommonUIPlugin.getDefault().getImageRegistry();
		Image image = registry.get(key);
		if (image == null) {
			try {
				image = ImageDescriptor.createFromURL(
						new URL(CommonUIPlugin.getDefault().getBundle()
								.getEntry("/"), "icons/add_annotation.png")).createImage();
				registry.put(key, image);
			} catch (MalformedURLException e) {
				CommonUIPlugin.getDefault().logError(e);
			}
		}
		return image;
	}
	
	private CompilationUnitChange updateAnnotation(String name, String parameter, ICompilationUnit compilationUnit, IAnnotation annotation) throws JavaModelException{
		String str = AT+name;
		
		str += "({";
		
		for(IMemberValuePair pair : annotation.getMemberValuePairs()){
			if(pair.getValueKind() == IMemberValuePair.K_STRING){
				Object value = pair.getValue();
				if(value instanceof String){
					if(value.toString().equals(parameter)){
						return null;
					}
					str += "\""+value+"\", ";
				}else if(value instanceof Object[]){
					Object[] array = (Object[])value;
					for(Object a : array){
						if(a instanceof String){
							if(a.toString().equals(parameter)){
								return null;
							}
							str += "\""+a+"\", ";
						}
					}
				}
			} else if(pair.getValueKind() == IMemberValuePair.K_QUALIFIED_NAME || pair.getValueKind() == IMemberValuePair.K_SIMPLE_NAME){
				Object value = pair.getValue();
				if(value instanceof String){
					str += value+", ";
				}else if(value instanceof Object[]){
					Object[] array = (Object[])value;
					for(Object a : array){
						if(a instanceof String){
							str += a+", ";
						}
					}
				}
			}
		}
		
		str += "\""+parameter+"\"";
		
		str += "})";
		
		CompilationUnitChange change = new CompilationUnitChange("", compilationUnit);
		
		ReplaceEdit edit = new ReplaceEdit(annotation.getSourceRange().getOffset(), annotation.getSourceRange().getLength(), str);
		change.setEdit(edit);
		
		return change;
	}

	private CompilationUnitChange addAnnotation(String name, ICompilationUnit compilationUnit, IJavaElement element, ASTNode node) throws JavaModelException{
		if(!(element instanceof ISourceReference))
			return null;
		
		ISourceReference workingCopySourceReference = (ISourceReference) element;
		
		IBuffer buffer = compilationUnit.getBuffer();
		
		int position = workingCopySourceReference.getSourceRange().getOffset();
		
		if(node != null){
			position = node.getStartPosition();
			if(node instanceof BodyDeclaration && ((BodyDeclaration)node).getJavadoc() != null){
				position += ((BodyDeclaration)node).getJavadoc().getLength();
				char c = buffer.getChar(position);
				while((c == '\r' || c == '\n') && position < buffer.getLength()-2 ){
					position++;
					c = buffer.getChar(position);
				}
			}
			while(position < buffer.getLength()-1){
				char c = buffer.getChar(position);
				if(c != '\r' && c != '\n' && c != ' ' && c != '\t'){
					break;
				}
				position++;
			}
		}
		
		String str = AT+name;
		
		if(!(workingCopySourceReference instanceof ILocalVariable)){
			
			str += compilationUnit.findRecommendedLineSeparator();
			
			int index = position;
			while(index >= 0){
				char c = buffer.getChar(index);
				if(c == '\r' || c == '\n')
					break;
				index--;
			}
			index++;
			if(index < position){
				String spaces = buffer.getText(index, position-index);
				str += spaces;
			}
			
		}else{
			str += SPACE;
		}
		
		CompilationUnitChange change = new CompilationUnitChange("", compilationUnit);
		
		InsertEdit edit = new InsertEdit(position, str);
		
		change.setEdit(edit);
		
		return change;
	}
	
	@SuppressWarnings("unchecked")
	private static <T extends IJavaElement> T findWorkingCopy(ICompilationUnit compilationUnit, T element) throws JavaModelException{
		if(element instanceof IAnnotation){
			IJavaElement parent = findWorkingCopy(compilationUnit, element.getParent());
			if(parent instanceof IAnnotatable){
				for(IAnnotation a : ((IAnnotatable)parent).getAnnotations()){
					if(a.getElementName().equals(element.getElementName()))
						return (T)a;
				}
			}
		}else if(element instanceof ILocalVariable && ((ILocalVariable) element).isParameter()){
			IJavaElement parent = findWorkingCopy(compilationUnit, element.getParent());
			if(parent instanceof IMethod){
				for(ILocalVariable parameter : ((IMethod)parent).getParameters()){
					if(parameter.getElementName().equals(element.getElementName()) && parameter.getTypeSignature().equals(((ILocalVariable)element).getTypeSignature()))
						return (T)parameter;
				}
			}
		}else{
			IJavaElement[] elements = compilationUnit.findElements(element);
			if(elements != null){
				for(IJavaElement e : elements){
					if(e.getClass().equals(element.getClass()))
						return (T)e;
				}
			}
		}
		return null;
	}
	
	static class SP extends SeverityPreferences{

		@Override
		protected Set<String> getSeverityOptionNames() {
			return null;
		}

		@Override
		protected String createSeverityOption(String shortName) {
			return null;
		}

		@Override
		protected String getPluginId() {
			return JavaCore.PLUGIN_ID;
		}
		
	}

	@Override
	public void apply(IDocument document) {
		do_run(cu, true);
	}

	@Override
	public Point getSelection(IDocument document) {
		return null;
	}

	@Override
	public String getAdditionalProposalInfo() {
		return description;
	}

	@Override
	public String getDisplayString() {
		return label;
	}

	@Override
	public IContextInformation getContextInformation() {
		return null;
	}

	@Override
	public int getRelevance() {
		return 100;
	}
	
}
