package bndtools.launch.api;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.ILaunchShortcut2;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ide.ResourceUtil;

import aQute.bnd.build.Project;
import bndtools.Plugin;
import bndtools.launch.LaunchConstants;

public abstract class AbstractLaunchShortcut implements ILaunchShortcut2 {
	private static final ILogger	logger	= Logger.getLogger(AbstractLaunchShortcut.class);

	private final String			launchId;

	public AbstractLaunchShortcut(String launchId) {
		this.launchId = launchId;
	}

	@Override
	public void launch(ISelection selection, String mode) {
		IStructuredSelection is = (IStructuredSelection) selection;
		int size = is.size();

		if (size == 1 && is.getFirstElement() != null) {
			try {
				Object selected = is.getFirstElement();
				launchSelectedObject(selected, mode);
			} catch (CoreException e) {
				ErrorDialog.openError(null, "Error", null,
					new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Error configuring launch.", e));
			}
		} else if (size > 1) {
			// only support multiple IJavaElements
			List<IJavaElement> elements = new ArrayList<>();
			Iterator<?> iterator = is.iterator();

			while (iterator.hasNext()) {
				Object element = iterator.next();
				if (element instanceof IJavaElement) {
					elements.add((IJavaElement) element);
				}
			}

			if (!elements.isEmpty()) {
				try {
					launchJavaElements(elements, mode);
				} catch (CoreException e) {
					ErrorDialog.openError(null, "Error", null,
						new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Error launching elements.", e));
				}
			}
		}
	}

	protected void launchSelectedObject(Object selected, String mode) throws CoreException {
		if (selected instanceof IJavaElement) {
			launchJavaElements(Collections.singletonList((IJavaElement) selected), mode);
		} else if (selected instanceof IResource && Project.BNDFILE.equals(((IResource) selected).getName())) {
			IProject project = ((IResource) selected).getProject();
			launchProject(project, mode);
		} else if (selected instanceof IFile && ((IFile) selected).getName()
			.endsWith(LaunchConstants.EXT_BNDRUN)) {
			IFile bndRunFile = (IFile) selected;
			launchBndRun(bndRunFile, mode);
		} else if (selected instanceof IAdaptable) {
			IAdaptable adaptable = (IAdaptable) selected;
			IJavaElement javaElement = adaptable.getAdapter(IJavaElement.class);
			if (javaElement != null) {
				launchJavaElements(Collections.singletonList(javaElement), mode);
			} else {
				IResource resource = adaptable.getAdapter(IResource.class);
				if (resource != null && resource != selected)
					launchSelectedObject(resource, mode);
			}
		}
	}

	@Override
	public void launch(IEditorPart editor, String mode) {
		IEditorInput input = editor.getEditorInput();
		IJavaElement element = input.getAdapter(IJavaElement.class);
		if (element != null) {
			IJavaProject jproject = element.getJavaProject();
			if (jproject != null) {
				launch(jproject.getProject()
					.getFullPath(), jproject.getProject(), mode);
			}
		} else {
			IFile file = ResourceUtil.getFile(input);
			if (file != null) {
				if (file.getName()
					.endsWith(LaunchConstants.EXT_BNDRUN)) {
					launch(file.getFullPath(), file.getProject(), mode);
				} else if (file.getName()
					.equals(Project.BNDFILE)) {
					launch(file.getProject()
						.getFullPath(), file.getProject(), mode);
				}
			}
		}
	}

	@SuppressWarnings("unused")
	protected void launchJavaElements(List<IJavaElement> elements, String mode) throws CoreException {
		IProject targetProject = elements.get(0)
			.getJavaProject()
			.getProject();
		launch(targetProject.getFullPath(), targetProject, mode);
	}

	protected void launchProject(IProject project, String mode) {
		launch(project.getFullPath(), project, mode);
	}

	protected void launchBndRun(IFile bndRunFile, String mode) {
		launch(bndRunFile.getFullPath(), bndRunFile.getProject(), mode);
	}

	protected void launch(IPath targetPath, IProject targetProject, String mode) {
		IPath tp = targetPath.makeRelative();
		try {
			ILaunchConfiguration config = findLaunchConfig(tp);
			if (config == null) {
				ILaunchConfigurationWorkingCopy wc = createConfiguration(tp, targetProject);
				config = wc.doSave();
			}
			DebugUITools.launch(config, mode);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	protected ILaunchConfiguration findLaunchConfig(IPath targetPath) throws CoreException {
		List<ILaunchConfiguration> candidateConfigs = new ArrayList<>();
		ILaunchManager manager = DebugPlugin.getDefault()
			.getLaunchManager();

		ILaunchConfigurationType configType = manager.getLaunchConfigurationType(launchId);
		ILaunchConfiguration[] configs = manager.getLaunchConfigurations(configType);

		for (int i = 0; i < configs.length; i++) {
			ILaunchConfiguration config = configs[i];
			String configTargetName = config.getAttribute(LaunchConstants.ATTR_LAUNCH_TARGET, (String) null);
			if (configTargetName != null && configTargetName.equals(targetPath.toString())) {
				candidateConfigs.add(config);
			}
		}

		// Return the latest (last in the list)
		return !candidateConfigs.isEmpty() ? candidateConfigs.get(candidateConfigs.size() - 1) : null;
	}

	protected ILaunchConfigurationWorkingCopy createConfiguration(IPath targetPath, IProject targetProject)
		throws CoreException {
		ILaunchManager manager = DebugPlugin.getDefault()
			.getLaunchManager();
		ILaunchConfigurationType configType = manager.getLaunchConfigurationType(launchId);

		ILaunchConfigurationWorkingCopy wc;
		wc = configType.newInstance(null, manager.generateLaunchConfigurationName(targetPath.lastSegment()));
		wc.setAttribute(LaunchConstants.ATTR_LAUNCH_TARGET, targetPath.toString());
		wc.setAttribute(LaunchConstants.ATTR_CLEAN, LaunchConstants.DEFAULT_CLEAN);
		wc.setAttribute(LaunchConstants.ATTR_DYNAMIC_BUNDLES, LaunchConstants.DEFAULT_DYNAMIC_BUNDLES);

		if (targetProject != null) {
			IJavaProject javaProject = JavaCore.create(targetProject);
			if (javaProject.exists()) {
				wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, targetProject.getName());
			}
		}

		return wc;
	}

	protected static IProject getProject(ISelection selection) {
		if (selection instanceof IStructuredSelection) {
			Object element = ((IStructuredSelection) selection).getFirstElement();
			if (element instanceof IResource)
				return ((IResource) element).getProject();
			else if (element instanceof IAdaptable) {
				IResource resource = ((IAdaptable) element).getAdapter(IResource.class);
				if (resource != null)
					return resource.getProject();
			}
		}
		return null;
	}

	protected static IProject getProject(IEditorPart editorPart) {
		IResource resource = ResourceUtil.getResource(editorPart);
		return resource != null ? resource.getProject() : null;
	}

	@Override
	public ILaunchConfiguration[] getLaunchConfigurations(ISelection selection) {
		IProject project = getProject(selection);
		return project != null ? getLaunchConfigsForProject(project) : null;
	}

	protected ILaunchConfiguration[] getLaunchConfigsForProject(@SuppressWarnings("unused") IProject project) {
		ILaunchManager manager = DebugPlugin.getDefault()
			.getLaunchManager();

		ILaunchConfigurationType type = manager.getLaunchConfigurationType(launchId);
		try {
			ILaunchConfiguration[] all = manager.getLaunchConfigurations(type);
			List<ILaunchConfiguration> result = new ArrayList<>(all.length);

			return result.toArray(new ILaunchConfiguration[0]);
		} catch (CoreException e) {
			logger.logError("Error retrieving launch configurations.", e);
			return null;
		}
	}

	@Override
	public ILaunchConfiguration[] getLaunchConfigurations(IEditorPart editorPart) {
		IProject project = getProject(editorPart);
		return project != null ? getLaunchConfigsForProject(project) : null;
	}

	@Override
	public IResource getLaunchableResource(ISelection selection) {
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection structSel = (IStructuredSelection) selection;
			if (structSel.size() == 1) {
				Object selected = structSel.getFirstElement();
				if (!(selected instanceof IJavaElement) && selected instanceof IAdaptable) {
					selected = ((IAdaptable) selected).getAdapter(IJavaElement.class);
				}
				if (selected instanceof IJavaElement) {
					return ((IJavaElement) selected).getResource();
				}
			}
		}
		return null;
	}

	@Override
	public IResource getLaunchableResource(IEditorPart editorPart) {
		ITypeRoot element = JavaUI.getEditorInputTypeRoot(editorPart.getEditorInput());
		if (element != null) {
			try {
				return element.getCorrespondingResource();
			} catch (JavaModelException e) {}
		}
		return null;
	}

}
