/*******************************************************************************
 * Copyright (c) 2011, 2017 WindRiver 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:
 *     WindRiver Corporation - initial API and implementation
 *     IBM Corporation - Ongoing development
 *     Ericsson AB (Pascal Rapicault) - Bug 387115 - Allow to export everything
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.importexport.internal.wizard;

import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.List;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.importexport.IUDetail;
import org.eclipse.equinox.internal.p2.importexport.VersionIncompatibleException;
import org.eclipse.equinox.internal.p2.importexport.internal.Messages;
import org.eclipse.equinox.internal.p2.ui.ProvUI;
import org.eclipse.equinox.internal.p2.ui.ProvUIMessages;
import org.eclipse.equinox.internal.p2.ui.dialogs.ISelectableIUsPage;
import org.eclipse.equinox.internal.p2.ui.dialogs.ProvisioningOperationWizard;
import org.eclipse.equinox.internal.p2.ui.viewers.IUColumnConfig;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.engine.ProvisioningContext;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.query.CompoundQueryable;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.dialogs.PatternFilter;

public class ImportPage extends AbstractImportPage implements ISelectableIUsPage {

	class InstallationContentProvider implements ITreeContentProvider {

		@Override
		public void dispose() {
			//
		}

		@Override
		public Object[] getElements(Object inputElement) {
			return (Object[]) inputElement;
		}

		@Override
		public void inputChanged(Viewer viewer1, Object oldInput, Object newInput) {
			//
		}

		@Override
		public Object[] getChildren(Object parentElement) {
			return new Object[0];
		}

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

		@Override
		public boolean hasChildren(Object element) {
			return false;
		}

	}

	class InstallationLabelProvider extends LabelProvider implements ITableLabelProvider, IColorProvider {

		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		@Override
		public String getColumnText(Object element, int columnIndex) {
			IInstallableUnit iu = ((IUDetail) element).getIU();
			switch (columnIndex) {
				case 0 :
					return getIUNameWithDetail(iu);
				case 1 :
					return iu.getVersion().toString();
				case 2 :
					return iu.getId();
				default :
					throw new RuntimeException("Should not happen"); //$NON-NLS-1$
			}

		}

		@Override
		public Color getForeground(Object element) {
			if (hasInstalled(ProvUI.getAdapter(element, IInstallableUnit.class)))
				return Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
			return null;
		}

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

	class P2ImportIUPatternFilter extends PatternFilter {

		boolean checkName, checkVersion, checkId = false;
		String patternString;

		/**
		 * Create a new instance of a AvailableIUPatternFilter 
		 */
		public P2ImportIUPatternFilter(IUColumnConfig[] columnConfig) {
			super();
			for (int i = 0; i < columnConfig.length; i++) {
				int field = columnConfig[i].getColumnType();
				if (field == IUColumnConfig.COLUMN_ID)
					checkId = true;
				else if (field == IUColumnConfig.COLUMN_NAME)
					checkName = true;
				else if (field == IUColumnConfig.COLUMN_VERSION)
					checkVersion = true;
			}

		}

		@Override
		public boolean isElementSelectable(Object element) {
			return element instanceof IUDetail;
		}

		/*
		 * Overridden to remember the pattern string for an optimization
		 * in isParentMatch
		 * (non-Javadoc)
		 * @see org.eclipse.ui.dialogs.PatternFilter#setPattern(java.lang.String)
		 */
		@Override
		public void setPattern(String patternString) {
			super.setPattern(patternString);
			this.patternString = patternString;
		}

		/*
		 * Overridden to avoid getting children unless there is actually
		 * a filter.
		 * (non-Javadoc)
		 * @see org.eclipse.ui.dialogs.PatternFilter#isParentMatch(org.eclipse.jface.viewers.Viewer, java.lang.Object)
		 */
		@Override
		protected boolean isParentMatch(Viewer viewer1, Object element) {
			if (patternString == null || patternString.length() == 0)
				return true;
			return super.isParentMatch(viewer1, element);
		}

		@Override
		protected boolean isLeafMatch(Viewer viewer1, Object element) {
			String text = null;
			if (element instanceof IUDetail) {
				IInstallableUnit iu = ((IUDetail) element).getIU();
				if (checkName) {
					// Get the iu name in the default locale
					text = iu.getProperty(IInstallableUnit.PROP_NAME, null);
					if (text != null && wordMatches(text))
						return true;
				}
				if (checkId || (checkName && text == null)) {
					text = iu.getId();
					if (wordMatches(text)) {
						return true;
					}
				}
				if (checkVersion) {
					text = iu.getVersion().toString();
					if (wordMatches(text))
						return true;
				}
			}
			return false;
		}

	}

	private List<IUDetail> features;
	private final List<URI> loadRepos = new ArrayList<>();
	private final Map<IUDetail, IUDetail[]> newProposedFeature = new HashMap<>();
	private Button contactAll;
	private Button installLatest;
	private String oldDestination;

	public ImportPage(ProvisioningUI ui, ProvisioningOperationWizard wizard) {
		super("importpage", ui, wizard); //$NON-NLS-1$
		setTitle(Messages.ImportPage_TITLE);
		setDescription(Messages.ImportPage_DESCRIPTION);
	}

	@Override
	protected void createContents(Composite composite) {
		createDestinationGroup(composite, false);
		createInstallationTable(composite);
		createAdditionOptions(composite);
	}

	private void createAdditionOptions(Composite parent) {
		Composite composite = new Composite(parent, SWT.BORDER);
		GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));

		contactAll = new Button(composite, SWT.CHECK);
		contactAll.setText(ProvUIMessages.AvailableIUsPage_ResolveAllCheckbox);

		installLatest = new Button(composite, SWT.CHECK);
		installLatest.setText(Messages.ImportPage_InstallLatestVersion);
		installLatest.setSelection(true);
	}

	@Override
	protected ITreeContentProvider getContentProvider() {
		return new InstallationContentProvider();
	}

	@Override
	protected ITableLabelProvider getLabelProvider() {
		return new InstallationLabelProvider();
	}

	@Override
	protected int getBrowseDialogStyle() {
		return SWT.OPEN;
	}

	@Override
	protected String getDestinationLabel() {
		return Messages.ImportPage_DESTINATION_LABEL;
	}

	@Override
	protected String getDialogTitle() {
		return Messages.ImportPage_FILEDIALOG_TITLE;
	}

	@Override
	protected IUDetail[] getInput() {
		return new IUDetail[0];
	}

	@Override
	protected String getInvalidDestinationMessage() {
		return Messages.ImportPage_DEST_ERROR;
	}

	@Override
	protected void giveFocusToDestination() {
		destinationNameField.setFocus();
	}

	@Override
	protected void updatePageCompletion() {
		super.updatePageCompletion();
		if (isPageComplete())
			getProvisioningWizard().operationSelectionsChanged(this);
	}

	@Override
	protected void handleDestinationChanged(String newDestination) {
		if (validateDestinationGroup()) {
			// p2f file is changed, update the cached data
			if (!newDestination.equals(oldDestination)) {
				loadRepos.clear();
				newProposedFeature.clear();
			}
			InputStream input = null;
			try {
				input = new BufferedInputStream(new FileInputStream(getDestinationValue()));
				features = importexportService.importP2F(input);
				contactAll.setSelection(hasEntriesWithoutRepo());
				viewer.setInput(features.toArray(new IUDetail[features.size()]));
				input.close();
			} catch (VersionIncompatibleException e) {
				MessageDialog.openWarning(getShell(), Messages.ImportPage_TITLE, e.getMessage());
			} catch (FileNotFoundException e) {
				MessageDialog.openError(getShell(), Messages.ImportPage_TITLE, Messages.ImportPage_FILENOTFOUND);
			} catch (IOException e) {
				MessageDialog.openError(getShell(), Messages.ImportPage_TITLE, e.getLocalizedMessage());
			}
		} else
			viewer.setInput(null);
		updatePageCompletion();
	}

	private boolean hasEntriesWithoutRepo() {
		for (IUDetail entry : features) {
			if (entry.getReferencedRepositories().size() == 0)
				return true;
		}
		return false;
	}

	@Override
	protected void setDestinationValue(String selectedFileName) {
		oldDestination = getDestinationValue();
		super.setDestinationValue(selectedFileName);
	}

	@Override
	protected boolean validDestination() {
		File target = new File(getDestinationValue());
		return super.validDestination() && target.exists() && target.canRead();
	}

	@Override
	public Object[] getCheckedIUElements() {
		Object[] checked = viewer.getCheckedElements();
		List<IUDetail> checkedFeatures = new ArrayList<>(checked.length);
		for (int i = 0; i < checked.length; i++) {
			IUDetail feature = (IUDetail) checked[i];
			IUDetail[] existingFeatures = newProposedFeature.get(feature);
			if (existingFeatures == null)
				checkedFeatures.add(feature);
			else {
				IUDetail matchPolicy = null;
				for (IUDetail f : existingFeatures) {
					if (matchPolicy == null)
						matchPolicy = f;
					// here use exact match
					else if (matchPolicy.getIU().getVersion().compareTo(f.getIU().getVersion()) < 0) {
						if (installLatest.getSelection())
							matchPolicy = f;
						else
							continue;
					} else
						matchPolicy = f;
				}
				if (matchPolicy != null)
					checkedFeatures.add(matchPolicy);
			}
		}
		return checkedFeatures.toArray(new IUDetail[checkedFeatures.size()]);
	}

	@Override
	public Object[] getSelectedIUElements() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setCheckedElements(Object[] elements) {
		new UnsupportedOperationException();
	}

	public ProvisioningContext getProvisioningContext() {
		if (agent != null) {
			Object[] checked = viewer.getCheckedElements();
			List<URI> referredRepos = new ArrayList<>(checked.length);
			for (Object checkItem : checked) {
				IUDetail feature = (IUDetail) checkItem;
				for (URI uri : feature.getReferencedRepositories()) {
					referredRepos.add(uri);
				}
			}
			ProvisioningContext context = new ProvisioningContext(agent);
			if (!contactAll.getSelection()) {
				context.setArtifactRepositories(referredRepos.toArray(new URI[referredRepos.size()]));
				context.setMetadataRepositories(referredRepos.toArray(new URI[referredRepos.size()]));
			}
			return context;
		}
		return null;
	}

	public boolean hasUnloadedRepo() {
		for (Object checked : viewer.getCheckedElements()) {
			IUDetail feature = (IUDetail) checked;
			for (URI uri : feature.getReferencedRepositories())
				if (!loadRepos.contains(uri))
					return true;
		}
		return false;
	}

	class GetCheckedElement implements Runnable {
		Object[] checkedElements = null;

		@Override
		public void run() {
			checkedElements = viewer.getCheckedElements();
		}
	}

	public Object[] getChecked() {
		if (Display.findDisplay(Thread.currentThread()) != null)
			return viewer.getCheckedElements();
		GetCheckedElement get = new GetCheckedElement();
		Display.getDefault().syncExec(get);
		return get.checkedElements;
	}

	public void recompute(IProgressMonitor monitor) throws InterruptedException {
		SubMonitor sub = SubMonitor.convert(monitor, Messages.ImportPage_QueryFeaturesJob, 1000);
		if (agent != null) {
			IMetadataRepositoryManager metaManager = (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME);
			IArtifactRepositoryManager artifactManager = (IArtifactRepositoryManager) agent.getService(IArtifactRepositoryManager.SERVICE_NAME);
			Object[] checked = getChecked();
			sub.setWorkRemaining(100 * checked.length);
			for (Object item : checked) {
				IUDetail feature = (IUDetail) item;
				if (!newProposedFeature.containsKey(feature)) {
					if (sub.isCanceled())
						throw new InterruptedException();
					SubMonitor sub2 = sub.newChild(100, SubMonitor.SUPPRESS_ALL_LABELS);
					sub2.setWorkRemaining(feature.getReferencedRepositories().size() * 500 + 100);
					List<IRepository<IInstallableUnit>> repos = new ArrayList<>();
					for (URI uri : feature.getReferencedRepositories()) {
						if (!metaManager.contains(uri)) {
							metaManager.addRepository(uri);
						}
						metaManager.setEnabled(uri, true);
						try {
							repos.add(metaManager.loadRepository(uri, sub2.newChild(500)));
						} catch (ProvisionException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (OperationCanceledException e) {
							throw new InterruptedException(e.getLocalizedMessage());
						}
						if (!artifactManager.contains(uri)) {
							artifactManager.addRepository(uri);
						}
						artifactManager.setEnabled(uri, true);
					}
					if (sub2.isCanceled())
						throw new InterruptedException();
					// don't suppress this warning as it will cause build-time warning
					// see bug 423628. Find a way to change the code to not produce
					// the warning.
					Set<IInstallableUnit> result = new CompoundQueryable<IInstallableUnit>(repos.toArray(new IRepository[repos.size()])).query(QueryUtil.createIUQuery(feature.getIU().getId(), new VersionRange(feature.getIU().getVersion(), true, null, false)), sub2.newChild(100)).toSet();
					List<IUDetail> existingFeatures = new ArrayList<>(result.size());
					for (IInstallableUnit iu : result) {
						existingFeatures.add(new IUDetail(iu, feature.getReferencedRepositories()));
					}
					newProposedFeature.put(feature, existingFeatures.toArray(new IUDetail[existingFeatures.size()]));
				} else {
					if (sub.isCanceled())
						throw new InterruptedException();
					sub.worked(100);
				}
			}
		}
	}

	@Override
	protected PatternFilter getPatternFilter() {
		return new P2ImportIUPatternFilter(getColumnConfig());
	}
}