/*******************************************************************************
 * 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
 *******************************************************************************/

package org.eclipse.jface.viewers;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

/**
 * OwnerDrawLabelProvider is an abstract implementation of a label provider that
 * handles custom draw.
 *
 * <p>
 * <b>This class is intended to be subclassed by implementors.</b>
 * </p>
 *
 * @since 3.3
 *
 */
public abstract class OwnerDrawLabelProvider extends CellLabelProvider {

	static class OwnerDrawListener implements Listener {
		Set<ViewerColumn> enabledColumns = new HashSet<>();
		int enabledGlobally = 0;
		private ColumnViewer viewer;

		OwnerDrawListener(ColumnViewer viewer) {
			this.viewer = viewer;
		}

		@Override
		public void handleEvent(Event event) {
			ViewerColumn column = viewer.getViewerColumn(event.index);
			if (column != null && (enabledGlobally > 0 || enabledColumns.contains(column))) {
				CellLabelProvider provider = column.getLabelProvider();
				if (provider instanceof OwnerDrawLabelProvider) {
					Object element = event.item.getData();
					OwnerDrawLabelProvider ownerDrawProvider = (OwnerDrawLabelProvider) provider;
					switch (event.type) {
					case SWT.MeasureItem:
						ownerDrawProvider.measure(event, element);
						break;
					case SWT.PaintItem:
						ownerDrawProvider.paint(event, element);
						break;
					case SWT.EraseItem:
						ownerDrawProvider.erase(event, element);
						break;
					}
				}
			}
		}
	}

	private static final String OWNER_DRAW_LABEL_PROVIDER_LISTENER = "owner_draw_label_provider_listener"; //$NON-NLS-1$

	/**
	 * Set up the owner draw callbacks for the viewer.
	 *
	 * @param viewer
	 *            the viewer the owner draw is set up
	 *
	 * @deprecated Since 3.4, the default implementation of
	 *             {@link CellLabelProvider#initialize(ColumnViewer, ViewerColumn)}
	 *             in this class will set up the necessary owner draw callbacks
	 *             automatically. Calls to this method can be removed.
	 */
	@Deprecated
	public static void setUpOwnerDraw(final ColumnViewer viewer) {
		getOrCreateOwnerDrawListener(viewer).enabledGlobally++;
	}

	private static OwnerDrawListener getOrCreateOwnerDrawListener(
			final ColumnViewer viewer) {
		Control control = viewer.getControl();
		OwnerDrawListener listener = (OwnerDrawListener) control
				.getData(OWNER_DRAW_LABEL_PROVIDER_LISTENER);
		if (listener == null) {
			listener = new OwnerDrawListener(viewer);
			control.setData(OWNER_DRAW_LABEL_PROVIDER_LISTENER, listener);
			control.addListener(SWT.MeasureItem, listener);
			control.addListener(SWT.EraseItem, listener);
			control.addListener(SWT.PaintItem, listener);
		}
		return listener;
	}

	/**
	 * Create a new instance of the receiver based on a column viewer.
	 *
	 */
	public OwnerDrawLabelProvider() {

	}

	@Override
	public void dispose(ColumnViewer viewer, ViewerColumn column) {
		if (!viewer.getControl().isDisposed()) {
			setOwnerDrawEnabled(viewer, column, false);
		}
		super.dispose(viewer, column);
	}

	/**
	 * This implementation of
	 * {@link CellLabelProvider#initialize(ColumnViewer, ViewerColumn)}
	 * delegates to {@link #initialize(ColumnViewer, ViewerColumn, boolean)}
	 * with a value of <code>true</code> for <code>enableOwnerDraw</code>.
	 * Subclasses may override this method but should either call the super
	 * implementation or, alternatively,
	 * {@link #initialize(ColumnViewer, ViewerColumn, boolean)}.
	 */
	@Override
	protected void initialize(ColumnViewer viewer, ViewerColumn column) {
		this.initialize(viewer, column, true);
	}

	/**
	 * May be called from subclasses that override
	 * {@link #initialize(ColumnViewer, ViewerColumn)} but want to customize
	 * whether owner draw will be enabled. This method calls
	 * <code>super.initialize(ColumnViewer, ViewerColumn)</code>, and then
	 * enables or disables owner draw by calling
	 * {@link #setOwnerDrawEnabled(ColumnViewer, ViewerColumn, boolean)}.
	 *
	 * @param viewer
	 *            the viewer
	 * @param column
	 *            the column, or <code>null</code> if a column is not
	 *            available.
	 * @param enableOwnerDraw
	 *            <code>true</code> if owner draw should be enabled for the
	 *            given viewer and column, <code>false</code> otherwise.
	 *
	 * @since 3.4
	 */
	final protected void initialize(ColumnViewer viewer, ViewerColumn column,
			boolean enableOwnerDraw) {
		super.initialize(viewer, column);
		setOwnerDrawEnabled(viewer, column, enableOwnerDraw);
	}

	@Override
	public void update(ViewerCell cell) {
		// Force a redraw
		Rectangle cellBounds = cell.getBounds();
		cell.getControl().redraw(cellBounds.x, cellBounds.y, cellBounds.width,
				cellBounds.height, true);

	}

	/**
	 * Handle the erase event. The default implementation colors the background
	 * of selected areas with {@link SWT#COLOR_LIST_SELECTION} and foregrounds
	 * with {@link SWT#COLOR_LIST_SELECTION_TEXT}. Note that this
	 * implementation causes non-native behavior on some platforms. Subclasses
	 * should override this method and <b>not</b> call the super
	 * implementation.
	 *
	 * @param event
	 *            the erase event
	 * @param element
	 *            the model object
	 * @see SWT#EraseItem
	 * @see SWT#COLOR_LIST_SELECTION
	 * @see SWT#COLOR_LIST_SELECTION_TEXT
	 */
	protected void erase(Event event, Object element) {

		Rectangle bounds = event.getBounds();
		if ((event.detail & SWT.SELECTED) != 0) {

			Color oldForeground = event.gc.getForeground();
			Color oldBackground = event.gc.getBackground();

			event.gc.setBackground(event.item.getDisplay().getSystemColor(
					SWT.COLOR_LIST_SELECTION));
			event.gc.setForeground(event.item.getDisplay().getSystemColor(
					SWT.COLOR_LIST_SELECTION_TEXT));
			event.gc.fillRectangle(bounds);
			/* restore the old GC colors */
			event.gc.setForeground(oldForeground);
			event.gc.setBackground(oldBackground);
			/* ensure that default selection is not drawn */
			event.detail &= ~SWT.SELECTED;

		}

	}

	/**
	 * Handle the measure event.
	 *
	 * @param event
	 *            the measure event
	 * @param element
	 *            the model element
	 * @see SWT#MeasureItem
	 */
	protected abstract void measure(Event event, Object element);

	/**
	 * Handle the paint event.
	 *
	 * @param event
	 *            the paint event
	 * @param element
	 *            the model element
	 * @see SWT#PaintItem
	 */
	protected abstract void paint(Event event, Object element);

	/**
	 * Enables or disables owner draw for the given viewer and column. This
	 * method will attach or remove a listener to the underlying control as
	 * necessary. This method is called from
	 * {@link #initialize(ColumnViewer, ViewerColumn)} and
	 * {@link #dispose(ColumnViewer, ViewerColumn)} but may be called from
	 * subclasses to enable or disable owner draw dynamically.
	 *
	 * @param viewer
	 *            the viewer
	 * @param column
	 *            the column, or <code>null</code> if a column is not
	 *            available
	 * @param enabled
	 *            <code>true</code> if owner draw should be enabled,
	 *            <code>false</code> otherwise
	 *
	 * @since 3.4
	 */
	protected void setOwnerDrawEnabled(ColumnViewer viewer,
			ViewerColumn column, boolean enabled) {
		if (enabled) {
			OwnerDrawListener listener = getOrCreateOwnerDrawListener(viewer);
			if (column == null) {
				listener.enabledGlobally++;
			} else {
				listener.enabledColumns.add(column);
			}
		} else {
			OwnerDrawListener listener = (OwnerDrawListener) viewer
					.getControl().getData(OWNER_DRAW_LABEL_PROVIDER_LISTENER);
			if (listener != null) {
				if (column == null) {
					listener.enabledGlobally--;
				} else {
					listener.enabledColumns.remove(column);
				}
				if (listener.enabledColumns.isEmpty()
						&& listener.enabledGlobally <= 0) {
					viewer.getControl().removeListener(SWT.MeasureItem,
							listener);
					viewer.getControl().removeListener(SWT.EraseItem, listener);
					viewer.getControl().removeListener(SWT.PaintItem, listener);
					viewer.getControl().setData(
							OWNER_DRAW_LABEL_PROVIDER_LISTENER, null);
				}
			}
		}
	}

}
