/*******************************************************************************
 * Copyright (c) 2008, 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
 ******************************************************************************/

package org.eclipse.jface.util;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;

/**
 * Utilities for configuring columns of trees and tables in a
 * keyboard-accessible way.
 *
 * @since 3.5
 *
 */
public class ConfigureColumns {

	/**
	 * Configure the columns of the given tree in a keyboard-accessible way,
	 * using the given shell provider to parent dialogs.
	 *
	 * @param tree the tree
	 * @param shellProvider a shell provider
	 * @return <code>false</code> if the user canceled, <code>true</code>
	 *         otherwise
	 */
	public static boolean forTree(Tree tree, IShellProvider shellProvider) {
		return new ConfigureColumnsDialog(shellProvider, tree).open() == Window.OK;
	}

	/**
	 * Configure the columns of the given tree in a keyboard-accessible way,
	 * using the given shell provider to parent dialogs.
	 *
	 * @param table the table
	 * @param shellProvider a shell provider
	 * @return <code>false</code> if the user canceled, <code>true</code>
	 *         otherwise
	 */
	public static boolean forTable(Table table, IShellProvider shellProvider) {
		return new ConfigureColumnsDialog(shellProvider, table).open() == Window.OK;
	}

	/**
	 * NON-API - This class is internal.
	 *
	 */
	static class ConfigureColumnsDialog extends Dialog {

		private Control targetControl;
		private ColumnObject[] columnObjects;
		private Table table;
		private Button upButton;
		private Button downButton;
		private Text text;
		private boolean moveableColumnsFound;

		static class ColumnObject {
			Item column;
			int index;
			String name;
			Image image;
			boolean visible;
			int width;
			boolean moveable;
			boolean resizable;

			ColumnObject(Item column, int index, String text, Image image, int width,
					boolean moveable, boolean resizable, boolean visible) {
				this.column = column;
				this.index = index;
				this.name = text;
				this.image = image;
				this.width = width;
				this.moveable = moveable;
				this.resizable = resizable;
				this.visible = visible;
			}
		}

		/**
		 * NON-API - This class is internal and will be moved to another package
		 * in 3.5. Creates a new dialog for configuring columns of the given
		 * column viewer. The column viewer must have an underlying {@link Tree}
		 * or {@link Table}, other controls are not supported.
		 *
		 * @param shellProvider
		 * @param table
		 */
		public ConfigureColumnsDialog(IShellProvider shellProvider, Table table) {
			this(shellProvider, (Control) table);
		}

		/**
		 * NON-API - This class is internal and will be moved to another package
		 * in 3.5. Creates a new dialog for configuring columns of the given
		 * column viewer. The column viewer must have an underlying {@link Tree}
		 * or {@link Table}, other controls are not supported.
		 *
		 * @param shellProvider
		 * @param tree
		 */
		public ConfigureColumnsDialog(IShellProvider shellProvider, Tree tree) {
			this(shellProvider, (Control) tree);
		}

		/**
		 * @param shellProvider
		 * @param control
		 */
		private ConfigureColumnsDialog(IShellProvider shellProvider, Control control) {
			super(shellProvider);
			this.targetControl = control;
			this.moveableColumnsFound = createColumnObjects();
		}

		@Override
		protected boolean isResizable() {
			return true;
		}

		@Override
		public void create() {
			super.create();
			getShell().setText(JFaceResources.getString("ConfigureColumnsDialog_Title")); //$NON-NLS-1$
		}

		@Override
		protected void initializeBounds() {
			super.initializeBounds();
			table.setSelection(0);
			handleSelectionChanged(0);
		}

		/**
		 * Returns true if any of the columns is moveable (can be reordered).
		 */
		private boolean createColumnObjects() {
			boolean result = true;
			Item[] columns = getViewerColumns();
			ColumnObject[] cObjects = new ColumnObject[columns.length];
			for (int i = 0; i < columns.length; i++) {
				Item c = columns[i];
				boolean moveable = getMoveable(c);
				result = result && moveable;
				cObjects[i] = new ColumnObject(c, i, getColumnName(c), getColumnImage(c),
						getColumnWidth(c), moveable, getResizable(c), true);
			}
			int[] columnOrder = getColumnOrder();
			columnObjects = new ColumnObject[columns.length];
			for (int i = 0; i < columnOrder.length; i++) {
				columnObjects[i] = cObjects[columnOrder[i]];
			}
			return result;
		}

		/**
		 * @param c
		 * @return
		 */
		private Image getColumnImage(Item item) {
			if (item instanceof TableColumn) {
				return ((TableColumn) item).getImage();
			} else if (item instanceof TreeColumn) {
				return ((TreeColumn) item).getImage();
			}
			return null;
		}

		/**
		 * @return
		 */
		private int[] getColumnOrder() {
			if (targetControl instanceof Table) {
				return ((Table) targetControl).getColumnOrder();
			} else if (targetControl instanceof Tree) {
				return ((Tree) targetControl).getColumnOrder();
			}
			return new int[0];
		}

		/**
		 * @param c
		 * @return
		 */
		private boolean getMoveable(Item item) {
			if (item instanceof TableColumn) {
				return ((TableColumn) item).getMoveable();
			} else if (item instanceof TreeColumn) {
				return ((TreeColumn) item).getMoveable();
			}
			return false;
		}

		/**
		 * @param c
		 * @return
		 */
		private boolean getResizable(Item item) {
			if (item instanceof TableColumn) {
				return ((TableColumn) item).getResizable();
			} else if (item instanceof TreeColumn) {
				return ((TreeColumn) item).getResizable();
			}
			return false;
		}

		@Override
		protected Control createDialogArea(Composite parent) {
			Composite composite = (Composite) super.createDialogArea(parent);

			table = new Table(composite, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL
					| SWT.H_SCROLL /*
									 * | SWT.CHECK
									 */);
			for (ColumnObject columnObject : columnObjects) {
				TableItem tableItem = new TableItem(table, SWT.NONE);
				tableItem.setText(columnObject.name);
				tableItem.setImage(columnObject.image);
				tableItem.setData(columnObject);
			}

			GridDataFactory.defaultsFor(table).span(1, moveableColumnsFound ? 3 : 1)
					.applyTo(table);

			if (moveableColumnsFound) {
				upButton = new Button(composite, SWT.PUSH);
				upButton.setText(JFaceResources.getString("ConfigureColumnsDialog_up")); //$NON-NLS-1$
				upButton.addListener(SWT.Selection, event -> handleMove(table, true));
				setButtonLayoutData(upButton);
				downButton = new Button(composite, SWT.PUSH);
				downButton.setText(JFaceResources
						.getString("ConfigureColumnsDialog_down")); //$NON-NLS-1$
				downButton.addListener(SWT.Selection, event -> handleMove(table, false));
				setButtonLayoutData(downButton);

				// filler label
				createLabel(composite, ""); //$NON-NLS-1$
			}

			Composite widthComposite = new Composite(composite, SWT.NONE);
			createLabel(widthComposite, JFaceResources
					.getString("ConfigureColumnsDialog_WidthOfSelectedColumn")); //$NON-NLS-1$

			text = new Text(widthComposite, SWT.SINGLE | SWT.BORDER);
			// see #initializeBounds
			text.setText(Integer.toString(1000));

			GridLayoutFactory.fillDefaults().numColumns(2).applyTo(widthComposite);

			int numColumns = moveableColumnsFound ? 2 : 1;

			GridDataFactory.defaultsFor(widthComposite).grab(false, false).span(
					numColumns, 1).applyTo(widthComposite);

			GridLayoutFactory.swtDefaults().numColumns(numColumns).applyTo(composite);

			table.addListener(SWT.Selection, event -> handleSelectionChanged(table.indexOf((TableItem) event.item)));
			text.addListener(SWT.Modify, event -> {
				ColumnObject columnObject = columnObjects[table.getSelectionIndex()];
				if (!columnObject.resizable) {
					return;
				}
				try {
					int width = Integer.parseInt(text.getText());
					columnObject.width = width;
				} catch (NumberFormatException ex) {
					// ignore for now
				}
			});

			Dialog.applyDialogFont(composite);

			return composite;
		}

		/**
		 * @param table
		 * @param up
		 */
		protected void handleMove(Table table, boolean up) {
			int index = table.getSelectionIndex();
			int newIndex = index + (up ? -1 : 1);
			if (index < 0 || index >= table.getItemCount()) {
				return;
			}
			ColumnObject columnObject = columnObjects[index];
			columnObjects[index] = columnObjects[newIndex];
			columnObjects[newIndex] = columnObject;
			table.getItem(index).dispose();
			TableItem newItem = new TableItem(table, SWT.NONE, newIndex);
			newItem.setText(columnObject.name);
			newItem.setImage(columnObject.image);
			newItem.setData(columnObject);
			table.setSelection(newIndex);
			handleSelectionChanged(newIndex);
		}

		private void createLabel(final Composite composite, String string) {
			Label label = new Label(composite, SWT.NONE);
			label.setText(string);
		}

		/**
		 * @param item
		 * @return
		 */
		private String getColumnName(Item item) {
			String result = ""; //$NON-NLS-1$
			if (item instanceof TableColumn) {
				result = ((TableColumn) item).getText();
				if (result.trim().equals("")) { //$NON-NLS-1$
					result = ((TableColumn) item).getToolTipText();
				}
			} else if (item instanceof TreeColumn) {
				result = ((TreeColumn) item).getText();
				if (result.trim().equals("")) { //$NON-NLS-1$
					result = ((TreeColumn) item).getToolTipText();
				}
			}
			return result;
		}

		/**
		 * @param item
		 * @return
		 */
		private int getColumnWidth(Item item) {
			if (item instanceof TableColumn) {
				return ((TableColumn) item).getWidth();
			} else if (item instanceof TreeColumn) {
				return ((TreeColumn) item).getWidth();
			}
			return 0;
		}

		/**
		 * @return
		 */
		private Item[] getViewerColumns() {
			if (targetControl instanceof Table) {
				return ((Table) targetControl).getColumns();
			} else if (targetControl instanceof Tree) {
				return ((Tree) targetControl).getColumns();
			}
			return new Item[0];
		}

		private void handleSelectionChanged(int index) {
			ColumnObject c = columnObjects[index];
			text.setText(Integer.toString(c.width));
			text.setEnabled(c.resizable);
			if (moveableColumnsFound) {
				upButton.setEnabled(c.moveable && index > 0);
				downButton.setEnabled(c.moveable && index + 1 < table.getItemCount());
			}
		}

		@Override
		protected void okPressed() {
			int[] columnOrder = new int[columnObjects.length];
			for (int i = 0; i < columnObjects.length; i++) {
				ColumnObject columnObject = columnObjects[i];
				columnOrder[i] = columnObject.index;
				setColumnWidth(columnObject.column, columnObject.width);
			}
			setColumnOrder(columnOrder);
			super.okPressed();
		}

		/**
		 * @param column
		 * @param width
		 */
		private void setColumnWidth(Item item, int width) {
			if (item instanceof TableColumn) {
				((TableColumn) item).setWidth(width);
			} else if (item instanceof TreeColumn) {
				((TreeColumn) item).setWidth(width);
			}
		}

		/**
		 * @param columnOrder
		 */
		private void setColumnOrder(int[] order) {
			if (targetControl instanceof Table) {
				((Table) targetControl).setColumnOrder(order);
			} else if (targetControl instanceof Tree) {
				((Tree) targetControl).setColumnOrder(order);
			}
		}
	}
}