/*******************************************************************************
 * Copyright (c) 2004, 2005 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.wst.common.snippets.internal.util;



import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellEditorListener;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.PlatformUI;

/**
 * TAKEN FROM: TableViewerImpl 1.1.2.
 *  - added table field - implemented handleDownArrow and handleUpArrow -
 * added row-wrapping tab/shift-tab traversal
 * 
 * Internal table viewer implementation.
 */
/* package */
abstract class TableViewerImpl implements TraverseListener {


	boolean fIsActivating = false;
	private boolean fSingleClickCellSelect = false;
	private CellEditor fCellEditor;
	private CellEditor[] fCellEditors;
	private ICellModifier fCellModifier;
	private String[] fColumnProperties;
	private Item fTableItem;
	private int fColumnNumber;
	private ICellEditorListener fCellEditorListener;

	protected Table fTable = null;

	TableViewerImpl(Table table) {
		fTable = table;
		initCellEditorListener();
	}

	/**
	 * Activate a cell editor for the given column.
	 */
	private void activateCellEditor() {
		if (fCellEditors != null) {
			if (fCellEditors[fColumnNumber] != null && fCellModifier != null) {
				Object element = fTableItem.getData();
				String property = fColumnProperties[fColumnNumber];
				if (fCellModifier.canModify(element, property)) {
					fCellEditor = fCellEditors[fColumnNumber];
					// table.showSelection();
					fCellEditor.addListener(fCellEditorListener);
					Object value = fCellModifier.getValue(element, property);
					fCellEditor.setValue(value);
					// Tricky flow of control here:
					// activate() can trigger callback to cellEditorListener
					// which will clear cellEditor
					// so must get control first, but must still call
					// activate() even if there is no control.
					Control control = fCellEditor.getControl();
					fCellEditor.activate();
					if (control != null) {
						setLayoutData(fCellEditor.getLayoutData());
						setEditor(control, fTableItem, fColumnNumber);
						fCellEditor.setFocus();
					}
				}
			}
		}
	}

	/**
	 * Activate a cell editor for the given mouse position.
	 */
	private void activateCellEditor(MouseEvent event) {
		if (fTableItem == null || fTableItem.isDisposed()) {
			// item no longer exists
			return;
		}
		int columns = getColumnCount();
		int columnToEdit = -1;
		Rectangle bounds = null;
		for (int i = 0; i < columns; i++) {
			bounds = getBounds(fTableItem, i);
			if (bounds.contains(event.x, event.y)) {
				columnToEdit = i;
				break;
			}
		}
		if (columnToEdit == -1 || bounds == null)
			return;

		fColumnNumber = columnToEdit;
		activateCellEditor();
	}

	/**
	 * Activate the first cell editor.
	 */
	public void activateFirstCellEditor() {
		if (fCellEditors != null) {
			int columnNumber = -1;
			for (int i = 0; i < fCellEditors.length; i++) {
				if (fCellEditors[i] != null && getCellModifier().canModify(fTableItem.getData(), (String) getColumnProperties()[i])) {
					columnNumber = i;
					break;
				}
			}
			if (columnNumber > -1 && fCellModifier != null) {
				fColumnNumber = columnNumber;
				activateCellEditor();
			}
		}
	}

	/**
	 * Deactivates the currently active cell editor.
	 */
	public void applyEditorValue() {
		CellEditor c = this.fCellEditor;
		if (c != null) {
			// null out cell editor before calling save
			// in case save results in applyEditorValue being re-entered
			// see 1GAHI8Z: ITPUI:ALL - How to code event notification when
			// using cell editor ?
			this.fCellEditor = null;
			Item t = this.fTableItem;
			// don't null out table item -- same item is still selected
			if (t != null && !t.isDisposed()) {
				saveEditorValue(c, t);
			}
			setEditor(null, null, 0);
			c.removeListener(fCellEditorListener);
			c.deactivate();
		}
	}

	/**
	 * Cancels the active cell editor, without saving the value back to the
	 * domain model.
	 */
	public void cancelEditing() {
		if (fCellEditor != null) {
			setEditor(null, null, 0);
			fCellEditor.removeListener(fCellEditorListener);
			fCellEditor.deactivate();
			fCellEditor = null;
		}
	}

	/**
	 * Start editing the given element.
	 */
	public void editElement(Object element, int column) {
		if (fCellEditor != null)
			applyEditorValue();

		setSelection(new StructuredSelection(element), true);
		Item[] selection = getSelection();
		if (selection.length != 1)
			return;

		fTableItem = selection[0];

		// Make sure selection is visible
		showSelection();
		fColumnNumber = column;
		activateCellEditor();

	}

	abstract Rectangle getBounds(Item item, int columnNumber);

	public CellEditor[] getCellEditors() {
		return fCellEditors;
	}

	public ICellModifier getCellModifier() {
		return fCellModifier;
	}

	abstract int getColumnCount();

	public Object[] getColumnProperties() {
		return fColumnProperties;
	}

	abstract Item[] getSelection();

	/**
	 * Handles the double click event.
	 */
	public void handleMouseDoubleClick(MouseEvent event) {
		// The last mouse down was a double click. Cancel
		// the cell editor activation.
		// isActivating = false;
	}

	/**
	 * Handles the mouse down event. Activates the cell editor if it is not a
	 * double click.
	 * 
	 * This implementation must: i) activate the cell editor when clicking
	 * over the item's text or over the item's image. ii) activate it only if
	 * the item is already selected. iii) do NOT activate it on a double click
	 * (whether the item is selected or not).
	 */
	public void handleMouseDown(MouseEvent event) {
		if (event.button != 1)
			return;

		boolean wasActivated = isCellEditorActive();
		if (wasActivated)
			applyEditorValue();

		Item[] items = getSelection();
		// Do not edit if more than one row is selected.
		if (items.length != 1) {
			fTableItem = null;
			return;
		}

		if (fTableItem != items[0]) {
			// This mouse down was a selection. Keep the selection and return;
			fTableItem = items[0];
			if (!fSingleClickCellSelect) // only return if you don't want a
											// single click to active the cell
											// editor
				return;
		}

		// It may be a double click. If so, the activation was started by the
		// first click.
		if (fIsActivating || wasActivated)
			return;

		fIsActivating = true;
		// Post the activation. So it may be canceled if it was a double
		// click.
		postActivation(event);
	}

	private void handleRightArrow(TraverseEvent e) {
	}

	private void handleDownArrow(TraverseEvent e) {
		int row = fTable.indexOf((TableItem) fTableItem);
		if (fCellEditors != null && fCellModifier != null && row < fTable.getItemCount() - 1) {
			String property = null;

			TableItem next = fTable.getItem(row + 1);
			Object element = next.getData();
			if (fCellEditors[fColumnNumber] != null) {
				property = fColumnProperties[fColumnNumber];
				if (fCellModifier.canModify(element, property)) {
					applyEditorValue();

					fTableItem = next;
					setSelection(new StructuredSelection(next.getData()), true);
					fTable.select(row + 1);
					showSelection();
					e.doit = false;
				}
			}
			if (e.doit == false)
				startActivationThread();
		}
	}

	private void handleLeftArrow(TraverseEvent e) {
	}

	private void handleUpArrow(TraverseEvent e) {
		int row = fTable.indexOf((TableItem) fTableItem);
		if (fCellEditors != null && fCellModifier != null && row > 0) {
			String property = null;

			TableItem previous = fTable.getItem(row - 1);
			Object element = previous.getData();
			if (fCellEditors[fColumnNumber] != null) {
				property = fColumnProperties[fColumnNumber];
				if (fCellModifier.canModify(element, property)) {
					applyEditorValue();

					fTableItem = previous;
					setSelection(new StructuredSelection(previous.getData()), true);
					fTable.select(row - 1);
					showSelection();
					e.doit = false;
				}
			}
			if (e.doit == false)
				startActivationThread();
		}
	}

	private void handleTabNext(TraverseEvent e) {
		if (fCellEditors != null && fCellModifier != null && fCellEditors.length > (fColumnNumber + 1)) {
			int start = fColumnNumber + 1;
			// CellEditor ce = null;
			String property = null;

			Object element = fTableItem.getData();
			for (int i = start; i <= fCellEditors.length; i++) {
				if (fCellEditors[i] != null) {
					property = fColumnProperties[i];
					if (fCellModifier.canModify(element, property)) {
						fColumnNumber = i;
						e.doit = false;
						break;
					}
				}
			}
			if (e.doit == false)
				startActivationThread();
		}
		else if (fCellEditors != null && fCellModifier != null && fCellEditors.length == (fColumnNumber + 1)) {
			fColumnNumber = 0;
			handleDownArrow(e);
		}
	}

	private void handleTabPrevious(TraverseEvent e) {
		if (fCellEditors != null && fCellModifier != null && fColumnNumber > 0) {
			int start = (fCellEditors.length >= fColumnNumber - 1) ? fColumnNumber - 1 : fCellEditors.length - 1;
			String property = null;

			Object element = fTableItem.getData();
			for (int i = start; i >= 0; i--) {
				if (fCellEditors[i] != null) {
					property = fColumnProperties[i];
					if (fCellModifier.canModify(element, property)) {
						fColumnNumber = i;
						e.doit = false;
						break;
					}
				}
			}
			if (e.doit == false)
				startActivationThread();
		}
		else if (fCellEditors != null && fCellModifier != null && fColumnNumber == 0) {
			fColumnNumber = getColumnCount() - 1;
			handleUpArrow(e);
		}
	}

	private void initCellEditorListener() {
		fCellEditorListener = new ICellEditorListener() {
			public void editorValueChanged(boolean oldValidState, boolean newValidState) {
				// Ignore.
			}

			public void cancelEditor() {
				TableViewerImpl.this.cancelEditing();
			}

			public void applyEditorValue() {
				TableViewerImpl.this.applyEditorValue();
			}
		};
	}

	/**
	 * Returns <code>true</code> if there is an active cell editor;
	 * otherwise <code>false</code> is returned.
	 */
	public boolean isCellEditorActive() {
		return fCellEditor != null;
	}

	public void keyTraversed(TraverseEvent e) {
		if (e.detail == SWT.TRAVERSE_TAB_NEXT) {
			applyEditorValue();
			handleTabNext(e);
		}
		else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
			applyEditorValue();
			handleTabPrevious(e);
		}
		else if (e.keyCode == SWT.ARROW_RIGHT) {
			handleRightArrow(e);
		}
		else if (e.keyCode == SWT.ARROW_LEFT) {
			handleLeftArrow(e);
		}
		else if (e.keyCode == SWT.ARROW_UP) {
			handleUpArrow(e);
		}
		else if (e.keyCode == SWT.ARROW_DOWN) {
			handleDownArrow(e);
		}
	}

	/**
	 * Handle the mouse down event. Activate the cell editor if it is not a
	 * doble click.
	 */
	private void postActivation(final MouseEvent event) {
		if (!fIsActivating)
			return;

		(new Thread() {
			public void run() {
				if (fIsActivating) {
					getDisplay().asyncExec(new Runnable() {
						public void run() {
							activateCellEditor(event);
							fIsActivating = false;
						}
					});
				}
			}
		}).start();
	}

	private Display getDisplay() {

		return PlatformUI.getWorkbench().getDisplay();
	}

	/**
	 * Saves the value of the currently active cell editor, by delegating to
	 * the cell modifier.
	 */
	private void saveEditorValue(CellEditor cellEditor, Item tableItem) {
		if (fCellModifier != null) {
			if (!cellEditor.isValueValid()) {
				// /Do what ???
			}
			String property = null;
			if (fColumnProperties != null && fColumnNumber < fColumnProperties.length)
				property = fColumnProperties[fColumnNumber];
			fCellModifier.modify(tableItem, property, cellEditor.getValue());
		}
	}

	public void setCellEditors(CellEditor[] editors) {
		this.fCellEditors = editors;
		Control control;
		for (int i = 0; i < editors.length; i++) {
			if (editors[i] != null) {
				control = editors[i].getControl();
				if (control != null)
					control.addTraverseListener(this);
			}
		}
	}

	public void setCellModifier(ICellModifier modifier) {
		this.fCellModifier = modifier;
	}

	public void setColumnProperties(String[] columnProperties) {
		this.fColumnProperties = columnProperties;
	}

	abstract void setEditor(Control w, Item item, int columnNumber);

	abstract void setLayoutData(CellEditor.LayoutData layoutData);

	abstract void setSelection(StructuredSelection selection, boolean b);

	abstract void showSelection();

	/**
	 * Gets the singleClickCellSelect.
	 * 
	 * @return Returns a boolean
	 */
	public boolean getSingleClickCellSelect() {
		return fSingleClickCellSelect;
	}

	/**
	 * Sets the singleClickCellSelect.
	 * 
	 * @param singleClickCellSelect
	 *            The singleClickCellSelect to set
	 */
	public void setSingleClickCellSelect(boolean singleClickCellSelect) {
		fSingleClickCellSelect = singleClickCellSelect;
	}

	public void setTableItem(Item item) {
		fTableItem = item;
	}

	private void startActivationThread() {
		(new Thread() {
			public void run() {
				getDisplay().asyncExec(new Runnable() {
					public void run() {
						activateCellEditor();
						fIsActivating = false;
					}
				});
			}
		}).start();
	}

}
