/*******************************************************************************
 * Copyright (c) 2009, 2017 Tasktop Technologies 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.discovery.compatibility;

import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.discovery.Catalog;
import org.eclipse.equinox.internal.p2.discovery.DiscoveryCore;
import org.eclipse.equinox.internal.p2.discovery.compatibility.util.TransportUtil;
import org.eclipse.equinox.internal.p2.discovery.model.CatalogItem;
import org.eclipse.osgi.util.NLS;

/**
 * Verifies if site URIs point to valid P2 repositories.
 * 
 * @author David Green
 * @author Steffen Pingel
 */
public class SiteVerifier {

	private final Catalog catalog;

	public SiteVerifier(Catalog catalog) {
		this.catalog = catalog;
	}

	/**
	 * Determine update site availability. This may be performed automatically as part of discovery when
	 * CatalogConfiguration#isVerifyUpdateSiteAvailability() is true, or it may be invoked later by calling this method.
	 */
	public void verifySiteAvailability(IProgressMonitor monitor) {
		// NOTE: we don't put java.net.URLs in the map since it involves DNS activity when
		//       computing the hash code.
		Map<String, Collection<CatalogItem>> urlToDescriptors = new HashMap<>();

		for (CatalogItem descriptor : catalog.getItems()) {
			String url = descriptor.getSiteUrl();
			if (url == null) {
				continue;
			}
			if (!url.endsWith("/")) { //$NON-NLS-1$
				url += "/"; //$NON-NLS-1$
			}
			Collection<CatalogItem> collection = urlToDescriptors.get(url);
			if (collection == null) {
				collection = new ArrayList<>();
				urlToDescriptors.put(url, collection);
			}
			collection.add(descriptor);
		}
		final int totalTicks = urlToDescriptors.size();
		monitor.beginTask(Messages.SiteVerifier_Verify_Job_Label, totalTicks);
		try {
			if (!urlToDescriptors.isEmpty()) {
				ExecutorService executorService = Executors.newFixedThreadPool(Math.min(urlToDescriptors.size(), 4));
				try {
					List<Future<VerifyUpdateSiteJob>> futures = new ArrayList<>(urlToDescriptors.size());
					for (String url : urlToDescriptors.keySet()) {
						futures.add(executorService.submit(new VerifyUpdateSiteJob(url)));
					}
					for (Future<VerifyUpdateSiteJob> jobFuture : futures) {
						try {
							for (;;) {
								try {
									VerifyUpdateSiteJob job = jobFuture.get(1L, TimeUnit.SECONDS);

									Collection<CatalogItem> descriptors = urlToDescriptors.get(job.url);
									for (CatalogItem descriptor : descriptors) {
										descriptor.setAvailable(job.ok);
									}
									break;
								} catch (TimeoutException e) {
									if (monitor.isCanceled()) {
										return;
									}
								}
							}
						} catch (InterruptedException e) {
							monitor.setCanceled(true);
							return;
						} catch (ExecutionException e) {
							if (e.getCause() instanceof OperationCanceledException) {
								monitor.setCanceled(true);
								return;
							}
							LogHelper.log(computeStatus(e, Messages.SiteVerifier_Unexpected_Error));
						}
						monitor.worked(1);
					}
				} finally {
					executorService.shutdownNow();
				}
			}
		} finally {
			monitor.done();
		}
	}

	private IStatus computeStatus(ExecutionException e, String message) {
		Throwable cause = e.getCause();
		if (cause.getMessage() != null) {
			message = NLS.bind(Messages.SiteVerifier_Error_with_cause, message, cause.getMessage());
		}
		return new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN, message, e);
	}

	private static class VerifyUpdateSiteJob implements Callable<VerifyUpdateSiteJob> {

		private final String url;

		private boolean ok = false;

		public VerifyUpdateSiteJob(String url) {
			this.url = url;
		}

		@Override
		public VerifyUpdateSiteJob call() throws Exception {
			URL baseUrl = new URL(url);
			List<URI> locations = new ArrayList<>();
			for (String location : new String[] {"content.jar", "content.xml", "site.xml", "compositeContent.jar", "compositeContent.xml"}) { //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
				locations.add(new URL(baseUrl, location).toURI());
			}
			ok = TransportUtil.verifyAvailability(locations, true, new NullProgressMonitor());
			return this;
		}

	}

}
