/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.texteditor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;

import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
import org.eclipse.jface.text.quickassist.IQuickFixableAnnotation;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IAnnotationAccessExtension;
import org.eclipse.jface.text.source.IAnnotationAccessExtension2;
import org.eclipse.jface.text.source.IAnnotationPresentation;
import org.eclipse.jface.text.source.ImageUtilities;
import org.eclipse.jface.text.source.projection.AnnotationBag;

import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.internal.texteditor.AnnotationType;
import org.eclipse.ui.internal.texteditor.AnnotationTypeHierarchy;


/**
 * Default class for accessing marker annotation properties.
 *
 * @since 2.1
 */
public class DefaultMarkerAnnotationAccess implements IAnnotationAccess, IAnnotationAccessExtension, IAnnotationAccessExtension2 {

	/**
	 * Constant for the unknown marker type.
	 *
	 * @deprecated As of 3.0, replaced by Annotation.TYPE_UNKNOWN
	 */
	@Deprecated
	public static final String UNKNOWN= Annotation.TYPE_UNKNOWN;

	/**
	 * Constant for the error system image.
	 * Value: <code>error</code>
	 *
	 * @since 3.0
	 */
	public static final String ERROR_SYSTEM_IMAGE= "error"; //$NON-NLS-1$
	/**
	 * Constant for the warning system image.
	 * Value: <code>warning</code>
	 *
	 * @since 3.0
	 */
	public static final String WARNING_SYSTEM_IMAGE= "warning"; //$NON-NLS-1$
	/**
	 * Constant for the info system image.
	 * Value: <code>info</code>
	 *
	 * @since 3.0
	 */
	public static final String INFO_SYSTEM_IMAGE= "info"; //$NON-NLS-1$
	/**
	 * Constant for the task system image.
	 * Value: <code>task</code>
	 *
	 * @since 3.0
	 */
	public static final String TASK_SYSTEM_IMAGE= "task"; //$NON-NLS-1$
	/**
	 * Constant for the bookmark system image.
	 * Value: <code>bookmark</code>
	 *
	 * @since 3.0
	 */
	public static final String BOOKMARK_SYSTEM_IMAGE= "bookmark"; //$NON-NLS-1$

	/**
	 * The mapping between external and internal symbolic system image names.
	 *
	 * @since 3.0
	 */
	private final static Map<String, String> MAPPING;

	static {
		MAPPING= new HashMap<>();
		MAPPING.put(ERROR_SYSTEM_IMAGE, ISharedImages.IMG_OBJS_ERROR_TSK);
		MAPPING.put(WARNING_SYSTEM_IMAGE, ISharedImages.IMG_OBJS_WARN_TSK);
		MAPPING.put(INFO_SYSTEM_IMAGE, ISharedImages.IMG_OBJS_INFO_TSK);
		MAPPING.put(TASK_SYSTEM_IMAGE, IDE.SharedImages.IMG_OBJS_TASK_TSK);
		MAPPING.put(BOOKMARK_SYSTEM_IMAGE, IDE.SharedImages.IMG_OBJS_BKMRK_TSK);
	}


	/**
	 * The marker annotation preferences.
	 *
	 * @deprecated As of 3.0, no replacement
	 */
	@Deprecated
	protected MarkerAnnotationPreferences fMarkerAnnotationPreferences;

	/**
	 * An optional quick assist processor.
	 *
	 * @since 3.2
	 */
	private IQuickAssistAssistant fQuickAssistAssistant;

	/**
	 * Returns a new default marker annotation access with the given preferences.
	 *
	 * @param markerAnnotationPreferences the marker annotation preference
	 * @deprecated As of 3.0, replaced by
	 *             {@link org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess#DefaultMarkerAnnotationAccess()}
	 */
	@Deprecated
	public DefaultMarkerAnnotationAccess(MarkerAnnotationPreferences markerAnnotationPreferences) {
		fMarkerAnnotationPreferences= markerAnnotationPreferences;
	}

	/**
	 * Creates a new default marker annotation access using the standard
	 * preference lookup strategy which is the one provided by the enclosing
	 * plug-in.
	 *
	 * @since 3.0
	 */
	public DefaultMarkerAnnotationAccess() {
	}

	@Override
	public void setQuickAssistAssistant(IQuickAssistAssistant assistant) {
		fQuickAssistAssistant= assistant;
	}

	/**
	 * Returns the annotation preference for the given annotation.
	 *
	 * @param annotation the annotation
	 * @return the annotation preference for the given annotation or <code>null</code>
	 */
	private AnnotationPreference getAnnotationPreference(Annotation annotation) {
		AnnotationPreferenceLookup lookup= getAnnotationPreferenceLookup();
		if (lookup != null)
			return lookup.getAnnotationPreference(annotation);
		return null;
	}

	/**
	 * Returns the annotation preference lookup used by this annotation access.
	 *
	 * @return the annotation preference lookup
	 * @since 3.0
	 */
	protected AnnotationPreferenceLookup getAnnotationPreferenceLookup() {
		return EditorsPlugin.getDefault().getAnnotationPreferenceLookup();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated use <code>Annotation.getType()</code>
	 */

	@Deprecated
	@Override
	public Object getType(Annotation annotation) {
		return annotation.getType();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated assumed to always return <code>true</code>
	 */
	@Deprecated
	@Override
	public boolean isMultiLine(Annotation annotation) {
		return true;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated assumed to always return <code>true</code>
	 */
	@Deprecated
	@Override
	public boolean isTemporary(Annotation annotation) {
		return !annotation.isPersistent();
	}

	/*
	 * @see org.eclipse.jface.text.source.IAnnotationAccessExtension#getLabel(org.eclipse.jface.text.source.Annotation)
	 * @since 3.0
	 */
	@Override
	public String getTypeLabel(Annotation annotation) {
		AnnotationPreference preference= getAnnotationPreference(annotation);
		return preference != null ? preference.getPreferenceLabel() : null;
	}

	@Override
	public int getLayer(Annotation annotation) {
		if (annotation instanceof IAnnotationPresentation presentation) {
			return presentation.getLayer();
		}

		AnnotationPreference preference= getAnnotationPreference(annotation);
		if (preference != null)
			return preference.getPresentationLayer();

		// backward compatibility, ignore exceptions, just return default layer
		try {

			Method method= annotation.getClass().getMethod("getLayer"); //$NON-NLS-1$
			Integer result= (Integer) method.invoke(annotation);
			return result.intValue();

		} catch (SecurityException x) {
		} catch (IllegalArgumentException x) {
		} catch (NoSuchMethodException x) {
		} catch (IllegalAccessException x) {
		} catch (InvocationTargetException x) {
		}

		return IAnnotationAccessExtension.DEFAULT_LAYER;
	}

	@Override
	public void paint(Annotation annotation, GC gc, Canvas canvas, Rectangle bounds) {

		if (annotation instanceof IAnnotationPresentation presentation) {
			presentation.paint(gc, canvas, bounds);
			return;
		}

		AnnotationPreference preference= getAnnotationPreference(annotation);
		if (preference != null) {
			Object type= getType(annotation);
			String annotationType= (type == null ? null : type.toString());
			Image image= getImage(annotation, preference, annotationType);
			if (image != null) {
				ImageUtilities.drawImage(image, gc, canvas, bounds, SWT.CENTER, SWT.TOP);
				return;
			}
		}

		// backward compatibility, ignore exceptions, just don't paint
		try {

			Method method= annotation.getClass().getMethod("paint", GC.class, Canvas.class, Rectangle.class); //$NON-NLS-1$
			method.invoke(annotation, gc, canvas, bounds);

		} catch (SecurityException x) {
		} catch (IllegalArgumentException x) {
		} catch (NoSuchMethodException x) {
		} catch (IllegalAccessException x) {
		} catch (InvocationTargetException x) {
		}
	}

	@Override
	public boolean isPaintable(Annotation annotation) {
		if (annotation instanceof IAnnotationPresentation)
			return true;

		AnnotationPreference preference= getAnnotationPreference(annotation);
		if (preference == null)
			return false;

		Object type= getType(annotation);
		String annotationType= (type == null ? null : type.toString());
		Image image= getImage(annotation, preference, annotationType);
		return image != null;
	}

	@Override
	public boolean isSubtype(Object annotationType, Object potentialSupertype) {
		AnnotationTypeHierarchy hierarchy= getAnnotationTypeHierarchy();
		return hierarchy.isSubtype(potentialSupertype.toString(), annotationType.toString());
	}

	@Override
	public Object[] getSupertypes(Object annotationType) {
		AnnotationTypeHierarchy hierarchy= getAnnotationTypeHierarchy();
		AnnotationType type= hierarchy.getAnnotationType(annotationType.toString());
		return type.getSuperTypes();
	}

	/**
	 * Returns the annotation type hierarchy used by this annotation access.
	 *
	 * @return the annotation type hierarchy
	 * @since 3.0
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected AnnotationTypeHierarchy getAnnotationTypeHierarchy() {
		return EditorsPlugin.getDefault().getAnnotationTypeHierarchy();
	}

	/**
	 * Translates the given symbolic image name into the shared image name as
	 * defined in {@link org.eclipse.ui.ISharedImages}.
	 * <p>
	 * The symbolic image name must be one of the
	 *
	 * @param symbolicImageName the symbolic image name, which must be one of
	 *            the valid values defined for the <code>symbolicIcon</code>
	 *            attribute in the
	 *            <code>org.eclipse.ui.editors.markerAnnotationSpecification</code>
	 *            extension point
	 * @return the shared image name
	 * @throws IllegalArgumentException if the <code>symbolicImageName</code> is not defined by the
	 * 			<code>org.eclipse.ui.editors.markerAnnotationSpecification</code> extension point
	 * @since 3.4
	 */
	public static String getSharedImageName(String symbolicImageName) {
		Assert.isLegal(symbolicImageName != null);
		String sharedImageName= MAPPING.get(symbolicImageName);
		Assert.isLegal(sharedImageName != null);
		return sharedImageName;
	}

	/**
	 * Returns the image for the given annotation and the given annotation preferences or
	 * <code>null</code> if there is no such image.
	 *
	 * @param annotation the annotation
	 * @param preference the annotation preference
	 * @param annotationType the annotation type
	 * @return the image or <code>null</code>
	 * @since 3.0
	 */
	private Image getImage(Annotation annotation, AnnotationPreference preference, String annotationType) {

		if (annotation instanceof AnnotationBag bag) {
			if (!bag.isEmpty())
				annotation= bag.iterator().next();
		}

		ImageRegistry registry= EditorsPlugin.getDefault().getImageRegistry();

		IAnnotationImageProvider annotationImageProvider = preference.getAnnotationImageProvider();
		if (annotationImageProvider != null) {

			Image image= annotationImageProvider.getManagedImage(annotation);
			if (image != null)
				return image;

			String id= annotationImageProvider.getImageDescriptorId(annotation);
			if (id != null) {
				image= registry.get(id);
				if (image == null) {
					ImageDescriptor descriptor= annotationImageProvider.getImageDescriptor(id);
					registry.put(id, descriptor);
					image= registry.get(id);
				}
				return image;
			}
		}

		if (annotationType == null)
			return null;

		if (hasQuickFix(annotation)) {
			ImageDescriptor quickFixImageDesc= preference.getQuickFixImageDescriptor();
			if (quickFixImageDesc != null) {
				Image image= registry.get(quickFixImageDesc.toString());
				if (image == null) {
					registry.put(quickFixImageDesc.toString(), quickFixImageDesc);
					image= registry.get(quickFixImageDesc.toString());
				}
				if (image != null)
					return image;
			}
		}

		Image image= registry.get(annotationType);
		if (image == null) {
			ImageDescriptor descriptor= preference.getImageDescriptor();
			if (descriptor != null) {
				registry.put(annotationType, descriptor);
				image= registry.get(annotationType);
			} else {
				String symbolicImageName= preference.getSymbolicImageName();
				if (symbolicImageName != null) {
					String key= getSharedImageName(preference.getSymbolicImageName());
					if (key != null) {
						ISharedImages sharedImages= PlatformUI.getWorkbench().getSharedImages();
						image= sharedImages.getImage(key);
					}
				}
			}
		}
		return image;
	}

	/**
	 * Checks whether there's a quick assist assistant and if so,
	 * whether the assistant has a possible fix for the given
	 * annotation.
	 *
	 * @param annotation the annotation
	 * @return <code>true</code> if there is quick fix
	 * @since 3.2
	 */
	protected boolean hasQuickFix(Annotation annotation) {
		if (annotation instanceof IQuickFixableAnnotation quickFixableAnnotation) {
			if (!quickFixableAnnotation.isQuickFixableStateSet())
				quickFixableAnnotation.setQuickFixable(fQuickAssistAssistant != null && fQuickAssistAssistant.canFix(annotation));
			return quickFixableAnnotation.isQuickFixable();
		}
		return false;
	}

}
