/*******************************************************************************
 * Copyright (c) 2009, 2013 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *     IBM Corporation - bug fixing
 *******************************************************************************/
package org.eclipse.debug.tests.viewer.model;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.function.Function;

import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation2;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentationFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
import org.eclipse.debug.tests.AbstractDebugTest;
import org.eclipse.debug.tests.TestUtil;
import org.eclipse.debug.tests.viewer.model.TestModel.TestElement;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.XMLMemento;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Tests to verify that the viewer properly handles initial columns width.
 */
public class ColumnPresentationTests extends AbstractDebugTest implements ITestModelUpdatesListenerConstants {
	private Display fDisplay;
	private Shell fShell;
	private TreeModelViewer fViewer;
	private TestModelUpdatesListener fListener;
	private boolean fResized = false;

	@Override
	@Before
	public void setUp() throws Exception {
		super.setUp();
		createViewer();
	}

	@Override
	@After
	public void tearDown() throws Exception {
		destroyViewer();
		super.tearDown();
	}

	void createViewer() {
		fDisplay = PlatformUI.getWorkbench().getDisplay();
		fShell = new Shell(fDisplay);
		fShell.setSize(800, 600);
		fShell.setLayout(new FillLayout());
		fViewer = new TreeModelViewer(fShell, SWT.VIRTUAL, new PresentationContext("TestViewer")); //$NON-NLS-1$
		fViewer.getTree().addControlListener(new ControlListener() {
			@Override
			public void controlResized(ControlEvent e) {
				fResized = true;
			}

			@Override
			public void controlMoved(ControlEvent e) {
			}
		});
		fListener = new TestModelUpdatesListener(fViewer, false, false);
		fShell.open();
		TestUtil.processUIEvents();
	}

	void destroyViewer() throws Exception {
		fListener.dispose();
		fViewer.getPresentationContext().dispose();
		// Close the shell.
		fShell.close();
		TestUtil.processUIEvents();
	}

	void recreateViewer() throws Exception {
		destroyViewer();
		createViewer();
	}

	static class MyColumnPresentation implements IColumnPresentation {
		private IPresentationContext fContext;
		protected String[] columnIds;

		MyColumnPresentation() {
			this (makeDefaultColumnIds());
		}

		static String[] makeDefaultColumnIds() {
			String[] columnIds = new String[5];
			for (int i = 0; i < columnIds.length; i++) {
				columnIds[i] = "ColumnId_" + i; //$NON-NLS-1$
			}
			return columnIds;
		}

		MyColumnPresentation(String[] columnIds) {
			this.columnIds = columnIds;
		}

		@Override
		public void init(IPresentationContext context) {
			fContext = context;
		}

		@Override
		public void dispose() {
			fContext = null;
		}

		@Override
		public ImageDescriptor getImageDescriptor(String id) {
			return null;
		}

		protected IPresentationContext getPresentationContext() {
			return fContext;
		}

		@Override
		public String[] getAvailableColumns() {
			return columnIds;
		}

		@Override
		public String[] getInitialColumns() {
			return columnIds;
		}

		@Override
		public String getHeader(String id) {
			if (Arrays.asList(columnIds).indexOf(id) != -1) {
				return id;
			}
			return null;
		}

		@Override
		public String getId() {
			return "MyColumnPresentationID"; //$NON-NLS-1$
		}

		@Override
		public boolean isOptional() {
			return true;
		}

	}

	static class MyColumnPresentation2 extends MyColumnPresentation implements IColumnPresentation2 {
		int[] repliedWidths;

		MyColumnPresentation2() {
			super();
			for (int i = 0; i < columnIds.length; i++) {
				columnIds[i] += "_2"; //$NON-NLS-1$
			}
			repliedWidths = new int[columnIds.length];
			Arrays.fill(repliedWidths, -2);
		}

		@Override
		public int getInitialColumnWidth(String id, int treeWidgetWidth,
				String[] visibleColumnIds) {
			for (int i = 0; i < columnIds.length; i++) {
				if (id.equals(columnIds[i]) && i == 0) {
					repliedWidths[i] = 2 * (treeWidgetWidth / visibleColumnIds.length);
					return repliedWidths[i];
				}
				if (id.equals(columnIds[i]) && (i == 1 || i == 2)) {
					repliedWidths[i] = (treeWidgetWidth / visibleColumnIds.length) / 2;
					return repliedWidths[i];
				}
				if (id.equals(columnIds[i]) && i == 3) {
					repliedWidths[i] = (treeWidgetWidth / visibleColumnIds.length);
					return repliedWidths[i];
				}
				if (id.equals(columnIds[i]) && i == 4) {
					repliedWidths[i] = -1;
					return repliedWidths[i];
				}
			}
			return -1;
		}

		@Override
		public String getId() {
			return "MyColumnPresentation2ID"; //$NON-NLS-1$
		}
	}

	class MyModel extends TestModel implements IColumnPresentationFactory {
		MyColumnPresentation colPresenation;

		MyModel(MyColumnPresentation cp1) {
			colPresenation = cp1;
		}

		@Override
		public IColumnPresentation createColumnPresentation(
				IPresentationContext context, Object element) {
			if (colPresenation != null) {
				return colPresenation;
			}
			return null;
		}

		@Override
		public String getColumnPresentationId(IPresentationContext context,
				Object element) {
			if (colPresenation != null) {
				return colPresenation.getId();
			}
			return null;
		}

	}

	private TestModel makeModel(MyColumnPresentation cp, String rootSufffix) throws Exception {
		MyModel model = new MyModel(cp);
		model.setRoot(new TestElement(model, "root" + rootSufffix, new TestElement[] { //$NON-NLS-1$
		new TestElement(model, "1", true, true, new TestElement[0]), //$NON-NLS-1$
		new TestElement(model, "2", true, false, new TestElement[0]), //$NON-NLS-1$
		new TestElement(model, "3", false, true, new TestElement[0]), //$NON-NLS-1$
		new TestElement(model, "4", false, false, new TestElement[0]), //$NON-NLS-1$
		new TestElement(model, "5", new TestElement[0]), //$NON-NLS-1$
		new TestElement(model, "6", new TestElement[0]) })); //$NON-NLS-1$
		fListener.reset(TreePath.EMPTY, model.getRootElement(), -1, true, false);
		fViewer.setInput(model.getRootElement());
		waitWhile(t -> !fListener.isFinished(), createListenerErrorMessage());
		model.validateData(fViewer, TreePath.EMPTY);
		return model;
	}

	/**
	 * In this test: verify that tree viewer can handle IColumnPresentation
	 * implementation without requiring the presentation being
	 * IColumnPresentation2 (i.e. ensure we do not break backward compatibility
	 * of interface). Also, we verify that the initial columns width is the
	 * average of tree width / number of visible columns, which is the logic in
	 * InternalTreeModelViewer.
	 */
	@Test
	public void testInitialColumnAverageWidth() throws Exception {
		fResized = false;

		MyColumnPresentation colPre = new MyColumnPresentation();
		makeModel(colPre, "m1"); //$NON-NLS-1$
		Tree tree = fViewer.getTree();
		TreeColumn[] columns = tree.getColumns();
		int treeWidth = tree.getSize().x;
		int avgWidth = treeWidth / columns.length;

		// Resizing the tree invalidates the test.
		if (fResized) {
			return;
		}

		for (int i = 0; i < columns.length - 1; i++) {
			assertEquals(avgWidth, columns[i].getWidth());
		}
		// Checking of the width of the last column is not reliable.
		// I.e. it's handled differenty on different platforms.
		//int remainder = treeWidth % columns.length;
		//assertEquals(avgWidth + remainder, columns[columns.length - 1].getWidth());
	}

	/**
	 * In this test: verify that tree viewer can handle IColumnPresentation2.
	 * Also, we verify that the initial columns width is the width computed by
	 * the IColumnPresentation2 implementation.
	 */
	@Test
	public void testInitialColumnWidth() throws Exception {
		fResized = false;

		MyColumnPresentation2 colPre = new MyColumnPresentation2();
		makeModel(colPre, "m2"); //$NON-NLS-1$
		Tree tree = fViewer.getTree();
		TreeColumn[] columns = tree.getColumns();

		// Resizing the tree invalidates the test.
		if (fResized) {
			return;
		}

		for (int i = 0; i < columns.length; i++) {
			int width = colPre.repliedWidths[i];
			if (width != -1) {
				assertEquals(width, columns[i].getWidth());
			}
		}
	}

	/**
	 * In this test: verify that tree viewer can handle IColumnPresentation2.
	 * Also, we verify that the initial columns width from IColumnPresentation2
	 * is not used when there are user settings inside the viewer which are
	 * created from user resizing columns.
	 */
	@Test
	public void testRespectUserSettings() throws Exception {
		MyColumnPresentation2 colPre = new MyColumnPresentation2();
		makeModel(colPre, "m2"); //$NON-NLS-1$
		TreeColumn[] columns = fViewer.getTree().getColumns();
		// simulate user resizing each column width
		int[] newWidths = new int[columns.length];
		for (int i = 0; i < columns.length; i++) {
			newWidths[i] = columns[i].getWidth() + 10;
			columns[i].setWidth(newWidths[i]);
		}
		for (int i = 0; i < columns.length; i++) {
			assertEquals(newWidths[i], columns[i].getWidth());
		}
		// get InternalTreeModelViewer to rebuild columns due to change of
		// model and presentation - first set to another model and column
		// presentation, then switch to a model with original presentation.
		makeModel(new MyColumnPresentation(), "m1"); //$NON-NLS-1$
		makeModel(colPre, "m3"); //$NON-NLS-1$
		// verify user resized widths are used instead of the initial widths from IColumnPresentation2
		columns = fViewer.getTree().getColumns();
		for (int i = 0; i < columns.length; i++) {
			assertEquals(newWidths[i], columns[i].getWidth());
		}

		// get InternalTreeModelViewer to rebuild columns due to hide and show columns
		fViewer.setShowColumns(false);
		TestUtil.processUIEvents();
		waitWhile(t -> fViewer.getTree().getColumns().length > 0, createColumnsErrorMessage());
		fViewer.setShowColumns(true);
		TestUtil.processUIEvents();
		waitWhile(t -> fViewer.getTree().getColumns().length != newWidths.length, createColumnsErrorMessage());
		// verify user resized widths are used instead of the initial widths from IColumnPresentation2
		columns = fViewer.getTree().getColumns();
		for (int i = 0; i < columns.length; i++) {
			assertEquals(newWidths[i], columns[i].getWidth());
		}
	}

	/**
	 * In this test: verify that tree viewer can handle IColumnPresentation2.
	 * Also, we verify that the initial columns width from IColumnPresentation2
	 * is not used when there are user settings inside the viewer which are
	 * restored from memento, e.g., restoring workspace, etc.
	 */
	@Test
	public void testRespectMemento() throws Exception {
		MyColumnPresentation2 colPre = new MyColumnPresentation2();
		makeModel(colPre, "m2"); //$NON-NLS-1$
		TreeColumn[] columns = fViewer.getTree().getColumns();
		// simulate user resizing each column width
		int[] newWidths = new int[columns.length];
		for (int i = 0; i < columns.length; i++) {
			newWidths[i] = columns[i].getWidth() + 10;
			columns[i].setWidth(newWidths[i]);
		}
		for (int i = 0; i < columns.length; i++) {
			assertEquals(newWidths[i], columns[i].getWidth());
		}
		// save memento
		final XMLMemento memento = XMLMemento.createWriteRoot("TEST"); //$NON-NLS-1$
		fViewer.saveState(memento);
		// throw away any settings inside the viewer and create a new viewer
		// with memento settings, this is the same effect resulted from closing
		// and opening workspace again.
		recreateViewer();
		fViewer.initState(memento);
		// get InternalTreeModelViewer to rebuild columns
		makeModel(colPre, "m2"); //$NON-NLS-1$
		// verify widths from memento are used instead of the initial widths from IColumnPresentation2
		columns = fViewer.getTree().getColumns();
		for (int i = 0; i < columns.length; i++) {
			assertEquals(newWidths[i], columns[i].getWidth());
		}
	}

	/**
	 * In this test: verify that tree viewer can handle the column presentation
	 * changing its available column IDs between runs (bug 360015).
	 */
	@Test
	public void testChangedColumnIds() throws Exception {
		MyColumnPresentation colPre = new MyColumnPresentation();

		makeModel(colPre, "m1"); //$NON-NLS-1$
		TreeColumn[] columns = fViewer.getTree().getColumns();
		// Select visible columns
		fViewer.setVisibleColumns(new String[] { colPre.columnIds[0] });
		TestUtil.processUIEvents();
		waitWhile(t -> fViewer.getTree().getColumns().length != 1, createColumnsErrorMessage());

		// get InternalTreeModelViewer to rebuild columns due to change of
		// model and presentation - first set to another model and column
		// presentation, then switch to a model with original presentation.
		makeModel(new MyColumnPresentation2(), "m2"); //$NON-NLS-1$

		String[] newColumnIds = MyColumnPresentation.makeDefaultColumnIds();
		newColumnIds[0] = "new_column_id"; //$NON-NLS-1$
		colPre = new MyColumnPresentation(newColumnIds);

		makeModel(colPre, "m3"); //$NON-NLS-1$

		// verify user resized widths are used instead of the initial widths from IColumnPresentation2
		columns = fViewer.getTree().getColumns();
		for (int i = 0; i < columns.length; i++) {
			assertEquals(newColumnIds[i], columns[i].getText());
		}
	}

	private Function<AbstractDebugTest, String> createColumnsErrorMessage() {
		return t -> "Unexpected columns number: " + fViewer.getTree().getColumns().length;
	}

	private Function<AbstractDebugTest, String> createListenerErrorMessage() {
		return t -> "Listener not finished: " + fListener;
	}
}
