/*******************************************************************************
 * Copyright (c) 2013, 2018 Red Hat, Inc. 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:
 *     Red Hat, Inc. - initial API and implementation
 *     Karsten Thoms, itemis - Bug#448789
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.dialogs;

import java.util.*;
import java.util.List;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.ui.*;
import org.eclipse.equinox.internal.p2.ui.model.*;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.operations.*;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;

public class RemediationGroup {
	final int ALLOWPARTIALINSTALL_INDEX = 0;
	final int ALLOWDIFFERENTVERSION_INDEX = 1;
	final int ALLOWINSTALLEDUPDATE_INDEX = 2;
	final int ALLOWINSTALLEDREMOVAL_INDEX = 3;

	private RemediationOperation remediationOperation;
	Composite remediationComposite;
	private Button bestBeingInstalledRelaxedButton;
	private Button bestInstalledRelaxedButton;
	Button buildMyOwnSolution;
	final ArrayList<Button> checkboxes = new ArrayList<>();
	private Composite resultFoundComposite;
	private Composite resultComposite;
	private Composite resultNotFoundComposite;
	private Composite resultErrorComposite;
	private Remedy currentRemedy;

	private TreeViewer treeViewer;
	private TreeViewerComparator treeComparator;
	protected IUElementListRoot input;
	private StackLayout switchRemediationLayout;
	Group detailsControl;
	Text detailStatusText;
	Composite checkBoxesComposite;
	private IUDetailsGroup iuDetailsGroup;

	HashMap<String, String[]> CONSTRAINTS;
	private WizardPage containerPage;

	public class RemedyContentProvider implements ITreeContentProvider {

		@Override
		public void dispose() {
			// not needed
		}

		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			// not needed
		}

		@Override
		public Object[] getElements(Object inputElement) {
			Object[] elements = ElementUtils.requestToRemedyElementsCategories((Remedy) inputElement);
			return elements;
		}

		@Override
		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof RemedyElementCategory) {
				RemedyElementCategory category = (RemedyElementCategory) parentElement;
				return category.getElements().toArray();
			}
			return null;
		}

		@Override
		public Object getParent(Object element) {
			return null;
		}

		@Override
		public boolean hasChildren(Object element) {
			if (element instanceof RemedyElementCategory) {
				return true;
			}
			return false;
		}
	}

	public RemediationGroup(WizardPage page) {
		CONSTRAINTS = new HashMap<>();
		CONSTRAINTS.put(ProvUIMessages.RemediationPage_BeingInstalledSection, new String[] {ProvUIMessages.RemediationPage_BeingInstalledSection_AllowPartialInstall, ProvUIMessages.RemediationPage_BeingInstalledSection_AllowDifferentVersion});
		CONSTRAINTS.put(ProvUIMessages.RemediationPage_InstalledSection, new String[] {ProvUIMessages.RemediationPage_InstalledSection_AllowInstalledUpdate, ProvUIMessages.RemediationPage_InstalledSection_AllowInstalledRemoval});

		containerPage = page;
	}

	public Composite getComposite() {
		return remediationComposite;
	}

	public void createRemediationControl(Composite container) {
		remediationComposite = new Composite(container, SWT.NONE);
		remediationComposite.setLayout(new GridLayout());
		Listener solutionslistener;

		Label descriptionLabel = new Label(remediationComposite, SWT.NONE);
		descriptionLabel.setText(ProvUIMessages.RemediationPage_SubDescription);

		solutionslistener = e -> {
			Button btn = (Button) e.widget;
			Remedy remedy = (btn.getData() == null ? null : (Remedy) btn.getData());
			checkboxes.get(ALLOWPARTIALINSTALL_INDEX).setSelection(remedy != null && remedy.getConfig().allowPartialInstall);
			checkboxes.get(ALLOWDIFFERENTVERSION_INDEX).setSelection(remedy != null && remedy.getConfig().allowDifferentVersion);
			checkboxes.get(ALLOWINSTALLEDUPDATE_INDEX).setSelection(remedy != null && remedy.getConfig().allowInstalledUpdate);
			checkboxes.get(ALLOWINSTALLEDREMOVAL_INDEX).setSelection(remedy != null && remedy.getConfig().allowInstalledRemoval);
			for (Button btn1 : checkboxes) {
				btn1.setVisible(true);
			}
			if (btn == buildMyOwnSolution && btn.getSelection()) {
				checkBoxesComposite.setVisible(true);
				((GridData) checkBoxesComposite.getLayoutData()).exclude = false;
			} else {
				checkBoxesComposite.setVisible(false);
				((GridData) checkBoxesComposite.getLayoutData()).exclude = true;
			}
			currentRemedy = searchRemedyMatchingUserChoices();
			refreshResultComposite();
			remediationComposite.layout(false);
		};

		bestBeingInstalledRelaxedButton = new Button(remediationComposite, SWT.RADIO);
		bestBeingInstalledRelaxedButton.setText(ProvUIMessages.RemediationPage_BestSolutionBeingInstalledRelaxed);
		bestBeingInstalledRelaxedButton.addListener(SWT.Selection, solutionslistener);

		bestInstalledRelaxedButton = new Button(remediationComposite, SWT.RADIO);
		bestInstalledRelaxedButton.setText(ProvUIMessages.RemediationPage_BestSolutionInstallationRelaxed);
		bestInstalledRelaxedButton.addListener(SWT.Selection, solutionslistener);

		buildMyOwnSolution = new Button(remediationComposite, SWT.RADIO);
		buildMyOwnSolution.setText(ProvUIMessages.RemediationPage_BestSolutionBuilt);
		buildMyOwnSolution.addListener(SWT.Selection, solutionslistener);

		Listener checkboxListener = e -> {
			currentRemedy = searchRemedyMatchingUserChoices();
			refreshResultComposite();
		};
		checkBoxesComposite = new Composite(remediationComposite, SWT.NONE);
		checkBoxesComposite.setLayout(new GridLayout(1, false));
		GridData data = new GridData();
		data.exclude = false;
		data.horizontalAlignment = SWT.FILL;
		checkBoxesComposite.setLayoutData(data);

		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
		gd.horizontalIndent = 30;
		Iterator<String> iter = CONSTRAINTS.keySet().iterator();
		while (iter.hasNext()) {
			String key = iter.next();
			String[] values = CONSTRAINTS.get(key);
			for (String value : values) {
				Button checkBtn = new Button(checkBoxesComposite, SWT.CHECK);
				checkBtn.setText(value);
				checkBtn.setData(value);
				checkBtn.setLayoutData(gd);
				checkBtn.addListener(SWT.Selection, checkboxListener);
				checkboxes.add(checkBtn);
			}

		}

		resultComposite = new Composite(remediationComposite, SWT.NONE);
		// GridLayoutFactory.fillDefaults().numColumns(1).applyTo(resultComposite);
		switchRemediationLayout = new StackLayout();
		resultComposite.setLayout(switchRemediationLayout);
		GridData data1 = new GridData(GridData.FILL_BOTH);
		resultComposite.setLayoutData(data1);

		resultErrorComposite = new Composite(resultComposite, SWT.NONE);
		resultErrorComposite.setLayout(new GridLayout());
		resultErrorComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		resultNotFoundComposite = new Composite(resultComposite, SWT.NONE);
		resultNotFoundComposite.setLayout(new GridLayout());
		Label resultNotFoundLabel = new Label(resultNotFoundComposite, SWT.NONE);
		resultNotFoundLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		resultNotFoundLabel.setText(ProvUIMessages.RemediationPage_NoSolutionFound);

		resultFoundComposite = new Composite(resultComposite, SWT.NONE);
		resultFoundComposite.setLayout(new GridLayout());

		Group insideFoundComposite = new Group(resultFoundComposite, SWT.NONE);
		insideFoundComposite.setText(ProvUIMessages.RemediationPage_SolutionDetails);
		GridLayout gridLayout = new GridLayout();
		gridLayout.marginWidth = 0;
		gridLayout.marginHeight = 0;
		insideFoundComposite.setLayout(gridLayout);
		insideFoundComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		treeViewer = new TreeViewer(insideFoundComposite, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
		ColumnViewerToolTipSupport.enableFor(treeViewer);
		data = new GridData(GridData.FILL_BOTH);
		Tree tree = treeViewer.getTree();
		tree.setLayoutData(data);
		tree.setHeaderVisible(true);
		TreeViewerColumn nameColumn = new TreeViewerColumn(treeViewer, SWT.LEFT);
		nameColumn.getColumn().setText(ProvUIMessages.ProvUI_NameColumnTitle);
		nameColumn.getColumn().setWidth(400);
		nameColumn.getColumn().setMoveable(true);
		nameColumn.getColumn().addSelectionListener(columnChangeListener(0));
		nameColumn.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				if (element instanceof RemedyElementCategory)
					return ((RemedyElementCategory) element).getName();
				if (element instanceof RemedyIUDetail) {
					RemedyIUDetail iu = (RemedyIUDetail) element;
					String label = iu.getIu().getProperty(IInstallableUnit.PROP_NAME, null);
					if (label == null)
						label = iu.getIu().getId();
					return label;
				}
				return super.getText(element);
			}

			@Override
			public Image getImage(Object element) {
				if (element instanceof RemedyElementCategory) {
					RemedyElementCategory category = (RemedyElementCategory) element;
					if (category.getName().equals(ProvUIMessages.RemedyCategoryAdded))
						return ProvUIImages.getImage(ProvUIImages.IMG_ADDED);
					if (category.getName().equals(ProvUIMessages.RemedyCategoryChanged))
						return ProvUIImages.getImage(ProvUIImages.IMG_CHANGED);
					if (category.getName().equals(ProvUIMessages.RemedyCategoryNotAdded))
						return ProvUIImages.getImage(ProvUIImages.IMG_NOTADDED);
					if (category.getName().equals(ProvUIMessages.RemedyCategoryRemoved))
						return ProvUIImages.getImage(ProvUIImages.IMG_REMOVED);
				} else if (element instanceof RemedyIUDetail) {
					RemedyIUDetail iuDetail = (RemedyIUDetail) element;
					int status = compare(iuDetail);
					if (compare(iuDetail.getBeingInstalledVersion(), iuDetail.getRequestedVersion()) < 0 && containerPage != null && containerPage.getWizard() instanceof UpdateWizard) {
						Image img = ProvUIImages.getImage(ProvUIImages.IMG_UPGRADED_IU);
						ImageDescriptor overlay = ProvUIActivator.getDefault().getImageRegistry().getDescriptor(ProvUIImages.IMG_INFO);
						String decoratedImageId = ProvUIImages.IMG_UPGRADED_IU.concat(ProvUIImages.IMG_INFO);
						if (ProvUIActivator.getDefault().getImageRegistry().get(decoratedImageId) == null) {
							DecorationOverlayIcon decoratedImage = new DecorationOverlayIcon(img, overlay, IDecoration.BOTTOM_RIGHT);
							ProvUIActivator.getDefault().getImageRegistry().put(decoratedImageId, decoratedImage);
						}
						Image decoratedImg = ProvUIActivator.getDefault().getImageRegistry().get(decoratedImageId);
						return decoratedImg;
					}

					if (status < 0)
						return ProvUIImages.getImage(ProvUIImages.IMG_DOWNGRADED_IU);
					if (status > 0)
						return ProvUIImages.getImage(ProvUIImages.IMG_UPGRADED_IU);
					return ProvUIImages.getImage(ProvUIImages.IMG_IU);
				}
				return super.getImage(element);
			}

			@Override
			public String getToolTipText(Object element) {
				if (element instanceof RemedyIUDetail) {
					RemedyIUDetail iuDetail = (RemedyIUDetail) element;
					String toolTipText = ""; //$NON-NLS-1$
					List<String> versions = new ArrayList<>();
					if (iuDetail.getInstalledVersion() != null)
						versions.add(ProvUIMessages.RemedyElementInstalledVersion + iuDetail.getInstalledVersion().toString());
					if (iuDetail.getRequestedVersion() != null)
						versions.add(ProvUIMessages.RemedyElementRequestedVersion + iuDetail.getRequestedVersion().toString());
					if (iuDetail.getBeingInstalledVersion() != null)
						versions.add(ProvUIMessages.RemedyElementBeingInstalledVersion + iuDetail.getBeingInstalledVersion().toString());
					for (String version : versions) {
						toolTipText += (toolTipText == "" ? "" : "\n") + version; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
					if (containerPage != null && containerPage.getWizard() instanceof UpdateWizard && compare(iuDetail.getBeingInstalledVersion(), iuDetail.getRequestedVersion()) < 0)
						toolTipText = ProvUIMessages.RemedyElementNotHighestVersion + "\n\n" + toolTipText; //$NON-NLS-1$
					return toolTipText;
				}
				return super.getToolTipText(element);
			}

			private int compare(Version versionA, Version versionB) {
				if (versionA != null && versionB != null)
					return versionA.compareTo(versionB);
				return 0;
			}

			private int compare(RemedyIUDetail iuDetail) {
				if (iuDetail.getStatus() == RemedyIUDetail.STATUS_ADDED && iuDetail.getRequestedVersion() != null && iuDetail.getBeingInstalledVersion() != null) {
					return compare(iuDetail.getBeingInstalledVersion(), iuDetail.getRequestedVersion());
				}
				if (iuDetail.getStatus() == RemedyIUDetail.STATUS_CHANGED && iuDetail.getInstalledVersion() != null && iuDetail.getBeingInstalledVersion() != null) {
					return compare(iuDetail.getBeingInstalledVersion(), iuDetail.getInstalledVersion());
				}
				return 0;
			}
		});
		TreeViewerColumn versionColumn = new TreeViewerColumn(treeViewer, SWT.LEFT);
		versionColumn.getColumn().setText(ProvUIMessages.ProvUI_VersionColumnTitle);
		versionColumn.getColumn().setWidth(200);
		versionColumn.getColumn().addSelectionListener(columnChangeListener(1));
		versionColumn.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				if (element instanceof RemedyIUDetail) {
					RemedyIUDetail iu = (RemedyIUDetail) element;
					if (iu.getBeingInstalledVersion() != null)
						return iu.getBeingInstalledVersion().toString();
				}
				return ""; //$NON-NLS-1$
			}
		});
		TreeViewerColumn idColumn = new TreeViewerColumn(treeViewer, SWT.LEFT);
		idColumn.getColumn().setText(ProvUIMessages.ProvUI_IdColumnTitle);
		idColumn.getColumn().setWidth(200);
		idColumn.getColumn().addSelectionListener(columnChangeListener(2));

		idColumn.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				if (element instanceof RemedyIUDetail) {
					RemedyIUDetail iu = (RemedyIUDetail) element;
					return iu.getIu().getId();
				}
				return ""; //$NON-NLS-1$
			}
		});

		treeComparator = new TreeViewerComparator();
		treeViewer.setComparator(treeComparator);
		treeViewer.setContentProvider(new RemedyContentProvider());
		treeViewer.setAutoExpandLevel(2);
		iuDetailsGroup = new IUDetailsGroup(resultErrorComposite, treeViewer, 500, true);
	}

	public IUDetailsGroup getDetailsGroup() {
		return iuDetailsGroup;
	}

	private Remedy searchBestDefaultRemedy() {
		if (remediationOperation.bestSolutionChangingTheRequest() != null) {
			return remediationOperation.bestSolutionChangingTheRequest();
		}
		if (remediationOperation.bestSolutionChangingWhatIsInstalled() != null) {
			return remediationOperation.bestSolutionChangingWhatIsInstalled();
		}
		return remediationOperation.getRemedies().get(0);
	}

	public void update(RemediationOperation operation) {
		Assert.isNotNull(operation, "operation"); //$NON-NLS-1$
		this.remediationOperation = operation;
		currentRemedy = searchBestDefaultRemedy();

		bestBeingInstalledRelaxedButton.setData(remediationOperation.bestSolutionChangingTheRequest());
		bestInstalledRelaxedButton.setData(remediationOperation.bestSolutionChangingWhatIsInstalled());

		bestBeingInstalledRelaxedButton.setEnabled(remediationOperation.bestSolutionChangingTheRequest() != null);
		bestInstalledRelaxedButton.setEnabled(remediationOperation.bestSolutionChangingWhatIsInstalled() != null);
		bestBeingInstalledRelaxedButton.setSelection(false);
		bestInstalledRelaxedButton.setSelection(false);
		buildMyOwnSolution.setSelection(false);

		if (currentRemedy == remediationOperation.bestSolutionChangingTheRequest()) {
			bestBeingInstalledRelaxedButton.setSelection(true);
			bestBeingInstalledRelaxedButton.notifyListeners(SWT.Selection, new Event());
		} else if (currentRemedy == remediationOperation.bestSolutionChangingWhatIsInstalled()) {
			bestInstalledRelaxedButton.setSelection(true);
			bestInstalledRelaxedButton.notifyListeners(SWT.Selection, new Event());
		} else {
			buildMyOwnSolution.setData(currentRemedy);
			buildMyOwnSolution.setSelection(true);
			buildMyOwnSolution.notifyListeners(SWT.Selection, new Event());
		}
	}

	private boolean isContraintOK(int btnIndex, boolean value) {
		return (checkboxes.get(btnIndex).getSelection() && value) || (!checkboxes.get(btnIndex).getSelection() && !value);
	}

	Remedy searchRemedyMatchingUserChoices() {
		if (remediationOperation == null) {
			ProvUIActivator.getDefault().getLog().log(new Status(IStatus.ERROR, ProvUIActivator.getContext().getBundle().getSymbolicName(), "RemediationOperation was not initialized yet.")); //$NON-NLS-1$
			return null;
		}
		for (Remedy remedy : remediationOperation.getRemedies()) {
			if (isContraintOK(ALLOWPARTIALINSTALL_INDEX, remedy.getConfig().allowPartialInstall) && isContraintOK(ALLOWDIFFERENTVERSION_INDEX, remedy.getConfig().allowDifferentVersion) && isContraintOK(ALLOWINSTALLEDUPDATE_INDEX, remedy.getConfig().allowInstalledUpdate) && isContraintOK(ALLOWINSTALLEDREMOVAL_INDEX, remedy.getConfig().allowInstalledRemoval)) {
				if (remedy.getRequest() != null) {
					return remedy;
				}
			}
		}
		return null;
	}

	void refreshResultComposite() {
		resultComposite.setVisible(true);
		if (!checkboxes.get(ALLOWPARTIALINSTALL_INDEX).getSelection() && !checkboxes.get(ALLOWDIFFERENTVERSION_INDEX).getSelection() && !checkboxes.get(ALLOWINSTALLEDUPDATE_INDEX).getSelection() && !checkboxes.get(ALLOWINSTALLEDREMOVAL_INDEX).getSelection()) {
			switchRemediationLayout.topControl = resultErrorComposite;
		} else {

			if (currentRemedy == null) {
				switchRemediationLayout.topControl = resultNotFoundComposite;
			} else {
				if (currentRemedy.getIusDetails().size() == 0) {
					switchRemediationLayout.topControl = resultNotFoundComposite;
				} else {
					treeViewer.setInput(currentRemedy);
					switchRemediationLayout.topControl = resultFoundComposite;
				}
			}
		}
		resultComposite.layout();
		containerPage.setPageComplete(currentRemedy != null);
	}

	public Remedy getCurrentRemedy() {
		return currentRemedy;
	}

	public String getMessage() {
		return ProvUIMessages.InstallRemediationPage_Description;
	}

	class TreeViewerComparator extends ViewerComparator {
		private int sortColumn = 0;
		private int ascending = 1;

		@Override
		public int compare(Viewer viewer1, Object e1, Object e2) {
			if (!(e1 instanceof RemedyIUDetail && e2 instanceof RemedyIUDetail))
				return 0;
			IInstallableUnit iu1 = ((RemedyIUDetail) e1).getIu();
			IInstallableUnit iu2 = ((RemedyIUDetail) e2).getIu();
			if (iu1 != null && iu2 != null) {
				if (viewer1 instanceof TreeViewer) {
					TreeViewer theTreeViewer = (TreeViewer) viewer1;
					ColumnLabelProvider labelProvider = (ColumnLabelProvider) theTreeViewer.getLabelProvider(sortColumn);
					String e1p = labelProvider.getText(e1);
					String e2p = labelProvider.getText(e2);
					// don't suppress this warning as it will cause build-time warning
					// see bug 423628. This should be possible to fix once
					// SWT/JFace adopts generics
					return ascending * getComparator().compare(e1p, e2p);
				}
				return 0;
			}
			return super.compare(viewer1, e1, e2);
		}

		public void setSortColumn(int sortColumn) {
			this.sortColumn = sortColumn;
		}

		public int getSortColumn() {
			return sortColumn;
		}

		public boolean isAscending() {
			return ascending == 1 ? true : false;
		}

		public void setAscending(boolean asc) {
			this.ascending = asc ? 1 : -1;
		}
	}

	private SelectionListener columnChangeListener(final int index) {
		return SelectionListener.widgetSelectedAdapter(e -> updateTableSorting(index));
	}

	private void updateTableSorting(int columnIndex) {
		TreeViewerComparator comparator = (TreeViewerComparator) treeViewer.getComparator();
		// toggle direction if it's the same column
		if (columnIndex == treeComparator.getSortColumn()) {
			comparator.setAscending(!treeComparator.isAscending());
		}
		comparator.setSortColumn(columnIndex);
		treeViewer.getTree().setSortColumn(treeViewer.getTree().getColumn(columnIndex));
		treeViewer.getTree().setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
		treeViewer.refresh(false);
		treeViewer.expandToLevel(2);
	}

}
