/*******************************************************************************
 *  Copyright (c) 2007, 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
 *     Sonatype, Inc. - ongoing development
 *     Red Hat, Inc. - support for remediation page
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 479145
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.dialogs;

import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.equinox.internal.p2.ui.*;
import org.eclipse.equinox.internal.p2.ui.model.*;
import org.eclipse.equinox.p2.engine.ProvisioningContext;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.operations.*;
import org.eclipse.equinox.p2.ui.LoadMetadataRepositoryJob;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;

/**
 * An install wizard that allows the users to browse all of the repositories and
 * search/select for items to install.
 *
 * @since 3.6
 */
public class InstallWizard extends WizardWithLicenses {

	SelectableIUsPage errorReportingPage;
	boolean ignoreSelectionChanges = false;
	IStatus installHandlerStatus;

	public InstallWizard(ProvisioningUI ui, InstallOperation operation, Collection<IInstallableUnit> initialSelections,
			LoadMetadataRepositoryJob preloadJob) {
		super(ui, operation, initialSelections == null ? null : initialSelections.toArray(), preloadJob);
		setWindowTitle(ProvUIMessages.InstallIUOperationLabel);
		setDefaultPageImageDescriptor(ProvUIImages.getImageDescriptor(ProvUIImages.WIZARD_BANNER_INSTALL));
	}

	@Override
	protected ResolutionResultsWizardPage createResolutionPage() {
		return new InstallWizardPage(ui, this, root, operation);
	}

	@Override
	protected ISelectableIUsPage createMainPage(IUElementListRoot input, Object[] selections) {
		mainPage = new AvailableIUsPage(ui, this);
		if (selections != null && selections.length > 0)
			mainPage.setCheckedElements(selections);
		return mainPage;

	}

	@Override
	protected void initializeResolutionModelElements(Object[] selectedElements) {
		if (selectedElements == null)
			return;
		root = new IUElementListRoot(ui);
		if (operation instanceof RemediationOperation) {
			AvailableIUElement[] elements = ElementUtils
					.requestToElement(((RemediationOperation) operation).getCurrentRemedy(), true);
			root.setChildren(elements);
			planSelections = elements;
		} else {
			ArrayList<AvailableIUElement> list = new ArrayList<>(selectedElements.length);
			ArrayList<AvailableIUElement> selections = new ArrayList<>(selectedElements.length);
			for (Object selectedElement : selectedElements) {
				IInstallableUnit iu = ElementUtils.getIU(selectedElement);
				if (iu != null) {
					AvailableIUElement element = new AvailableIUElement(root, iu, getProfileId(),
							shouldShowProvisioningPlanChildren());
					list.add(element);
					selections.add(element);
				}
			}
			root.setChildren(list.toArray());
			planSelections = selections.toArray();
		}
	}

	/*
	 * Overridden to dynamically determine which page to get selections from.
	 */
	@Override
	protected Object[] getOperationSelections() {
		return getOperationSelectionsPage().getCheckedIUElements();
	}

	/*
	 * Get the page that is driving operation selections. This is usually the main
	 * page, but it could be error page if there was a resolution error and the user
	 * decides to change selections and try again without going back.
	 */
	protected ISelectableIUsPage getOperationSelectionsPage() {
		IWizardPage page = getContainer().getCurrentPage();
		if (page instanceof ISelectableIUsPage)
			return (ISelectableIUsPage) page;
		// return the main page if we weren't on main or error page
		return mainPage;
	}

	@Override
	protected ProvisioningContext getProvisioningContext() {
		return ((AvailableIUsPage) mainPage).getProvisioningContext();
	}

	@Override
	protected IResolutionErrorReportingPage createErrorReportingPage() {
		if (root == null)
			errorReportingPage = new SelectableIUsPage(ui, this, null, null);
		else
			errorReportingPage = new SelectableIUsPage(ui, this, root, root.getChildren(root));
		errorReportingPage.setTitle(ProvUIMessages.InstallWizardPage_Title);
		errorReportingPage.setDescription(ProvUIMessages.PreselectedIUInstallWizard_Description);
		errorReportingPage.updateStatus(root, operation);
		return errorReportingPage;
	}

	@Override
	protected RemediationPage createRemediationPage() {
		remediationPage = new RemediationPage(ui, this, root, operation);
		return remediationPage;
	}

	@Override
	protected ProfileChangeOperation getProfileChangeOperation(Object[] elements) {
		InstallOperation op = new InstallOperation(ui.getSession(), ElementUtils.elementsToIUs(elements));
		op.setProfileId(getProfileId());
		// op.setRootMarkerKey(getRootMarkerKey());
		return op;
	}

	@Override
	protected boolean shouldUpdateErrorPageModelOnPlanChange() {
		// We don't want the root of the error page to change unless we are on the
		// main page. For example, if we are on the error page, change checkmarks, and
		// resolve again with an error, we wouldn't want the root items to change in the
		// error page.
		return getContainer().getCurrentPage() == mainPage && super.shouldUpdateErrorPageModelOnPlanChange();
	}

	@Override
	protected void planChanged() {
		super.planChanged();
		synchSelections(getOperationSelectionsPage());
	}

	/*
	 * overridden to ensure that the main page selections stay in synch with changes
	 * to the error page.
	 */
	@Override
	public void operationSelectionsChanged(ISelectableIUsPage page) {
		if (ignoreSelectionChanges)
			return;
		super.operationSelectionsChanged(page);
		// If we are on the error page, resolution has failed.
		// Our ability to move on depends on whether the selections have changed.
		// If they are the same selections, then we are not complete until selections
		// are changed.
		if (getOperationSelectionsPage() == errorPage)
			((WizardPage) errorPage).setPageComplete(
					pageSelectionsHaveChanged(errorPage) && errorPage.getCheckedIUElements().length > 0);
		synchSelections(page);
	}

	private void synchSelections(ISelectableIUsPage triggeringPage) {
		// We don't want our programmatic changes to cause all this to happen again
		ignoreSelectionChanges = true;
		try {
			if (triggeringPage == errorReportingPage) {
				mainPage.setCheckedElements(triggeringPage.getCheckedIUElements());
			} else if (triggeringPage == mainPage) {
				errorReportingPage.setCheckedElements(triggeringPage.getCheckedIUElements());
			}
		} finally {
			ignoreSelectionChanges = false;
		}
	}

	/*
	 * Overridden to check whether there are UpdateManager install handlers in the
	 * item to be installed. Operations don't know about this compatibility issue.
	 */
	@Override
	public IStatus getCurrentStatus() {
		IStatus originalStatus = super.getCurrentStatus();
		int sev = originalStatus.getSeverity();
		// Use the previously computed status if the user cancelled or if we were
		// already in error.
		// If we don't have an operation or a plan, we can't check this condition
		// either, so just
		// use the normal status.
		if (sev == IStatus.CANCEL || sev == IStatus.ERROR || operation == null
				|| operation.getProvisioningPlan() == null) {
			return originalStatus;
		}
		// Does the plan require install handler support?
		installHandlerStatus = UpdateManagerCompatibility.getInstallHandlerStatus(operation.getProvisioningPlan());
		if (!installHandlerStatus.isOK()) {
			// Set the status into the wizard. This ensures future calls to this method
			// won't
			// repeat the work (and prompting).
			couldNotResolveStatus = installHandlerStatus;

			// Is the update manager installer present? If so, offer to open it.
			// In either case, the failure will be reported in this wizard.
			if (ProvUI.isUpdateManagerInstallerPresent()) {
				PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
					Shell shell = ProvUI.getDefaultParentShell();
					MessageDialog dialog = new MessageDialog(shell, ProvUIMessages.Policy_RequiresUpdateManagerTitle,
							null, ProvUIMessages.Policy_RequiresUpdateManagerMessage, MessageDialog.WARNING,
							new String[] { ProvUIMessages.LaunchUpdateManagerButton, IDialogConstants.CANCEL_LABEL },
							0);
					if (dialog.open() == 0)
						BusyIndicator.showWhile(shell.getDisplay(), UpdateManagerCompatibility::openInstaller);
				});
			}
			return installHandlerStatus;
		}
		return originalStatus;
	}

	/*
	 * When we've found an install handler, that status trumps anything that the
	 * operation might have determined. We are relying here on the knowledge that
	 * the wizard's couldNotResolveStatus is reset on every new resolution, so that
	 * status only holds the installHandler status when it is the current status.
	 * The installHandlerStatus must be non-OK for it to matter at all.
	 *
	 */
	@Override
	public boolean statusOverridesOperation() {
		return super.statusOverridesOperation() || (installHandlerStatus != null && !installHandlerStatus.isOK()
				&& couldNotResolveStatus == installHandlerStatus);
	}
}
