/*******************************************************************************
 * Copyright (c) 2006, 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
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 440270
 *******************************************************************************/
package org.eclipse.jface.fieldassist;

import java.util.HashMap;
import java.util.Iterator;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.graphics.Image;

/**
 * FieldDecorationRegistry is a common registry used to define shared field
 * decorations within an application. Unlike resource registries, the
 * FieldDecorationRegistry does not perform any lifecycle management of the
 * decorations.
 * <p>
 * Clients may specify images for the decorations in several different ways.
 * Images may be described by their image id in a specified
 * {@link ImageRegistry}. In this case, the life cycle of the image is managed
 * by the image registry, and the decoration registry will not attempt to obtain
 * an image from the image registry until the decoration is actually requested.
 * In cases where the client has access to an already-created image, the image
 * itself can be specified when registering the decoration. In this case, the
 * life cycle should be managed by the specifying client.
 * </p>
 *
 * @see FieldDecoration
 * @see ImageRegistry
 *
 * @since 3.2
 */
public class FieldDecorationRegistry {

	/**
	 * Decoration id for the decoration that should be used to cue the user that
	 * content proposals are available.
	 */
	public static final String DEC_CONTENT_PROPOSAL = "DEC_CONTENT_PROPOSAL"; //$NON-NLS-1$

	/**
	 * Decoration id for the decoration that should be used to cue the user that
	 * a field is required.
	 */
	public static final String DEC_REQUIRED = "DEC_REQUIRED"; //$NON-NLS-1$

	/**
	 * Decoration id for the decoration that should be used to cue the user that
	 * a field has an error.
	 */
	public static final String DEC_ERROR = "DEC_ERROR"; //$NON-NLS-1$

	/**
	 * Decoration id for the decoration that should be used to cue the user that
	 * a field has a warning.
	 */
	public static final String DEC_WARNING = "DEC_WARNING"; //$NON-NLS-1$

	/**
	 * Decoration id for the decoration that should be used to cue the user that
	 * a field has additional information.
	 *
	 * @since 3.3
	 */
	public static final String DEC_INFORMATION = "DEC_INFORMATION"; //$NON-NLS-1$

	/**
	 * Decoration id for the decoration that should be used to cue the user that
	 * a field has an error with quick fix available.
	 *
	 * @since 3.3
	 */
	public static final String DEC_ERROR_QUICKFIX = "DEC_ERRORQUICKFIX"; //$NON-NLS-1$

	/*
	 * Image id's
	 */
	private static final String IMG_DEC_FIELD_CONTENT_PROPOSAL = "org.eclipse.jface.fieldassist.IMG_DEC_FIELD_CONTENT_PROPOSAL"; //$NON-NLS-1$

	private static final String IMG_DEC_FIELD_REQUIRED = "org.eclipse.jface.fieldassist.IMG_DEC_FIELD_REQUIRED"; //$NON-NLS-1$

	private static final String IMG_DEC_FIELD_ERROR = "org.eclipse.jface.fieldassist.IMG_DEC_FIELD_ERROR"; //$NON-NLS-1$

	private static final String IMG_DEC_FIELD_ERROR_QUICKFIX = "org.eclipse.jface.fieldassist.IMG_DEC_FIELD_ERROR_QUICKFIX"; //$NON-NLS-1$

	private static final String IMG_DEC_FIELD_WARNING = "org.eclipse.jface.fieldassist.IMG_DEC_FIELD_WARNING"; //$NON-NLS-1$

	private static final String IMG_DEC_FIELD_INFO = "org.eclipse.jface.fieldassist.IMG_DEC_FIELD_INFO"; //$NON-NLS-1$

	/*
	 * Declare images and decorations immediately.
	 */
	static {
		ImageRegistry imageRegistry = JFaceResources.getImageRegistry();

		// Define the images used in the standard decorations.
		imageRegistry.put(IMG_DEC_FIELD_CONTENT_PROPOSAL, ImageDescriptor
				.createFromFile(FieldDecorationRegistry.class,
						"images/contassist_ovr.png"));//$NON-NLS-1$
		imageRegistry.put(IMG_DEC_FIELD_ERROR, ImageDescriptor.createFromFile(
				FieldDecorationRegistry.class, "images/error_ovr.png"));//$NON-NLS-1$

		imageRegistry.put(IMG_DEC_FIELD_WARNING, ImageDescriptor
				.createFromFile(FieldDecorationRegistry.class,
						"images/warn_ovr.png"));//$NON-NLS-1$

		imageRegistry.put(IMG_DEC_FIELD_REQUIRED, ImageDescriptor
				.createFromFile(FieldDecorationRegistry.class,
						"images/required_field_cue.png"));//$NON-NLS-1$

		imageRegistry.put(IMG_DEC_FIELD_ERROR_QUICKFIX, ImageDescriptor
				.createFromFile(FieldDecorationRegistry.class,
						"images/errorqf_ovr.png"));//$NON-NLS-1$

		imageRegistry.put(IMG_DEC_FIELD_INFO, ImageDescriptor
				.createFromFile(FieldDecorationRegistry.class,
						"images/info_ovr.png"));//$NON-NLS-1$

		// Define the standard decorations. Some do not have standard
		// descriptions. Use null in these cases.
		getDefault()
				.registerFieldDecoration(
						DEC_CONTENT_PROPOSAL,
						JFaceResources
								.getString("FieldDecorationRegistry.contentAssistMessage"), //$NON-NLS-1$
						IMG_DEC_FIELD_CONTENT_PROPOSAL, imageRegistry);

		getDefault().registerFieldDecoration(
				DEC_ERROR,
				JFaceResources
						.getString("FieldDecorationRegistry.errorMessage"), //$NON-NLS-1$
				IMG_DEC_FIELD_ERROR, imageRegistry);

		getDefault().registerFieldDecoration(
				DEC_ERROR_QUICKFIX,
				JFaceResources
						.getString("FieldDecorationRegistry.errorQuickFixMessage"), //$NON-NLS-1$
				IMG_DEC_FIELD_ERROR_QUICKFIX, imageRegistry);

		getDefault().registerFieldDecoration(DEC_WARNING, null,
				IMG_DEC_FIELD_WARNING, imageRegistry);

		getDefault().registerFieldDecoration(DEC_INFORMATION, null,
				IMG_DEC_FIELD_INFO, imageRegistry);

		getDefault()
				.registerFieldDecoration(
						DEC_REQUIRED,
						JFaceResources
								.getString("FieldDecorationRegistry.requiredFieldMessage"), //$NON-NLS-1$
						IMG_DEC_FIELD_REQUIRED, imageRegistry);

	}

	/*
	 * Data structure that holds onto the decoration image info and description,
	 * and can produce a decorator on request.
	 */
	static class Entry {
		private String description;

		private String imageId;

		private ImageRegistry imageRegistry;

		private Image image;

		private FieldDecoration decoration;

		Entry(String description, String imageId, ImageRegistry registry) {
			this.description = description;
			this.imageId = imageId;
			this.imageRegistry = registry;
		}

		Entry(String description, Image image) {
			this.description = description;
			this.image = image;
		}

		FieldDecoration getDecoration() {
			if (decoration == null) {
				if (image == null) {
					if (imageRegistry == null) {
						imageRegistry = JFaceResources.getImageRegistry();
					}
					image = imageRegistry.get(imageId);
				}
				decoration = new FieldDecoration(image, description);
			}
			// Null out all other fields now that the decoration has an image
			description = null;
			imageId = null;
			imageRegistry = null;
			image = null;

			return decoration;
		}
	}

	/**
	 * Default instance of the registry. Applications may install their own
	 * registry.
	 */
	private static FieldDecorationRegistry defaultInstance;

	/**
	 * Maximum width and height used by decorations in this registry. Clients
	 * may use these values to reserve space in dialogs for decorations or to
	 * adjust layouts so that decorated and non-decorated fields line up.
	 */
	private int maxDecorationWidth = 0;
	private int maxDecorationHeight = 0;

	private HashMap<String, Entry> decorations = new HashMap<>();

	/**
	 * Get the default FieldDecorationRegistry.
	 *
	 * @return the singleton FieldDecorationRegistry that is used to manage
	 *         shared field decorations.
	 */
	public static FieldDecorationRegistry getDefault() {
		if (defaultInstance == null) {
			defaultInstance = new FieldDecorationRegistry();
		}
		return defaultInstance;
	}

	/**
	 * Set the default FieldDecorationRegistry.
	 *
	 * @param defaultRegistry
	 *            the singleton FieldDecorationRegistry that is used to manage
	 *            shared field decorations.
	 */
	public static void setDefault(FieldDecorationRegistry defaultRegistry) {
		defaultInstance = defaultRegistry;
	}

	/**
	 * Construct a FieldDecorationRegistry.
	 */
	public FieldDecorationRegistry() {
		maxDecorationWidth = 0;
		maxDecorationHeight = 0;
	}

	/**
	 * Get the maximum width (in pixels) of any decoration retrieved so far in
	 * the registry. This value changes as decorations are added and retrieved.
	 * This value can be used by clients to reserve space or otherwise compute
	 * margins when aligning non-decorated fields with decorated fields.
	 *
	 * @return the maximum width in pixels of any accessed decoration
	 */
	public int getMaximumDecorationWidth() {
		return maxDecorationWidth;
	}

	/**
	 * Get the maximum height (in pixels) of any decoration retrieved so far in
	 * the registry. This value changes as decorations are added and retrieved.
	 * This value can be used by clients to reserve space or otherwise compute
	 * margins when aligning non-decorated fields with decorated fields.
	 *
	 *
	 * @return the maximum height in pixels of any accessed decoration
	 */
	public int getMaximumDecorationHeight() {
		return maxDecorationHeight;
	}

	/**
	 * Registers a field decoration using the specified id. The lifecyle of the
	 * supplied image should be managed by the client. That is, it will never be
	 * disposed by this registry and the decoration should be removed from the
	 * registry if the image is ever disposed elsewhere.
	 *
	 * @param id
	 *            the String id used to identify and access the decoration.
	 * @param description
	 *            the String description to be used in the decoration, or
	 *            <code>null</code> if the decoration has no description.
	 * @param image
	 *            the image to be used in the decoration
	 */
	public void registerFieldDecoration(String id, String description,
			Image image) {
		decorations.put(id, new Entry(description, image));
		// Recompute the maximums since this might be a replacement
		recomputeMaximums();
	}

	/**
	 * Registers a field decoration using the specified id. An image id of an
	 * image located in the default JFaceResources image registry is supplied.
	 * The image will not be created until the decoration is requested.
	 *
	 * @param id
	 *            the String id used to identify and access the decoration.
	 * @param description
	 *            the String description to be used in the decoration, or
	 *            <code>null</code> if the decoration has no description. *
	 * @param imageId
	 *            the id of the image in the JFaceResources image registry that
	 *            is used for this decorator
	 */
	public void registerFieldDecoration(String id, String description,
			String imageId) {
		decorations.put(id, new Entry(description, imageId, JFaceResources
				.getImageRegistry()));
		// Recompute the maximums as this could be a replacement of a previous
		// image.
		recomputeMaximums();
	}

	/**
	 * Registers a field decoration using the specified id. An image id and an
	 * image registry are supplied. The image will not be created until the
	 * decoration is requested.
	 *
	 * @param id
	 *            the String id used to identify and access the decoration.
	 * @param description
	 *            the String description to be used in the decoration, or
	 *            <code>null</code> if the decoration has no description. *
	 * @param imageId
	 *            the id of the image in the supplied image registry that is
	 *            used for this decorator
	 * @param imageRegistry
	 *            the registry used to obtain the image
	 */
	public void registerFieldDecoration(String id, String description,
			String imageId, ImageRegistry imageRegistry) {
		decorations.put(id, new Entry(description, imageId, imageRegistry));
		// Recompute the maximums since this could be a replacement
		recomputeMaximums();
	}

	/**
	 * Unregisters the field decoration with the specified id. No lifecycle
	 * management is performed on the decoration's image. This message has no effect
	 * if no field decoration with the specified id was previously registered.
	 * <p>
	 * This method need not be called if the registered decoration's image is
	 * managed in an image registry. In that case, leaving the decoration in the
	 * registry will do no harm since the image will remain valid and will be
	 * properly disposed when the application is shut down. This method should be
	 * used in cases where the caller intends to dispose of the image referred to by
	 * the decoration, or otherwise determines that the decoration should no longer
	 * be used.
	 * </p>
	 *
	 * @param id the String id of the decoration to be unregistered.
	 */
	public void unregisterFieldDecoration(String id) {
		decorations.remove(id);
		recomputeMaximums();
	}

	/**
	 * Returns the field decoration registered by the specified id .
	 *
	 * @param id
	 *            the String id used to access the decoration.
	 * @return the FieldDecoration with the specified id, or <code>null</code>
	 *         if there is no decoration with the specified id.
	 */
	public FieldDecoration getFieldDecoration(String id) {
		Object entry = decorations.get(id);
		if (entry == null) {
			return null;
		}
		return ((Entry) entry).getDecoration();

	}

	/*
	 * The maximum decoration width and height must be recomputed. Typically
	 * called in response to adding, removing, or replacing a decoration.
	 */
	private void recomputeMaximums() {
		Iterator<Entry> entries = decorations.values().iterator();

		maxDecorationHeight = 0;
		maxDecorationWidth = 0;
		while (entries.hasNext()) {
			Image image = entries.next().getDecoration().getImage();
			if (image != null) {
				maxDecorationHeight = Math.max(maxDecorationHeight, image.getBounds().height);
				maxDecorationWidth = Math.max(maxDecorationWidth, image.getBounds().width);
			}
		}

	}
}
