package bndtools.m2e;

import static java.util.stream.Collectors.toList;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

import org.apache.maven.lifecycle.MavenExecutionPlan;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;

import aQute.bnd.build.Run;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.maven.lib.configuration.Bndruns;

public interface MavenRunListenerHelper {

	// IMaven maven = MavenPlugin.getMaven();
	// IMavenProjectRegistry mavenProjectRegistry =
	// MavenPlugin.getMavenProjectRegistry();
	// IWorkspace iWorkspace = ResourcesPlugin.getWorkspace();

	static IResource getResource(Run run) {
		File propertiesFile = run.getPropertiesFile();
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		IWorkspaceRoot root = workspace.getRoot();
		IFile[] locations = root.findFilesForLocationURI(propertiesFile.toURI());
		IFile shortest = null;
		for (IFile f : locations) {
			if (shortest == null || (f.getProjectRelativePath()
				.segmentCount() < shortest.getProjectRelativePath()
					.segmentCount())) {
				shortest = f;
			}
		}
		return shortest;
	}

	static MavenProject getMavenProject(IMavenProjectFacade mavenProjectFacade) {
		try {
			return mavenProjectFacade.getMavenProject(new NullProgressMonitor());
		} catch (CoreException e) {
			throw Exceptions.duck(e);
		}
	}

	static IMavenProjectFacade getMavenProjectFacade(IMavenProjectRegistry mavenProjectRegistry, IResource resource) {
		return mavenProjectRegistry.getProject(resource.getProject());
	}

	static boolean isMavenProject(IMavenProjectRegistry mavenProjectRegistry, IResource resource) {
		if ((resource != null) && (resource.getProject() != null)
			&& (getMavenProjectFacade(mavenProjectRegistry, resource) != null)) {
			return true;
		}

		return false;
	}

	static boolean hasBndMavenPlugin(IMavenProjectFacade projectFacade) throws CoreException {
		return getBndMavenPlugin(projectFacade).isPresent();
	}

	static Optional<PluginExecution> getBndMavenPlugin(IMavenProjectFacade projectFacade) throws CoreException {
		return getBndMavenPluginById(projectFacade, "biz.aQute.bnd:bnd-maven-plugin");
	}

	static boolean hasBndResolverMavenPlugin(IMavenProjectFacade projectFacade) throws CoreException {
		return getBndResolverMavenPlugin(projectFacade).isPresent();
	}

	static Optional<PluginExecution> getBndResolverMavenPlugin(IMavenProjectFacade projectFacade)
		throws CoreException {
		return getBndMavenPluginById(projectFacade, "biz.aQute.bnd:bnd-resolver-maven-plugin");
	}

	static MojoExecution getBndResolverMojoExecution(IMaven maven, IMavenProjectFacade projectFacade,
		IProgressMonitor monitor)
		throws CoreException {
		return getMojoExecution(maven, projectFacade, "biz.aQute.bnd:bnd-resolver-maven-plugin",
			"bnd-resolver:resolve@",
			exe -> true, monitor);
	}

	static MojoExecution getBndResolverMojoExecution(IMaven maven, IMavenProjectFacade projectFacade,
		Predicate<MojoExecution> predicate, IProgressMonitor monitor) throws CoreException {
		return getMojoExecution(maven, projectFacade, "biz.aQute.bnd:bnd-resolver-maven-plugin",
			"bnd-resolver:resolve@",
			predicate, monitor);
	}

	static boolean hasBndTestingMavenPlugin(IMavenProjectFacade projectFacade) throws CoreException {
		return getBndTestingMavenPlugin(projectFacade).isPresent();
	}

	static Optional<PluginExecution> getBndTestingMavenPlugin(IMavenProjectFacade projectFacade)
		throws CoreException {
		return getBndMavenPluginById(projectFacade, "biz.aQute.bnd:bnd-testing-maven-plugin");
	}

	static Optional<PluginExecution> getBndMavenPluginById(IMavenProjectFacade projectFacade, String id)
		throws CoreException {
		return Optional.ofNullable(getMavenProject(projectFacade).getPlugin(id))
			.map(Plugin::getExecutions)
			.orElseGet(ArrayList::new)
			.stream()
			.findFirst();
	}

	static MojoExecution getBndTestingMojoExecution(IMaven maven, IMavenProjectFacade projectFacade,
		IProgressMonitor monitor)
		throws CoreException {
		return getMojoExecution(maven, projectFacade, "biz.aQute.bnd:bnd-testing-maven-plugin", "bnd-testing:testing@",
			exe -> true, monitor);
	}

	static MojoExecution getBndTestingMojoExecution(IMaven maven, IMavenProjectFacade projectFacade,
		Predicate<MojoExecution> predicate, IProgressMonitor monitor) throws CoreException {
		return getMojoExecution(maven, projectFacade, "biz.aQute.bnd:bnd-testing-maven-plugin", "bnd-testing:testing@",
			predicate, monitor);
	}

	static MojoExecution getMojoExecution(IMaven maven, IMavenProjectFacade projectFacade, String pluginId,
		String executionPrefix,
		Predicate<MojoExecution> predicate, IProgressMonitor monitor) throws CoreException {
		MavenProject mavenProject = getMavenProject(projectFacade);
		Plugin plugin = getMavenProject(projectFacade).getPlugin(pluginId);
		if (plugin == null) {
			return null;
		}
		List<String> tasks = plugin
			.getExecutionsAsMap()
			.keySet()
			.stream()
			.map(executionPrefix::concat)
			.collect(toList());

		MavenExecutionPlan plan = maven.calculateExecutionPlan(mavenProject, tasks, true, monitor);
		return plan.getMojoExecutions()
			.stream()
			.filter(predicate)
			.findFirst()
			.orElse(null);
	}

	default String createErrorMessage(String pluginId) {
		StringBuilder builder = new StringBuilder("Maven Plugin ");
		builder.append(pluginId);
		builder.append(" could not be found in your projects maven setup. Please make sure you configured ");
		builder.append(pluginId);
		builder.append(" for the bndrun file you want to resolve. Please consult the ");
		builder.append(pluginId);
		builder.append(" documentation for details.");
		return builder.toString();
	}

	static boolean isOffline(IMaven maven) {
		try {
			return maven.getSettings()
				.isOffline();
		} catch (CoreException e) {
			throw Exceptions.duck(e);
		}
	}

	static <T> T lookupComponent(IMaven maven, Class<T> clazz) {
		try {
			Method lookupComponentMethod = maven.getClass()
				.getMethod("lookup", Class.class);

			return clazz.cast(lookupComponentMethod.invoke(maven, clazz));
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
			| SecurityException e) {
			try {
				Method lookupComponentMethod = maven.getClass()
					.getMethod("lookupComponent", Class.class);

				return clazz.cast(lookupComponentMethod.invoke(maven, clazz));
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
				| NoSuchMethodException | SecurityException e2) {
				return null;
			}
		}
	}

	static boolean containsBndrun(IMaven maven, MojoExecution mojoExecution, MavenProject mavenProject, File bndrunFile,
		IProgressMonitor monitor) {
		try {
			Bndruns bndruns = maven.getMojoParameterValue(mavenProject, mojoExecution, "bndruns", Bndruns.class,
				monitor);

			return bndruns.getFiles(mavenProject.getBasedir())
				.contains(bndrunFile);
		} catch (Exception e) {
			throw Exceptions.duck(e);
		}
	}

}
