/*******************************************************************************
 * Copyright (c) 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.bpel.ui.util;

import org.eclipse.bpel.ui.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.Accessible;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TypedListener;


/** 
 * A modification of the SWT TableCursor class to better handle empty tables and dynamic tables 
 */
public class TableCursor extends Canvas {

	Table table;
	int row = -1, column = 0; // having row negative will end up hiding the cursor
	Listener tableListener, resizeListener;
	boolean progVisible = false;
	boolean userVisible = true;
	boolean hasFocus = false;
	AccessibleAdapter accessAdapter = null;
	Listener selectionAccessListener = null;
	Listener focusAccessListener = null;
	AccessibleControlAdapter accessControlAdapter = null;
	boolean hasAccessibility = false;

	public TableCursor(Table parent, int style) {
		super(parent, style);
		table = parent;
		Listener listener = new Listener() {
			public void handleEvent(Event event) {
				switch (event.type) {
					case SWT.Dispose :
						dispose(event);
						break;
					case SWT.KeyDown :
						keyDown(event);
						break;
					case SWT.Paint :
						paint(event);
						break;
					case SWT.Traverse :
						traverse(event);
						break;
				}
			}
		};
		addListener(SWT.Dispose, listener);
		addListener(SWT.KeyDown, listener);
		addListener(SWT.Paint, listener);
		addListener(SWT.Traverse, listener);

		tableListener = new Listener() {
			public void handleEvent(Event event) {
				switch (event.type) {
					case SWT.MouseDown :
						tableMouseDown(event);
						break;
					case SWT.FocusIn :
						tableFocusIn(event);
						break;
				}
			}
		};
		table.addListener(SWT.FocusIn, tableListener);
		table.addListener(SWT.MouseDown, tableListener);

		resizeListener = new Listener() {
			public void handleEvent(Event event) {
				resize();
			}
		};
		
		ScrollBar hBar = table.getHorizontalBar();
		if (hBar != null)
			hBar.addListener(SWT.Selection, resizeListener);
		
		ScrollBar vBar = table.getVerticalBar();
		if (vBar != null) 
			vBar.addListener(SWT.Selection, resizeListener);
		
		this.addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				hasFocus = true;
				redraw();
			}

			public void focusLost(FocusEvent e) {
				hasFocus = false;
				redraw();
				
			}});

		refresh();
		
		initAccessible();
	}
	
	/** 
	 * this is called whenever the content of the table has changed, it 
	 * will reconcile the cursor and any listeners that we need
	 */ 
	
	public void refresh() {
		// the number of rows and columns could have changed, ideally we should track
		// which columns have listeners but for the now, we'll just remove and readd
		int columns = table.getColumnCount();
		for (int i = 0; i < columns; i++) {
			TableColumn column = table.getColumn(i);
			column.removeListener(SWT.Resize, resizeListener);
		}
		for (int i = 0; i < columns; i++) {
			TableColumn column = table.getColumn(i);
			column.addListener(SWT.Resize, resizeListener);
		}
		
		// reset the row and column to be a valid one
		//boolean repaint = false;
		if (row >= table.getItemCount()) {
			row = table.getItemCount()-1;
		}
		if (column >= columns) {
			column = columns - 1;
		}
		
		// check to see what the selection is, and reset the tablecursor to 
		// a valid row/column in that selection
		
		TableItem[] selection = table.getSelection();
		if (selection.length == 0) {
			row = -1;
		}
		else {
//			// there is a selection, so make sure our table cursor is in that selection
//			// range
//			int min = 999999;
//			int max = -1;
//			for (int i = 0; i < selection.length; i++) {
//				int temp = table.indexOf(selection[i]);
//				min = Math.min(temp, min);
//				max = Math.max(temp, max);
//			}
//			if (row > max || row < min)
//				row = min;
		}
		setSelection(row, column);
	}

	public void addSelectionListener(SelectionListener listener) {
		checkWidget();
		if (listener == null)
			SWT.error(SWT.ERROR_NULL_ARGUMENT);
		TypedListener typedListener = new TypedListener(listener);
		addListener(SWT.Selection, typedListener);
		addListener(SWT.DefaultSelection, typedListener);
	}

	void dispose(Event event) {
		Display display = getDisplay();
		display.asyncExec(new Runnable() {
			public void run() {
				if (table.isDisposed())
					return;
				table.removeListener(SWT.FocusIn, tableListener);
				table.removeListener(SWT.MouseDown, tableListener);
				int columns = table.getColumnCount();
				for (int i = 0; i < columns; i++) {
					TableColumn column = table.getColumn(i);
					column.removeListener(SWT.Resize, resizeListener);
				}
				ScrollBar hBar = table.getHorizontalBar();
				if (hBar != null) {
					hBar.removeListener(SWT.Selection, resizeListener);
				}
				ScrollBar vBar = table.getVerticalBar();
				if (vBar != null) {
					vBar.removeListener(SWT.Selection, resizeListener);
				}
			}
		});
	}

	void keyDown(Event event) {
		switch (event.character) {
			case SWT.CR :
				notifyListeners(SWT.DefaultSelection, new Event());
				return;
		}
		switch (event.keyCode) {
			case SWT.ARROW_UP :
				if (column < 0)
					column = 0;
				setRowColumn(row - 1, column, true);
				break;
			case SWT.ARROW_DOWN :
			if (column < 0)
				column = 0;
				setRowColumn(row + 1, column, true);
				break;
			case SWT.ARROW_LEFT :
			case SWT.ARROW_RIGHT :
				{
					if (column < 0)
						column = 0;
					int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
					if (event.keyCode == leadKey) {
						setRowColumn(row, column - 1, true);
					} else {
						setRowColumn(row, column + 1, true);
					}
					break;
				}
			case SWT.HOME :
				if (column < 0)
					column = 0;
				setRowColumn(0, column, true);
				break;
			case SWT.END :
				{
					if (column < 0)
						column = 0;
					int row = table.getItemCount() - 1;
					setRowColumn(row, column, true);
					break;
				}
		}
	}

	void paint(Event event) {
		GC gc = event.gc;
		Display display = getDisplay();
		gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
		gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION));
		gc.fillRectangle(event.x, event.y, event.width, event.height);
		TableItem item = null;
		if (row >= 0)
			item = table.getItem(row);
		int x = 0, y = 0;
		Point size = getSize();
		if (item != null) {
			Image image = item.getImage(column);
			if (image != null) {
				Rectangle imageSize = image.getBounds();
				int imageY = y + (int) (((float)size.y - (float)imageSize.height) / 2.0);
				gc.drawImage(image, x, imageY);
				x += imageSize.width;
			}
			x += (column == 0) ? 2 : 6;
			int textY = y + (int) (((float)size.y - (float)gc.getFontMetrics().getHeight()) / 2.0);
			gc.drawString(item.getText(column), x, textY);
		}
		
		if (isFocusControl()) {
			gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
			gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
			gc.drawFocus(0, 0, size.x, size.y);
		}

	}

	void tableFocusIn(Event event) {
		if (isDisposed())
			return;
		if (isVisible()) {
			setFocus();
			redraw();
		}
	}

	void tableMouseDown(Event event) {
		event.doit = true;
		if (isDisposed() || !isVisible())
			return;
		Point pt = new Point(event.x, event.y);
		Rectangle clientRect = table.getClientArea();
		int columns = table.getColumnCount();
		int start = table.getTopIndex();
		int end = table.getItemCount();
		for (int row = start; row < end; row++) {
			TableItem item = table.getItem(row);
			for (int column = 0; column < columns; column++) {
				Rectangle rect = item.getBounds(column);
				if (rect.y > clientRect.y + clientRect.height)
					return;
				if (rect.contains(pt)) {
					setRowColumn(row, column, true);
					//setFocus();
					return;
				}
			}
		}
	}

	void traverse(Event event) {
		switch (event.detail) {
			case SWT.TRAVERSE_ARROW_NEXT :
			case SWT.TRAVERSE_ARROW_PREVIOUS :
			case SWT.TRAVERSE_RETURN :
				event.doit = false;
				return;
		}
		event.doit = true;
	}

	void setRowColumn(int row, int column, boolean notify) {
		if (0 <= row && row < table.getItemCount()) {
			if (0 <= column && column < table.getColumnCount()) {
				this.row = row;
				this.column = column;
				TableItem item = table.getItem(row);
				table.showItem(item);
				setBounds(item.getBounds(column));
//				redraw();
				if (notify) {
					notifyListeners(SWT.Selection, new Event());
				}
			}
		}
		redraw();
		updateVisible();
	}

	@Override
	public void setVisible(boolean visible) {
		checkWidget();
		userVisible = visible;
		resize();
	}

	void resize() {
		if (row >= 0 && row < table.getItemCount()) {
			TableItem item = table.getItem(row);
			setBounds(item.getBounds(column));
		}
		updateVisible();
	}
	
	void updateVisible() {
		progVisible = false;
		if (0 <= row && row < table.getItemCount()) {
			if (0 <= column && column < table.getColumnCount()) {
				progVisible = true;
			}
		}
		super.setVisible(progVisible && userVisible);
	}

	public int getColumn() {
		checkWidget();
		return column;
	}

	public TableItem getRow() {
		checkWidget();
		if (table.getItemCount() == 0)
			return null;
		return table.getItem(row);
	}

	public void setSelection(int row, int column) {
		checkWidget();
		setRowColumn(row, column, false);
	}
	
	public void setSelection(TableItem row, int column) {
		checkWidget();
		setRowColumn(table.indexOf(row), column, false);
	}
	
	private void initAccessible() {
		final Accessible accessible = getAccessible();
		if (accessAdapter == null) {
			accessAdapter = new AccessibleAdapter() {
				@Override
				public void getName(AccessibleEvent e) {
					String name = null;
					TableItem item = null;
					TableColumn[] tableColumns = table.getColumns();
					TableColumn thisCol = null;

					if (row >= 0 && row < table.getItemCount() && column >= 0 && column < table.getColumnCount()) {
						item = table.getItem(row);
						if (column >= 0 && column < tableColumns.length) {
							thisCol = tableColumns[column];
						}
					}
					if (item != null) {
						if (thisCol != null)
							name = thisCol.getText();
						if (name != null && name.length() > 0)
							name = name + "=" + item.getText(column); //$NON-NLS-1$
						else
							name = item.getText(column);
					}
					e.result = name;
				}

				@Override
				public void getHelp(AccessibleEvent e) {
					String help = null;
					e.result = help;
				}
				@Override
				public void getKeyboardShortcut(AccessibleEvent e) {
				}
			};

			accessControlAdapter = new AccessibleControlAdapter() {
				@Override
				public void getChildAtPoint(AccessibleControlEvent e) {
					Point testPoint = toControl(new Point(e.x, e.y));
					int childID = ACC.CHILDID_NONE;
					if (childID == ACC.CHILDID_NONE) {
						Rectangle location = getBounds();
						location.height = location.height - getClientArea().height;
						if (location.contains(testPoint)) {
							childID = ACC.CHILDID_SELF;
						}
					}
					e.childID = childID;
				}

				@Override
				public void getLocation(AccessibleControlEvent e) {
					Rectangle location = null;
					int childID = e.childID;
					if (childID == ACC.CHILDID_SELF) {
						location = getBounds();
					}
					if (location != null) {
						Point pt = toDisplay(new Point(location.x, location.y));
						e.x = pt.x;
						e.y = pt.y;
						e.width = location.width;
						e.height = location.height;
					}
				}

				@Override
				public void getChildCount(AccessibleControlEvent e) {
					e.detail = 0;
				}

				@Override
				public void getDefaultAction(AccessibleControlEvent e) {
					String action = Messages.TableCursor_ScreenReader_Cell_Action1; 
					e.result = action;
				}

				@Override
				public void getFocus(AccessibleControlEvent e) {
					int childID = ACC.CHILDID_NONE;
					if (isFocusControl()) {
						childID = ACC.CHILDID_SELF;
					}
					e.childID = childID;
				}

				@Override
				public void getRole(AccessibleControlEvent e) {
					int role = 0;
					int childID = e.childID;
					if (childID == ACC.CHILDID_SELF)
						role = ACC.ROLE_LISTITEM;
					e.detail = role;
				}

				@Override
				public void getSelection(AccessibleControlEvent e) {
					e.childID = ACC.CHILDID_NONE;
				}

				@Override
				public void getState(AccessibleControlEvent e) {
					int state = 0;
					int childID = e.childID;
					if (childID == ACC.CHILDID_SELF) {
						state = ACC.STATE_SELECTABLE;
						if (isFocusControl()) {
							state |= ACC.STATE_FOCUSABLE;
							if (TableCursor.this.hasFocus) {
								state += ACC.STATE_FOCUSED | ACC.STATE_SELECTED;
							}
						}
					}
					e.detail = state;
				}

				@Override
				public void getChildren(AccessibleControlEvent e) {
					e.children = null;
				}
			};

			selectionAccessListener = new Listener() {
				public void handleEvent(Event event) {
					accessible.setFocus(ACC.CHILDID_SELF);
				}
			};

			focusAccessListener = new Listener() {
				public void handleEvent(Event event) {
					accessible.setFocus(ACC.CHILDID_SELF);
				}
			};
		}

		if (hasAccessibility) {
			accessible.removeAccessibleListener(accessAdapter);
			accessible.removeAccessibleControlListener(accessControlAdapter);
			removeListener(SWT.Selection, selectionAccessListener);
			removeListener(SWT.FocusIn, focusAccessListener);
			hasAccessibility = false;
		}
		if (hasAccessibility == false) {
			accessible.addAccessibleListener(accessAdapter);
			accessible.addAccessibleControlListener(accessControlAdapter);
			addListener(SWT.Selection, selectionAccessListener);
			addListener(SWT.FocusIn, focusAccessListener);
			hasAccessibility = true;
		}
	}	
}

