/*******************************************************************************
 * Copyright (c) 2008, 2018 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
 *     EclipseSource - ongoing development
 *     Sonatype, Inc. - ongoing development
 *     Red Hat, Inc. - support for remediation page
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.dialogs;

import java.util.ArrayList;
import org.eclipse.equinox.internal.p2.ui.ProvUI;
import org.eclipse.equinox.internal.p2.ui.ProvUIMessages;
import org.eclipse.equinox.internal.p2.ui.model.*;
import org.eclipse.equinox.internal.p2.ui.viewers.*;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.operations.ProfileChangeOperation;
import org.eclipse.equinox.p2.ui.Policy;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

/**
 * A wizard page that presents a check box list of IUs and allows the user to
 * select and deselect them. Typically the first page in a provisioning
 * operation wizard, and usually it is the page used to report resolution errors
 * before advancing to resolution detail.
 *
 * @since 3.5
 *
 */
public class SelectableIUsPage extends ResolutionStatusPage implements IResolutionErrorReportingPage {

	private static final String DIALOG_SETTINGS_SECTION = "SelectableIUsPage"; //$NON-NLS-1$

	IUElementListRoot root;
	Object[] initialSelections;
	ProfileChangeOperation resolvedOperation;
	CheckboxTableViewer tableViewer;
	IUDetailsGroup iuDetailsGroup;
	ProvElementContentProvider contentProvider;
	IUDetailsLabelProvider labelProvider;
	protected Display display;
	protected Policy policy;
	SashForm sashForm;

	public SelectableIUsPage(ProvisioningUI ui, ProvisioningOperationWizard wizard, IUElementListRoot root,
			Object[] initialSelections) {
		super("IUSelectionPage", ui, wizard); //$NON-NLS-1$
		this.root = root;
		if (root == null)
			root = new IUElementListRoot(ui);
		if (initialSelections == null)
			this.initialSelections = new IInstallableUnit[0];
		else
			this.initialSelections = initialSelections;

	}

	@Override
	public void createControl(Composite parent) {
		display = parent.getDisplay();
		sashForm = new SashForm(parent, SWT.VERTICAL);
		FillLayout layout = new FillLayout();
		sashForm.setLayout(layout);
		GridData data = new GridData(GridData.FILL_BOTH);
		sashForm.setLayoutData(data);
		initializeDialogUnits(sashForm);

		Composite composite = new Composite(sashForm, SWT.NONE);
		GridLayout gridLayout = new GridLayout();
		gridLayout.marginWidth = 0;
		gridLayout.marginHeight = 0;
		composite.setLayout(gridLayout);

		tableViewer = createTableViewer(composite);
		data = new GridData(GridData.FILL_BOTH);
		data.heightHint = convertHeightInCharsToPixels(ILayoutConstants.DEFAULT_TABLE_HEIGHT);
		data.widthHint = convertWidthInCharsToPixels(ILayoutConstants.DEFAULT_TABLE_WIDTH);
		Table table = tableViewer.getTable();
		table.setLayoutData(data);
		table.setHeaderVisible(true);
		activateCopy(table);
		IUColumnConfig[] columns = getColumnConfig();
		for (int i = 0; i < columns.length; i++) {
			TableColumn tc = new TableColumn(table, SWT.LEFT, i);
			tc.setResizable(true);
			tc.setText(columns[i].getColumnTitle());
			tc.setWidth(columns[i].getWidthInPixels(table));
		}

		tableViewer.addSelectionChangedListener(event -> setDetailText(resolvedOperation));

		tableViewer.addCheckStateListener(event -> {
			// If the checkEvent is on a locked update element, uncheck it and select it.
			if (event.getElement() instanceof AvailableUpdateElement) {
				AvailableUpdateElement checkedElement = (AvailableUpdateElement) event.getElement();
				if (checkedElement.isLockedForUpdate()) {
					event.getCheckable().setChecked(checkedElement, false);
					// Select the element so that the locked description is displayed
					CheckboxTableViewer viewer = ((CheckboxTableViewer) event.getSource());
					int itemCount = viewer.getTable().getItemCount();
					for (int i = 0; i < itemCount; i++) {
						if (viewer.getElementAt(i).equals(checkedElement)) {
							viewer.getTable().deselectAll();
							viewer.getTable().select(i);
							setDetailText(resolvedOperation);
							break;
						}
					}
				}
			}
			updateSelection();
		});

		// Filters and sorters before establishing content, so we don't refresh
		// unnecessarily.
		IUComparator comparator = new IUComparator(IUComparator.IU_NAME);
		comparator.useColumnConfig(ProvUI.getIUColumnConfig());
		tableViewer.setComparator(comparator);
		tableViewer.setComparer(new ProvElementComparer());

		contentProvider = new ProvElementContentProvider();
		tableViewer.setContentProvider(contentProvider);
		labelProvider = new IUDetailsLabelProvider(null, ProvUI.getIUColumnConfig(), getShell());
		tableViewer.setLabelProvider(labelProvider);
		tableViewer.setInput(root);
		setInitialCheckState();

		// Select and Deselect All buttons
		createSelectButtons(composite);

		// Controls for filtering/presentation/site selection
		Composite controlsComposite = new Composite(composite, SWT.NONE);
		gridLayout = new GridLayout();
		gridLayout.marginWidth = 0;
		gridLayout.marginHeight = 0;
		gridLayout.numColumns = 2;
		gridLayout.makeColumnsEqualWidth = true;
		gridLayout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
		controlsComposite.setLayout(layout);
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		controlsComposite.setLayoutData(gd);

		// The text area shows a description of the selected IU, or error detail if
		// applicable.
		iuDetailsGroup = new IUDetailsGroup(sashForm, tableViewer,
				convertWidthInCharsToPixels(ILayoutConstants.DEFAULT_TABLE_WIDTH), true);

		updateStatus(root, resolvedOperation);
		setControl(sashForm);
		sashForm.setWeights(getSashWeights());
		Dialog.applyDialogFont(sashForm);
	}

	private void createSelectButtons(Composite parent) {
		Composite buttonParent = new Composite(parent, SWT.NONE);
		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 3;
		gridLayout.marginWidth = 0;
		gridLayout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
		buttonParent.setLayout(gridLayout);
		GridData data = new GridData(SWT.FILL, SWT.DEFAULT, true, false);
		buttonParent.setLayoutData(data);

		Button selectAll = new Button(buttonParent, SWT.PUSH);
		selectAll.setText(ProvUIMessages.SelectableIUsPage_Select_All);
		setButtonLayoutData(selectAll);
		selectAll.addListener(SWT.Selection, event -> {
			tableViewer.setAllChecked(true);
			updateSelection();
		});

		Button deselectAll = new Button(buttonParent, SWT.PUSH);
		deselectAll.setText(ProvUIMessages.SelectableIUsPage_Deselect_All);
		setButtonLayoutData(deselectAll);
		deselectAll.addListener(SWT.Selection, event -> {
			tableViewer.setAllChecked(false);
			updateSelection();
		});

		// dummy to take extra space
		Label dummy = new Label(buttonParent, SWT.NONE);
		data = new GridData(SWT.FILL, SWT.FILL, true, true);
		dummy.setLayoutData(data);

		// separator underneath
		Label sep = new Label(buttonParent, SWT.HORIZONTAL | SWT.SEPARATOR);
		data = new GridData(SWT.FILL, SWT.DEFAULT, true, false);
		data.horizontalSpan = 3;
		sep.setLayoutData(data);
	}

	protected CheckboxTableViewer createTableViewer(Composite parent) {
		// The viewer allows selection of IU's for browsing the details,
		// and checking to include in the provisioning operation.
		CheckboxTableViewer v = CheckboxTableViewer.newCheckList(parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
		return v;
	}

	@Override
	public Object[] getCheckedIUElements() {
		if (tableViewer == null)
			return initialSelections;
		return tableViewer.getCheckedElements();
	}

	@Override
	public Object[] getSelectedIUElements() {
		return tableViewer.getStructuredSelection().toArray();
	}

	@Override
	protected Object[] getSelectedElements() {
		return tableViewer.getStructuredSelection().toArray();
	}

	protected IInstallableUnit[] elementsToIUs(Object[] elements) {
		IInstallableUnit[] theIUs = new IInstallableUnit[elements.length];
		for (int i = 0; i < elements.length; i++) {
			theIUs[i] = ProvUI.getAdapter(elements[i], IInstallableUnit.class);
		}
		return theIUs;
	}

	protected void setInitialCheckState() {
		if (initialSelections == null) {
			return;
		}

		ArrayList<Object> selections = new ArrayList<>(initialSelections.length);

		for (Object initialSelection : initialSelections) {
			if (initialSelection instanceof AvailableUpdateElement) {
				AvailableUpdateElement element = (AvailableUpdateElement) initialSelection;
				if (element.isLockedForUpdate()) {
					continue;
				}
			}
			selections.add(initialSelection);
		}
		tableViewer.setCheckedElements(selections.toArray(new Object[selections.size()]));
	}

	/*
	 * Overridden so that we don't call getNextPage(). We use getNextPage() to start
	 * resolving the operation so we only want to do that when the next button is
	 * pressed.
	 *
	 */
	@Override
	public boolean canFlipToNextPage() {
		return isPageComplete();
	}

	/*
	 * Overridden to null out any cached page so that the wizard is always
	 * consulted. This allows wizards to do things like synchronize previous page
	 * selections with this page.
	 */
	@Override
	public IWizardPage getPreviousPage() {
		setPreviousPage(null);
		return super.getPreviousPage();
	}

	@Override
	protected String getClipboardText(Control control) {
		StringBuilder buffer = new StringBuilder();
		Object[] elements = getSelectedElements();
		for (int i = 0; i < elements.length; i++) {
			if (i > 0)
				buffer.append(CopyUtils.NEWLINE);
			buffer.append(labelProvider.getClipboardText(elements[i], CopyUtils.DELIMITER));
		}
		return buffer.toString();
	}

	@Override
	protected IInstallableUnit getSelectedIU() {
		java.util.List<IInstallableUnit> units = ElementUtils.elementsToIUs(getSelectedElements());
		if (units.size() == 0)
			return null;
		return units.get(0);
	}

	@Override
	protected IUDetailsGroup getDetailsGroup() {
		return iuDetailsGroup;
	}

	@Override
	protected boolean isCreated() {
		return tableViewer != null;
	}

	@Override
	protected void updateCaches(IUElementListRoot newRoot, ProfileChangeOperation op) {
		resolvedOperation = op;
		if (newRoot != null && root != newRoot) {
			root = newRoot;
			if (tableViewer != null)
				tableViewer.setInput(newRoot);
		}

	}

	@Override
	public void setCheckedElements(Object[] elements) {
		if (tableViewer == null)
			initialSelections = elements;
		else
			tableViewer.setCheckedElements(elements);
	}

	@Override
	protected SashForm getSashForm() {
		return sashForm;
	}

	@Override
	protected String getDialogSettingsName() {
		return getWizard().getClass().getName() + "." + DIALOG_SETTINGS_SECTION; //$NON-NLS-1$
	}

	@Override
	protected int getColumnWidth(int index) {
		return tableViewer.getTable().getColumn(index).getWidth();
	}

	void updateSelection() {
		setPageComplete(tableViewer.getCheckedElements().length > 0);
		getProvisioningWizard().operationSelectionsChanged(this);
	}
}
