package bndtools.editor.project;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;

import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.bndtools.utils.osgi.BundleUtils;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;
import org.osgi.util.function.Consumer;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;

import aQute.bnd.build.Workspace;
import aQute.bnd.service.RepositoryPlugin;
import aQute.bnd.version.Version;
import bndtools.Plugin;
import bndtools.central.Central;
import bndtools.model.repo.LoadingContentElement;

public class OSGiFrameworkContentProvider implements IStructuredContentProvider {
	private static final ILogger				logger		= Logger.getLogger(OSGiFrameworkContentProvider.class);

	List<OSGiFramework>							frameworks	= new ArrayList<>();
	private final Deferred<List<OSGiFramework>>	contentReadyQueue;

	public OSGiFrameworkContentProvider() {
		contentReadyQueue = Central.promiseFactory()
			.deferred();
	}

	private StructuredViewer	structuredViewer;
	private Workspace			workspace;
	private LoadingContentJob	loadingJob;

	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		frameworks.clear();
		loadingJob = null;
		workspace = (Workspace) newInput;
		structuredViewer = (StructuredViewer) viewer;
	}

	private IStatus refreshProviders() {
		List<IStatus> statuses = new ArrayList<>();

		IConfigurationElement[] configElements = Platform.getExtensionRegistry()
			.getConfigurationElementsFor(Plugin.PLUGIN_ID, "osgiFrameworks");

		for (IConfigurationElement element : configElements) {
			String frameworkName = element.getAttribute("name");
			String bsn = element.getAttribute("bsn");

			URL iconUrl = null;
			String iconPath = element.getAttribute("icon");
			if (iconPath != null) {
				Bundle contributorBundle = BundleUtils.findBundle(Plugin.getDefault()
					.getBundleContext(),
					element.getContributor()
						.getName(),
					null);
				if (contributorBundle != null)
					iconUrl = contributorBundle.getEntry(iconPath);
			}

			List<RepositoryPlugin> repositories;
			try {
				repositories = (workspace != null) ? workspace.getRepositories()
					: Collections.<RepositoryPlugin> emptyList();
			} catch (Exception e) {
				return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, e.getMessage(), e);
			}

			for (RepositoryPlugin repo : repositories) {
				try {
					SortedSet<Version> versions = repo.versions(bsn);
					if (versions != null)
						for (Version version : versions) {
							try {
								File framework = repo.get(bsn, version, null);
								if (framework != null)
									frameworks.add(new OSGiFramework(frameworkName, bsn, version, iconUrl));
							} catch (Exception e) {
								String msg = String.format(
									"Error finding repository entry for OSGi framework %s, version %s.", bsn,
									version.toString());
								logger.logError(msg, e);
								statuses.add(new Status(IStatus.ERROR, Plugin.PLUGIN_ID, msg, e));
							}
						}
				} catch (Exception e) {
					String msg = String.format("Error searching repository for OSGi framework %s.", bsn);
					logger.logError(msg, e);
					statuses.add(new Status(IStatus.ERROR, Plugin.PLUGIN_ID, msg, e));
				}
			}
		}

		Display.getDefault()
			.asyncExec(() -> structuredViewer.refresh(true));

		if (statuses.size() > 0) {
			return new MultiStatus(Plugin.PLUGIN_ID, IStatus.ERROR, statuses.toArray(new IStatus[0]),
				"Errors while refreshing OSGi framework providers.", null);
		}

		return Status.OK_STATUS;
	}

	@Override
	public void dispose() {}

	@Override
	public Object[] getElements(Object inputElement) {
		if (frameworks.isEmpty()) {
			if (loadingJob == null) {
				loadingJob = new LoadingContentJob("Refreshing OSGi Framework content...") {
					@Override
					protected IStatus run(IProgressMonitor monitor) {
						IStatus status = refreshProviders();

						contentReadyQueue.resolve(frameworks);

						return status;
					}
				};
				loadingJob.schedule();
			}

			return loadingJob.getLoadingContent();
		}

		return frameworks.toArray();
	}

	public void onContentReady(Consumer<List<OSGiFramework>> callback) {
		Promise<List<OSGiFramework>> p = contentReadyQueue.getPromise();
		p.thenAccept(callback)
			.onFailure(fail -> logger.logError("onContentReady callback failed", fail));
	}

	private static abstract class LoadingContentJob extends Job {

		public LoadingContentJob(String name) {
			super(name);
		}

		public Object[] getLoadingContent() {
			return loadingContent;
		}

		private final Object[] loadingContent = new Object[] {
			new LoadingContentElement()
		};

	}
}
