/*******************************************************************************
 * Copyright (c) 2005, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Tasktop Technologies - generalized ProgressInfoItem for reuse
 *******************************************************************************/

package org.eclipse.equinox.internal.p2.ui.discovery.util;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.PlatformUI;

/**
 * Based on {@link org.eclipse.ui.internal.progress.ProgressInfoItem}.
 * 
 * @author Steffen Pingel
 */
public abstract class ControlListItem<T> extends Composite {

	static String DARK_COLOR_KEY = "org.eclipse.mylyn.commons.ui.ControlListItem.DARK_COLOR"; //$NON-NLS-1$

	interface IndexListener {

		/**
		 * Select the item previous to the receiver.
		 */
		public void selectPrevious();

		/**
		 * Select the next previous to the receiver.
		 */
		public void selectNext();

		/**
		 * Select the receiver.
		 */
		public void select();

		public void open();

	}

	IndexListener indexListener;

	private int currentIndex;

	private boolean selected;

	private final MouseAdapter mouseListener;

	private boolean isShowing = true;

	private final MouseTrackAdapter mouseTrackListener;

	private boolean hot;

	static {
		// Mac has different Gamma value
		int shift = "carbon".equals(SWT.getPlatform()) ? -25 : -10;//$NON-NLS-1$ 

		Color lightColor = PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);

		// Determine a dark color by shifting the list color
		RGB darkRGB = new RGB(Math.max(0, lightColor.getRed() + shift), Math.max(0, lightColor.getGreen() + shift), Math.max(0, lightColor.getBlue() + shift));
		JFaceResources.getColorRegistry().put(DARK_COLOR_KEY, darkRGB);
	}

	/**
	 * Create a new instance of the receiver with the specified parent, style and info object/
	 * 
	 * @param parent
	 * @param style
	 * @param element
	 */
	public ControlListItem(Composite parent, int style, T element) {
		super(parent, style | SWT.NO_FOCUS);
		Assert.isNotNull(element);
		super.setData(element);
		setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
		mouseListener = doCreateMouseListener();
		mouseTrackListener = doCreateMouseTrackListener();
		registerChild(this);
		//		Control[] children = getChildren();
		//		for (Control child : children) {
		//			registerChild(child);
		//		}
		setHot(false);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getData() {
		return (T) super.getData();
	}

	@Override
	public void setData(Object data) {
		throw new IllegalArgumentException();
	}

	private MouseTrackAdapter doCreateMouseTrackListener() {
		return new MouseTrackAdapter() {
			private int enterCount;

			@Override
			public void mouseEnter(MouseEvent e) {
				enterCount++;
				updateHotState();
			}

			@Override
			public void mouseExit(MouseEvent e) {
				enterCount--;
				getDisplay().asyncExec(new Runnable() {
					public void run() {
						if (!isDisposed()) {
							updateHotState();
						}
					}
				});
			}

			private void updateHotState() {
				if (enterCount == 0) {
					if (isHot()) {
						setHot(false);
					}
				} else {
					if (!isHot()) {
						setHot(true);
					}
				}
			}
		};
	}

	private MouseAdapter doCreateMouseListener() {
		return new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				if (indexListener != null) {
					if (e.count == 2) {
						indexListener.open();
					} else {
						indexListener.select();
					}
				}
			}
		};
	}

	public boolean isHot() {
		return hot;
	}

	public void setHot(boolean hot) {
		this.hot = hot;
	}

	protected void registerChild(Control child) {
		child.addMouseListener(mouseListener);
		child.addMouseTrackListener(mouseTrackListener);

	}

	/**
	 * Refresh the contents of the receiver.
	 */
	protected abstract void refresh();

	/**
	 * Set the color base on the index
	 * 
	 * @param index
	 */
	public void updateColors(int index) {
		currentIndex = index;

		if (selected) {
			setBackground(getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION));
			setForeground(getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
		} else {
			if (index % 2 == 0) {
				setBackground(JFaceResources.getColorRegistry().get(DARK_COLOR_KEY));
			} else {
				setBackground(getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
			}
			setForeground(getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND));
		}
	}

	@Override
	public void setForeground(Color color) {
		super.setForeground(color);
		Control[] children = getChildren();
		for (Control child : children) {
			child.setForeground(color);
		}
	}

	@Override
	public void setBackground(Color color) {
		super.setBackground(color);
		Control[] children = getChildren();
		for (Control child : children) {
			child.setBackground(color);
		}
	}

	/**
	 * Set the selection colors.
	 * 
	 * @param select
	 *            boolean that indicates whether or not to show selection.
	 */
	public void setSelected(boolean select) {
		selected = select;
		updateColors(currentIndex);
	}

	/**
	 * Set the listener for index changes.
	 * 
	 * @param indexListener
	 */
	void setIndexListener(IndexListener indexListener) {
		this.indexListener = indexListener;
	}

	/**
	 * Return whether or not the receiver is selected.
	 * 
	 * @return boolean
	 */
	public boolean isSelected() {
		return selected;
	}

	/**
	 * Set whether or not the receiver is being displayed based on the top and bottom of the currently visible area.
	 * 
	 * @param top
	 * @param bottom
	 */
	void setDisplayed(int top, int bottom) {
		int itemTop = getLocation().y;
		int itemBottom = itemTop + getBounds().height;
		setDisplayed(itemTop <= bottom && itemBottom > top);

	}

	/**
	 * Set whether or not the receiver is being displayed
	 * 
	 * @param displayed
	 */
	private void setDisplayed(boolean displayed) {
		// See if this element has been turned off
		boolean refresh = !isShowing && displayed;
		isShowing = displayed;
		if (refresh) {
			refresh();
		}
	}

}
