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<ILaunchConfiguration>();
        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<ILaunchConfiguration>(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;
    }

}
