/*******************************************************************************
 * Copyright (c) 2008, 2011 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
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.javaeditor.breadcrumb;

import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;


/**
 * The label and icon part of the breadcrumb item.
 *
 * @since 3.4
 */
class BreadcrumbItemDetails {

	private final Label fElementImage;
	private final Label fElementText;
	private final Composite fDetailComposite;
	private final BreadcrumbItem fParent;
	private final Composite fTextComposite;
	private final Composite fImageComposite;

	private boolean fTextVisible;
	private boolean fSelected;
	private boolean fHasFocus;


	public BreadcrumbItemDetails(BreadcrumbItem parent, Composite parentContainer) {
		fParent= parent;
		fTextVisible= true;

		fDetailComposite= new Composite(parentContainer, SWT.NONE);
		fDetailComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		GridLayout layout= new GridLayout(2, false);
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		layout.horizontalSpacing= 0;
		fDetailComposite.setLayout(layout);
		addElementListener(fDetailComposite);

		fImageComposite= new Composite(fDetailComposite, SWT.NONE);
		fImageComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		layout= new GridLayout(1, false);
		layout.marginHeight= 1;
		layout.marginWidth= 2;
		fImageComposite.setLayout(layout);
		fImageComposite.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				if (fHasFocus && !isTextVisible()) {
					e.gc.drawFocus(e.x, e.y, e.width, e.height);
				}
			}
		});
		installFocusComposite(fImageComposite);
		addElementListener(fImageComposite);

		fElementImage= new Label(fImageComposite, SWT.NONE);
		GridData layoutData= new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
		fElementImage.setLayoutData(layoutData);
		addElementListener(fElementImage);

		fTextComposite= new Composite(fDetailComposite, SWT.NONE);
		fTextComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		layout= new GridLayout(1, false);
		layout.marginHeight= 2;
		layout.marginWidth= 2;
		fTextComposite.setLayout(layout);
		addElementListener(fTextComposite);
		fTextComposite.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				if (fHasFocus && isTextVisible()) {
					e.gc.drawFocus(e.x, e.y, e.width, e.height);
				}
			}
		});
		installFocusComposite(fTextComposite);

		fElementText= new Label(fTextComposite, SWT.NONE);
		layoutData= new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
		fElementText.setLayoutData(layoutData);
		addElementListener(fElementText);

		fTextComposite.getAccessible().addAccessibleListener(new AccessibleAdapter() {
			@Override
			public void getName(AccessibleEvent e) {
				e.result= fElementText.getText();
			}
		});
		fImageComposite.getAccessible().addAccessibleListener(new AccessibleAdapter() {
			@Override
			public void getName(AccessibleEvent e) {
				e.result= fElementText.getText();
			}
		});

		fDetailComposite.setTabList(new Control[] { fTextComposite });
	}

	/**
	 * Returns whether this element has the keyboard focus.
	 *
	 * @return true if this element has the keyboard focus.
	 */
	public boolean hasFocus() {
		return fHasFocus;
	}

	/**
	 * Sets the tool tip to the given text.
	 *
	 * @param text the tool tip
	 */
	public void setToolTip(String text) {
		if (isTextVisible()) {
			fElementText.getParent().setToolTipText(text);
			fElementText.setToolTipText(text);

			fElementImage.setToolTipText(text);
		} else {
			fElementText.getParent().setToolTipText(null);
			fElementText.setToolTipText(null);

			fElementImage.setToolTipText(text);
		}
	}

	/**
	 * Sets the image to the given image.
	 *
	 * @param image the image to use
	 */
	public void setImage(Image image) {
		if (image != fElementImage.getImage()) {
			fElementImage.setImage(image);
		}
	}

	/**
	 * Sets the text to the given text.
	 *
	 * @param text the text to use
	 */
	public void setText(String text) {
		if (text == null) {
			text= ""; //$NON-NLS-1$
		}
		if (!text.equals(fElementText.getText())) {
			fElementText.setText(text);
		}
	}

	/**
	 * Returns the width of this element.
	 *
	 * @return current width of this element
	 */
	public int getWidth() {
		int result= 2;

		if (fElementImage.getImage() != null)
			result+= fElementImage.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;

		if (fTextVisible && fElementText.getText().length() > 0)
			result+= fElementText.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;

		return result;
	}

	public void setTextVisible(boolean enabled) {
		if (fTextVisible == enabled)
			return;

		fTextVisible= enabled;

		GridData data= (GridData) fTextComposite.getLayoutData();
		data.exclude= !enabled;
		fTextComposite.setVisible(enabled);

		if (fTextVisible) {
			fDetailComposite.setTabList(new Control[] { fTextComposite });
		} else {
			fDetailComposite.setTabList(new Control[] { fImageComposite });
		}

		if (fHasFocus) {
			if (isTextVisible()) {
				fTextComposite.setFocus();
			} else {
				fImageComposite.setFocus();
			}
		}
		updateSelection();
	}

	/**
	 * Tells whether this item shows a text or only an image.
	 *
	 * @return <code>true</code> if it shows a text and an image, false if it only shows the image
	 */
	public boolean isTextVisible() {
		return fTextVisible;
	}

	/**
	 * Sets whether details should be shown.
	 *
	 * @param visible <code>true</code> if details should be shown
	 */
	public void setVisible(boolean visible) {
		fDetailComposite.setVisible(visible);

		GridData data= (GridData) fDetailComposite.getLayoutData();
		data.exclude= !visible;
	}

	public void setSelected(boolean selected) {
		if (selected == fSelected)
			return;

		fSelected= selected;
		if (!fSelected)
			fHasFocus= false;

		updateSelection();
	}

	public void setFocus(boolean enabled) {
		if (enabled == fHasFocus)
			return;

		fHasFocus= enabled;
		if (fHasFocus) {
			if (isTextVisible()) {
				fTextComposite.setFocus();
			} else {
				fImageComposite.setFocus();
			}
		}
		updateSelection();
	}

	private void updateSelection() {
		Color background;
		Color foreground;

		if (fSelected && fHasFocus) {
			background= Display.getDefault().getSystemColor(SWT.COLOR_LIST_SELECTION);
			foreground= Display.getDefault().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
		} else {
			foreground= null;
			background= null;
		}

		if (isTextVisible()) {
			fTextComposite.setBackground(background);
			fElementText.setBackground(background);
			fElementText.setForeground(foreground);

			fImageComposite.setBackground(null);
			fElementImage.setBackground(null);
		} else {
			fImageComposite.setBackground(background);
			fElementImage.setBackground(background);

			fTextComposite.setBackground(null);
			fElementText.setBackground(null);
			fElementText.setForeground(null);
		}

		fTextComposite.redraw();
		fImageComposite.redraw();
	}

	/**
	 * Install focus and key listeners to the given composite.
	 *
	 * @param composite the composite which may get focus
	 */
	private void installFocusComposite(Composite composite) {
		composite.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent e) {
				if (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
					int index= fParent.getViewer().getIndexOfItem(fParent);
					if (e.detail == SWT.TRAVERSE_TAB_NEXT) {
						index++;
					} else {
						index--;
					}

					if (index > 0 && index < fParent.getViewer().getItemCount()) {
						fParent.getViewer().selectItem(fParent.getViewer().getItem(index));
					}

					e.doit= true;
				}
			}
		});
		composite.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				BreadcrumbViewer viewer= fParent.getViewer();

				switch (e.keyCode) {
					case SWT.ARROW_LEFT:
						if (fSelected) {
							viewer.doTraverse(false);
							e.doit= false;
						} else {
							viewer.selectItem(fParent);
						}
						break;
					case SWT.ARROW_RIGHT:
						if (fSelected) {
							viewer.doTraverse(true);
							e.doit= false;
						} else {
							viewer.selectItem(fParent);
						}
						break;
					case SWT.ARROW_DOWN:
						if (!fSelected) {
							viewer.selectItem(fParent);
						}
						openDropDown();
						e.doit= false;
						break;
					case SWT.KEYPAD_ADD:
						if (!fSelected) {
							viewer.selectItem(fParent);
						}
						openDropDown();
						e.doit= false;
						break;
					case SWT.CR:
						if (!fSelected) {
							viewer.selectItem(fParent);
						}
						viewer.fireOpen();
						break;
					default:
						if (e.character == ' ') {
							if (!fSelected) {
								viewer.selectItem(fParent);
							}
							openDropDown();
							e.doit= false;
						}
						break;
				}
			}

			private void openDropDown() {
				BreadcrumbViewer viewer= fParent.getViewer();

				int index= viewer.getIndexOfItem(fParent);
				BreadcrumbItem parent= fParent.getViewer().getItem(index - 1);

				Shell shell= parent.getDropDownShell();
				if (shell == null) {
					parent.openDropDownMenu();
					shell= parent.getDropDownShell();
				}
				shell.setFocus();
			}

			public void keyReleased(KeyEvent e) {
			}
		});

		composite.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				if (!fHasFocus) {
					fHasFocus= true;
					updateSelection();
				}
			}

			public void focusLost(FocusEvent e) {
				if (fHasFocus) {
					fHasFocus= false;
					updateSelection();
				}
			}
		});
	}

	/**
	 * Add mouse listeners to the given control.
	 *
	 * @param control the control to which may be clicked
	 */
	private void addElementListener(Control control) {
		control.addMouseListener(new MouseListener() {
			public void mouseDoubleClick(MouseEvent e) {
			}

			public void mouseDown(MouseEvent e) {
				BreadcrumbViewer viewer= fParent.getViewer();

				int parentIndex= viewer.getIndexOfItem(fParent) - 1;
				Shell shell= null;
				if (parentIndex >= 0) {//sanity check, must always hold
					BreadcrumbItem dropDownItem= viewer.getItem(parentIndex);
					shell= dropDownItem.getDropDownShell();
				}

				viewer.selectItem(fParent);
				if (shell == null && e.button == 1 && e.stateMask == 0) {
					fParent.getViewer().fireDoubleClick();
				}
			}

			public void mouseUp(MouseEvent e) {
			}
		});
		control.addMenuDetectListener(new MenuDetectListener() {
			public void menuDetected(MenuDetectEvent e) {
				BreadcrumbViewer viewer= fParent.getViewer();
				viewer.selectItem(fParent);
				fParent.getViewer().fireMenuDetect(e);
			}
		});
	}
}
