/*
 * Copyright (c) 2004 Christopher Lenz and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     Christopher Lenz - initial implementation
 * 
 * $Id: ProblemsLabelDecorator.java,v 1.3 2006-10-21 23:13:54 pombredanne Exp $
 */

package net.sourceforge.phpeclipse.ui.views.outline;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import net.sourceforge.phpeclipse.core.model.ISourceReference;
import net.sourceforge.phpeclipse.ui.WebUI;
import net.sourceforge.phpeclipse.ui.views.util.ImageDescriptorRegistry;
import net.sourceforge.phpeclipse.ui.views.util.ImageImageDescriptor;
import net.sourceforge.phpeclipse.ui.views.util.OverlayImageDescriptor;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;

/**
 * Label decorator for the outline page that adds error and warning overlay
 * icons to elements in the outline. The information is retrieved from the
 * annotation model corresponding to the input of the associated text editor.
 */
public class ProblemsLabelDecorator extends LabelProvider implements
		ILabelDecorator {

	// Constants ---------------------------------------------------------------

	private static final String ANNOTATION_TYPE_ERROR = "org.eclipse.ui.workbench.texteditor.error"; //$NON-NLS-1$

	private static final String ANNOTATION_TYPE_WARNING = "org.eclipse.ui.workbench.texteditor.warning"; //$NON-NLS-1$

	// Instance Variables ------------------------------------------------------

	/** The associated text editor if the decorator is used in the outline. */
	private ITextEditor editor;

	/** Registry of icons and overlay icons. */
	private ImageDescriptorRegistry registry = new ImageDescriptorRegistry();

	// Constructors ------------------------------------------------------------

	/**
	 * Constructor.
	 * 
	 * @param editor
	 *            the associated text editor
	 */
	public ProblemsLabelDecorator(ITextEditor editor) {
		this.editor = editor;
	}

	// ILabelDecorator Implementation ------------------------------------------

	/*
	 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
	 */
	public void dispose() {
		super.dispose();
		registry.dispose();
	}

	/*
	 * @see ILabelDecorator#decorateImage(Image, Object)
	 */
	public Image decorateImage(Image image, Object element) {
		Annotation annotations[] = getAssociatedAnnotations((ISourceReference) element);
		ImageDescriptor overlay = null;
		for (int i = 0; i < annotations.length; i++) {
			if (isError(annotations[i])) {
				overlay = WebUI.getDefault().getImageRegistry().getDescriptor(
						WebUI.ICON_OVERLAY_ERROR);
			} else if (isWarning(annotations[i])) {
				overlay = WebUI.getDefault().getImageRegistry().getDescriptor(
						WebUI.ICON_OVERLAY_WARNING);
			}
		}
		if (overlay != null) {
			ImageDescriptor base = new ImageImageDescriptor(image);
			return registry.get(new OverlayImageDescriptor(base,
					new ImageDescriptor[][] { null, null, { overlay }, null },
					null));
		} else {
		}
		return image;
	}

	/*
	 * @see ILabelDecorator#decorateText(String, Object)
	 */
	public String decorateText(String text, Object element) {
		return text;
	}

	// Private Methods ---------------------------------------------------------

	/**
	 * Returns all annotations associated with the given model element.
	 * 
	 * @param element
	 *            the model element for which annotations should be collected
	 * @return an array containing all annotations for the given element, or an
	 *         empty array if no annotations are found
	 */
	private Annotation[] getAssociatedAnnotations(ISourceReference element) {
		List retVal = new ArrayList();
		if (editor != null) {
			IEditorInput input = editor.getEditorInput();
			IAnnotationModel model = editor.getDocumentProvider()
					.getAnnotationModel(input);
			if (model != null) { // bug #1120670
				for (Iterator i = model.getAnnotationIterator(); i.hasNext();) {
					Annotation annotation = (Annotation) i.next();
					Position pos = model.getPosition(annotation);
					if (pos != null && isInside(pos.getOffset(), element)) {
						retVal.add(annotation);
					}
				}
			}
		}
		return (Annotation[]) retVal.toArray(new Annotation[retVal.size()]);
	}

	/**
	 * Determines whether the given annotation is an error.
	 * 
	 * @param annotation
	 *            the annotation to check
	 * @return <tt>true</tt> if the annotation is to be displayed as an error,
	 *         <tt>false</tt> otherwise
	 */
	private boolean isError(Annotation annotation) {
		return ANNOTATION_TYPE_ERROR.equals(annotation.getType());
	}

	/**
	 * Determines whether the given annotation is a warning.
	 * 
	 * @param annotation
	 *            the annotation to check
	 * @return <tt>true</tt> if the annotation is to be displayed as a
	 *         warning, <tt>false</tt> otherwise
	 */
	private boolean isWarning(Annotation annotation) {
		return ANNOTATION_TYPE_WARNING.equals(annotation.getType());
	}

	/**
	 * Tests if the given position is inside the source region of a model
	 * element.
	 * 
	 * @param pos
	 *            the position to be tested
	 * @param element
	 *            the source element
	 * @return boolean <tt>true</tt> if position is located inside the
	 *         element, otherwise <tt>false</tt>
	 */
	private boolean isInside(int pos, ISourceReference element) {
		IRegion region = element.getSourceRegion();
		if (region != null) {
			int offset = region.getOffset();
			return ((offset <= pos) && (offset + region.getLength() > pos));
		}
		return false;
	}

}