/*******************************************************************************
 * Copyright (c) 2012, 2014 Tilera 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:
 *     William R. Swanson (Tilera Corporation)
 *     Xavier Raynaud (Kalray) - Bug 430804
 *******************************************************************************/

package org.eclipse.cdt.visualizer.ui.canvas;

import java.util.ArrayList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

// ---------------------------------------------------------------------------
// GraphicCanvas
// ---------------------------------------------------------------------------

/**
 * Viewer canvas -- base class for canvas that displays a collection
 * of persistent, repositionable graphic objects.
 *
 * Note: painting is done in order objects were added,
 * so objects added last are drawn "on top" of others.
 * Use raise/lower methods to change the object z-ordering, if needed.
 */
public class GraphicCanvas extends BufferedCanvas {
	// --- members ---

	/** Viewer elements. */
	protected ArrayList<IGraphicObject> m_objects = null;

	// --- constructors/destructors ---

	/** Constructor. */
	public GraphicCanvas(Composite parent) {
		super(parent);
		m_objects = new ArrayList<>();
		Listener mouseListener = new Listener() {
			@Override
			public void handleEvent(Event event) {
				switch (event.type) {
				case SWT.MouseEnter:
				case SWT.MouseMove:
					IGraphicObject obj = getGraphicObject(event.x, event.y);
					if (obj instanceof ITooltipProvider) {
						String tooltip = ((ITooltipProvider) obj).getTooltip(event.x, event.y);
						setToolTipText(tooltip);
					}
					break;
				}
			}
		};
		addListener(SWT.MouseMove, mouseListener);
		addListener(SWT.MouseEnter, mouseListener);
	}

	/** Dispose method. */
	@Override
	public void dispose() {
		if (m_objects != null) {
			m_objects.clear();
			m_objects = null;
		}
		super.dispose();
	}

	// --- object management methods ---

	/** Removes all elements */
	public void clear() {
		m_objects.clear();
	}

	/** Adds an element */
	public IGraphicObject add(IGraphicObject element) {
		if (!m_objects.contains(element)) {
			m_objects.add(element);
		}
		return element;
	}

	/** Removes an element */
	public void remove(IGraphicObject element) {
		m_objects.remove(element);
	}

	/** Raises an element to top of repaint z-ordering */
	public void raiseToFront(IGraphicObject element) {
		if (m_objects.contains(element)) {
			m_objects.remove(element);
			m_objects.add(element);
		}
	}

	/** Lowers an element to bottom of repaint z-ordering */
	public void lowerToBack(IGraphicObject element) {
		if (m_objects.contains(element)) {
			m_objects.remove(element);
			m_objects.add(0, element);
		}
	}

	// --- painting methods ---

	/** Paints elements on canvas. */
	@Override
	public void paintCanvas(GC gc) {
		// paint background first
		clearCanvas(gc);

		// we paint object list from start to end,
		// so end of the list is "top" in z-ordering

		// allow objects to draw themselves
		for (IGraphicObject gobj : m_objects) {
			gobj.paint(gc, false);
		}

		// allow objects to paint any "decorations" on top of other stuff
		for (IGraphicObject gobj : m_objects) {
			if (gobj.hasDecorations())
				gobj.paint(gc, true);
		}
	}

	// --- point-to-object accessors ---

	/** Returns first graphic object found under specified point */
	public IGraphicObject getGraphicObject(int x, int y) {
		return getGraphicObject(null, x, y);
	}

	/** Returns first graphic object found under specified point.
	 *  If type argument is non-null, returns first object assignable to specified type.
	 */
	public IGraphicObject getGraphicObject(Class<?> type, int x, int y) {
		IGraphicObject result = null;

		// note: have to search list in reverse order we draw it,
		// so we hit items "on top" in the z-ordering first
		int count = (m_objects == null) ? 0 : m_objects.size();
		for (int i = count - 1; i >= 0; i--) {
			IGraphicObject gobj = m_objects.get(i);
			if (gobj.contains(x, y)) {
				if (type != null) {
					Class<?> objType = gobj.getClass();
					if (!type.isAssignableFrom(objType))
						continue;
				}
				result = gobj;
				break;
			}
		}

		return result;
	}

	// --- model data accessors ---

	/** Returns graphic object (if any) that has specified data value. */
	public IGraphicObject getGraphicObjectFor(Object value) {
		IGraphicObject result = null;
		for (IGraphicObject gobj : m_objects) {
			if (gobj.getData() == value) {
				result = gobj;
				break;
			}
		}
		return result;
	}

	/** Returns data value (if any) for the specified graphic element. */
	public Object getDataFor(IGraphicObject IGraphicObject) {
		return (IGraphicObject == null) ? null : IGraphicObject.getData();
	}
}
