/*******************************************************************************
 * Copyright (c) 2007, 2015 IBM 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:
 *     IBM Corporation - initial API and implementation
 *     Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
 *                                               - bug fix for bug 187189, 182800, 215069
 *******************************************************************************/

package org.eclipse.jface.viewers;

import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

import org.eclipse.core.runtime.Assert;

/**
 * This class is responsible to provide cell management base features for the
 * SWT-Controls {@link org.eclipse.swt.widgets.Table} and
 * {@link org.eclipse.swt.widgets.Tree}.
 *
 * @since 3.3
 *
 */
abstract class SWTFocusCellManager {

	private CellNavigationStrategy navigationStrategy;

	private ColumnViewer viewer;

	private ViewerCell focusCell;

	private FocusCellHighlighter cellHighlighter;

	private DisposeListener itemDeletionListener = e -> setFocusCell(null);

	/**
	 * @param viewer
	 * @param focusDrawingDelegate
	 * @param navigationDelegate
	 */
	public SWTFocusCellManager(ColumnViewer viewer,
			FocusCellHighlighter focusDrawingDelegate,
			CellNavigationStrategy navigationDelegate) {
		this.viewer = viewer;
		this.cellHighlighter = focusDrawingDelegate;
		if( this.cellHighlighter != null ) {
			this.cellHighlighter.setMgr(this);
		}

		this.navigationStrategy = navigationDelegate;
		hookListener(viewer);
	}

	/**
	 * This method is called by the framework to initialize this cell manager.
	 */
	void init() {
		this.cellHighlighter.init();
		this.navigationStrategy.init();
	}

	private void handleMouseDown(Event event) {
		ViewerCell cell = viewer.getCell(new Point(event.x, event.y));
		if (cell != null) {

			if (!cell.equals(focusCell)) {
				setFocusCell(cell);
			}
		}
	}

	private void handleKeyDown(Event event) {
		ViewerCell tmp = null;

		if (navigationStrategy.isCollapseEvent(viewer, focusCell, event)) {
			navigationStrategy.collapse(viewer, focusCell, event);
		} else if (navigationStrategy.isExpandEvent(viewer, focusCell, event)) {
			navigationStrategy.expand(viewer, focusCell, event);
		} else if (navigationStrategy.isNavigationEvent(viewer, event)) {
			tmp = navigationStrategy.findSelectedCell(viewer, focusCell, event);

			if (tmp != null) {
				if (!tmp.equals(focusCell)) {
					setFocusCell(tmp);
				}
			}
		}

		if (navigationStrategy.shouldCancelEvent(viewer, event)) {
			event.doit = false;
		}
	}

	private void handleSelection(Event event) {
		if ((event.detail & SWT.CHECK) == 0 && focusCell != null && focusCell.getItem() != event.item
				&& event.item != null && ! event.item.isDisposed() ) {
			ViewerRow row = viewer.getViewerRowFromItem(event.item);
			Assert
					.isNotNull(row,
							"Internal Structure invalid. Row item has no row ViewerRow assigned"); //$NON-NLS-1$
			ViewerCell tmp = row.getCell(focusCell.getColumnIndex());
			if (!focusCell.equals(tmp)) {
				setFocusCell(tmp);
			}
		}
	}

	/**
	 * Handles the {@link SWT#FocusIn} event.
	 *
	 * @param event the event
	 */
	private void handleFocusIn(Event event) {
		if (focusCell == null) {
			setFocusCell(getInitialFocusCell());
		}
	}

	abstract ViewerCell getInitialFocusCell();

	private void hookListener(final ColumnViewer viewer) {
		Listener listener = event -> {
			switch (event.type) {
			case SWT.MouseDown:
				handleMouseDown(event);
				break;
			case SWT.KeyDown:
				handleKeyDown(event);
				break;
			case SWT.Selection:
				handleSelection(event);
				break;
			case SWT.FocusIn:
				handleFocusIn(event);
				break;
			}
		};

		viewer.getControl().addListener(SWT.MouseDown, listener);
		viewer.getControl().addListener(SWT.KeyDown, listener);
		viewer.getControl().addListener(SWT.Selection, listener);
		viewer.addSelectionChangedListener(event -> {
			if( event.selection.isEmpty() ) {
				setFocusCell(null);
			}
		});
		viewer.getControl().addListener(SWT.FocusIn, listener);
		viewer.getControl().getAccessible().addAccessibleListener(
				new AccessibleAdapter() {
					@Override
					public void getName(AccessibleEvent event) {
						ViewerCell cell = getFocusCell();
						if (cell == null)
							return;

						ViewerRow row = cell.getViewerRow();
						if (row == null)
							return;

						ViewerColumn viewPart = viewer.getViewerColumn(cell
								.getColumnIndex());

						if (viewPart == null)
							return;

						CellLabelProvider labelProvider = viewPart
								.getLabelProvider();

						if (labelProvider == null)
							return;
						labelProvider.update(cell);
						event.result = cell.getText();
					}
				});

	}

	/**
	 * @return the cell with the focus
	 *
	 */
	public ViewerCell getFocusCell() {
		return focusCell;
	}

	final ViewerCell _getFocusCell() {
		return focusCell;
	}

	void setFocusCell(ViewerCell focusCell) {
		ViewerCell oldCell = this.focusCell;

		if( this.focusCell != null && ! this.focusCell.getItem().isDisposed() ) {
			this.focusCell.getItem().removeDisposeListener(itemDeletionListener);
		}

		this.focusCell = focusCell;

		if( this.focusCell != null && ! this.focusCell.getItem().isDisposed() ) {
			this.focusCell.getItem().addDisposeListener(itemDeletionListener);
		}

		if( focusCell != null ) {
			focusCell.scrollIntoView();
		}

		this.cellHighlighter.focusCellChanged(focusCell,oldCell);

		getViewer().getControl().getAccessible().setFocus(ACC.CHILDID_SELF);
	}

	ColumnViewer getViewer() {
		return viewer;
	}
}
