/*******************************************************************************
 * Copyright (c) 2011, 2018 Ericsson
 *
 * All rights reserved. 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:
 *   Francois Chouinard - Initial API and implementation
 *   Bernd Hufmann - Added project creation utility
 *   Patrick Tasse - Refactor resource change listener
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.ui.project.model;

import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;

import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
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.ICoreRunnable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.ui.ITmfUIPreferences;
import org.eclipse.tracecompass.internal.tmf.ui.project.model.TmfProjectModelHelper;
import org.eclipse.tracecompass.tmf.core.TmfProjectNature;
import org.eclipse.tracecompass.tmf.core.io.ResourceUtil;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;

/**
 * Factory class storing TMF tracing projects and creating TMF project model elements.
 * <p>
 * @version 1.0
 * @author Francois Chouinard
 */
public class TmfProjectRegistry implements IResourceChangeListener {

    // Create the singleton instance
    private static final TmfProjectRegistry INSTANCE = new TmfProjectRegistry();

    // The map of project resource to project model elements
    private static final Map<IProject, TmfProjectElement> registry = new HashMap<>();

    private static final Queue<TmfTraceElement> promptQueue = new ArrayDeque<>();

    private TmfProjectRegistry() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
        TmfSignalManager.register(this);
    }

    /**
     * Initializes the project registry
     *
     * @since 3.3
     */
    public static void init() {
        /* static variables are initialized */
    }

    /**
     * Disposes the project registry
     *
     * @since 2.3
     */
    public static void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(INSTANCE);
        TmfSignalManager.deregister(INSTANCE);
        registry.values().forEach(TmfProjectElement::dispose);
    }

    /**
     * Get the project model element for a project resource
     * @param project the project resource
     * @return the project model element or null if it does not exist
     */
    public static synchronized TmfProjectElement getProject(IProject project) {
        try {
            if (project.hasNature(TmfProjectNature.ID)) {
                return getProject(project, true);
            }
        } catch (CoreException e) {
            /* ignore */
        }
        return null;
    }

    /**
     * Get the project model element for a project resource
     * @param aProject the project resource
     * @param force a flag controlling whether a new project should be created if it doesn't exist
     * @return the project model element
     */
    public static synchronized TmfProjectElement getProject(IProject aProject, boolean force) {
        IProject project = aProject;
        if (TmfProjectElement.showProjectRoot(project)) {
            IWorkspace workspace = ResourcesPlugin.getWorkspace();
            IProject shadowProject = workspace.getRoot().getProject(TmfProjectModelHelper.getShadowProjectName(project.getName()));
            if (shadowProject.exists()) {
                project = shadowProject;
            }
        }
        TmfProjectElement element = registry.get(project);
        if (element == null && force) {
            element = new TmfProjectElement(project.getName(), project, null);
            registry.put(project, element);
            // force the model to be populated
            element.refreshChildren();
        }
        return element;
    }

    /**
     * Utility method to create a tracing project.
     *
     * @param projectName
     *          - A project name
     * @param projectLocation
     *          - A project location URI. Use null for default location (which is workspace).
     * @param monitor
     *          - A progress monitor
     * @return the IProject object or null
     */
    public static IProject createProject(String projectName, final URI projectLocation, IProgressMonitor monitor) {

        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        IWorkspaceRoot root = workspace.getRoot();
        final IProject project = root.getProject(projectName);
        WorkspaceModifyOperation action = new WorkspaceModifyOperation() {
            @Override
            protected void execute(IProgressMonitor progressMonitor) throws CoreException, InvocationTargetException, InterruptedException {
                if (!project.exists()) {
                    IProjectDescription description = workspace.newProjectDescription(project.getName());
                    if (projectLocation != null) {
                        description.setLocationURI(projectLocation);
                    }
                    project.create(description, progressMonitor);
                }

                if (!project.isOpen()) {
                    project.open(progressMonitor);
                }

                IProjectDescription description = project.getDescription();
                description.setNatureIds(new String[] { TmfProjectNature.ID });
                project.setDescription(description, null);

                TmfProjectElement.createFolderStructure(project);
            }
        };
        try {
            action.run(monitor);
        } catch (InvocationTargetException e) {
            Activator.getDefault().logError("Error creating TMF project " + project.getName(), e); //$NON-NLS-1$
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return project;
    }

    /**
     * Add a the tracing nature to the given project resource
     *
     * @param project
     *            the project resource
     * @param monitor
     *          - A progress monitor
     * @since 3.2
     */
    public static void addTracingNature(IProject project, IProgressMonitor monitor) {
        WorkspaceModifyOperation action = new WorkspaceModifyOperation() {
            @Override
            protected void execute(IProgressMonitor progressMonitor) throws CoreException, InvocationTargetException, InterruptedException {
                if (!project.exists()) {
                    return;
                }
                if (!project.isOpen()) {
                    project.open(progressMonitor);
                }
                IProjectDescription description = project.getDescription();
                boolean hasNature = description.hasNature(TmfProjectNature.ID);
                String[] natures = description.getNatureIds();
                IWorkspace workspace = ResourcesPlugin.getWorkspace();
                if (workspace == null) {
                    return;
                }
                if (!hasNature && natures.length > 0) {
                    // Only add nature if project doesn't have the tracing nature
                    String[] newNatures = new String[natures.length + 1];
                    System.arraycopy(natures, 0, newNatures, 0, natures.length);

                    // add the tracing nature ID
                    newNatures[natures.length] = TmfProjectNature.ID;

                    // validate the natures
                    IStatus status = workspace.validateNatureSet(newNatures);

                    // only apply new nature, if the status is ok
                    if (status.isOK()) {
                        description.setNatureIds(newNatures);
                        project.setDescription(description, null);
                    } else {
                        Activator.getDefault().getLog().log(status);
                    }
                }

                // Create shadow project
                description = project.getDescription();
                hasNature = description.hasNature(TmfProjectNature.ID);
                natures = description.getNatureIds();
                if (hasNature && natures.length > 1) {
                    String shadowProjectName = TmfProjectModelHelper.getShadowProjectName(project.getName());
                    IProject shadowProject = workspace.getRoot().getProject(shadowProjectName);

                    if (shadowProject.exists() && !shadowProject.isAccessible()) {
                        /*
                         * If a shadow project is not accessible, i.e. closed or deleted from file
                         * system while not being removed from the workspace, then remove the project
                         * from workspace in order to be able to successfully re-create the shadow
                         * project afterwards.
                         */
                        shadowProject.delete(false, true, progressMonitor);
                    }

                    if (!shadowProject.exists()) {
                        // Get or create shadow project
                        IFolder shadowProjectFolder = TmfProjectElement.createFolderStructure(project, null);
                        shadowProject = createProject(shadowProjectName, shadowProjectFolder.getLocationURI(), progressMonitor);
                        shadowProjectFolder.refreshLocal(IResource.DEPTH_INFINITE, progressMonitor);
                    }

                    if (!shadowProject.isOpen()) {
                        shadowProject.open(progressMonitor);
                    }
                    // Create project directory in shadow project
                    TmfProjectElement.createFolderStructure(project, shadowProject);

                    // If the traces or experiment or supplementary folder is null
                    // then refresh children to make sure that the created folder(s) are
                    // available
                    TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, false);
                    if ((tmfProject != null) &&
                            ((tmfProject.getTracesFolder() == null) || (tmfProject.getExperimentsFolder() == null) || (tmfProject.getSupplementaryFolder() == null))) {
                        tmfProject.refreshChildren();
                    }
                }
            }
        };
        try {
            action.run(monitor);
        } catch (InvocationTargetException e) {
            Activator.getDefault().logError("Error adding tracing nature to project " + project.getName(), e); //$NON-NLS-1$
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    // ------------------------------------------------------------------------
    // IResourceChangeListener
    // ------------------------------------------------------------------------

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        if (event.getType() == IResourceChangeEvent.PRE_DELETE || event.getType() == IResourceChangeEvent.PRE_CLOSE) {
            if (event.getResource() instanceof IProject) {
                IProject aProject = (IProject) event.getResource();
                try {
                    boolean isShadowProject = TmfProjectModelHelper.isShadowProject(Objects.requireNonNull(aProject));
                    if (aProject.isAccessible() && aProject.hasNature(TmfProjectNature.ID)) {
                        // A tracing project is being deleted.
                        IProject project = aProject;
                        if (!isShadowProject) {
                            // If a shadow project exists, use the shadow project.
                            IProject shadowProject = TmfProjectModelHelper.getShadowProject(project);
                            if (shadowProject.exists()) {
                                project = shadowProject;
                            }
                        }

                        TmfProjectElement tmfProjectElement = registry.get(project);
                        if (tmfProjectElement != null) {
                            // Close all traces editors
                            TmfTraceFolder tracesFolder = tmfProjectElement.getTracesFolder();
                            if (tracesFolder != null) {
                                final List<TmfTraceElement> traces = tracesFolder.getTraces();
                                if (!traces.isEmpty()) {
                                    // Close editors in UI Thread
                                    Display.getDefault().syncExec(() -> traces.forEach(TmfTraceElement::closeEditors));
                                }
                            }
                        }

                        boolean removeProjectElement = (event.getType() == IResourceChangeEvent.PRE_CLOSE);

                        // If parent project was deleted and a shadow project exists,
                        if (!isShadowProject) {
                            final IProject shadowProject = TmfProjectModelHelper.getShadowProject(aProject);
                            if (shadowProject.exists()) {
                                if (event.getType() == IResourceChangeEvent.PRE_DELETE) {
                                    Display.getDefault().asyncExec(() -> {
                                        try {
                                            shadowProject.delete(false, true, null);
                                        } catch (CoreException e) {
                                            Activator.getDefault().logError("Error deleting shadow project when is parent project being deleted " + shadowProject.getName(), e); //$NON-NLS-1$
                                        }
                                    });
                                } else {
                                    Display.getDefault().asyncExec(() -> {
                                        try {
                                            shadowProject.close(null);
                                        } catch (CoreException e) {
                                            Activator.getDefault().logError("Error removing shadow project when is parent project being closed " + shadowProject.getName(), e); //$NON-NLS-1$
                                        }
                                    });
                                }
                                removeProjectElement = false;
                            }
                        }

                        if (removeProjectElement) {
                            // IResourceChangeEvent.PRE_CLOSE of a non-shadow project
                            TmfProjectElement projectElement = registry.remove(project);
                            if (projectElement != null) {
                                projectElement.dispose();
                            }
                        }
                    }
                } catch (CoreException e) {
                    Activator.getDefault().logError("Error handling resource change event for " + aProject.getName(), e); //$NON-NLS-1$
                }
            }
        } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
            for (IResourceDelta delta : event.getDelta().getAffectedChildren()) {
                if (delta.getResource() instanceof IProject) {
                    IProject project = (IProject) delta.getResource();

                    try {
                        if (delta.getKind() == IResourceDelta.REMOVED) {
                            // Handle project deleted (it is also closed)
                            TmfProjectElement projectElement = registry.remove(project);

                            if (projectElement != null) {
                                projectElement.dispose();
                            }

                            // Refresh viewer for parent project
                            IProject parentProject = TmfProjectModelHelper.getProjectFromShadowProject(project);
                            if (parentProject != null) {
                                new TmfProjectElement(parentProject.getName(), parentProject, null).refreshViewer();
                            }
                        } else if (!project.isOpen() || !project.hasNature(TmfProjectNature.ID)) {
                            // Project is closed or does not have tracing nature, skip it
                            continue;
                        } else if ((delta.getKind() == IResourceDelta.ADDED) && (delta.getFlags() & IResourceDelta.MOVED_FROM) != 0 ) {
                            // Handle project moved - it covers only if it's not shadow project
                            handleProjectMoved(project);
                        } else if (delta.getKind() == IResourceDelta.CHANGED) {
                            // If shadow project exists, handle resource change in the shadow project
                            if (TmfProjectModelHelper.shadowProjectAccessible(project)) {
                                handleParentProjectRefresh(project);
                                continue;
                            } else if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
                                handleParentProjectOpen(project);
                                continue;
                            }

                            // Handle resource change in the relevant project
                            IProject parentProject = TmfProjectModelHelper.getProjectFromShadowProject(project);
                            Set<IResource> resourcesToRefresh = new HashSet<>();
                            Map<IResource, Integer> resourceFlags = new HashMap<>();
                            delta.accept(visited -> {
                                resourceFlags.put(visited.getResource(), visited.getFlags());
                                if ((visited.getFlags() & (IResourceDelta.CONTENT | IResourceDelta.LOCAL_CHANGED)) != 0) {
                                    // visited resource content has changed, refresh it
                                    resourcesToRefresh.add(visited.getResource());
                                } else if ((visited.getKind() != IResourceDelta.CHANGED)) {
                                    IResource parent = visited.getResource().getParent();
                                    if (((visited.getFlags() & (IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO)) == 0)) {
                                        // visited resource is added or removed, refresh it and its parent
                                        resourcesToRefresh.add(visited.getResource());
                                        resourcesToRefresh.add(parent);
                                    } else {
                                        Integer parentFlags = resourceFlags.get(parent);
                                        if (parentFlags != null && ((parentFlags & (IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO)) == 0)) {
                                            // visited resource is moved, refresh its parent if not moved itself
                                            resourcesToRefresh.add(parent);
                                        }
                                    }
                                }
                                return true;
                            });
                            Set<ITmfProjectModelElement> elementsToRefresh = new HashSet<>();
                            for (IResource resource : resourcesToRefresh) {
                                ITmfProjectModelElement element = findElement(resource, false);
                                if (element != null) {
                                    elementsToRefresh.add(element);
                                }
                            }
                            Set<TmfTraceElement> deletedTraces = new HashSet<>();
                            Set<TmfTraceElement> changedTraces = new HashSet<>();
                            Iterator<ITmfProjectModelElement> iterator = elementsToRefresh.iterator();
                            while (iterator.hasNext()) {
                                ITmfProjectModelElement element = iterator.next();
                                if (element instanceof TmfTraceElement) {
                                    TmfTraceElement trace = (TmfTraceElement) element;
                                    IResource resource = trace.getResource();
                                    if (isHandleDeleted(resource, parentProject)) {
                                        deletedTraces.add(trace);
                                    } else {
                                        changedTraces.add(trace);
                                    }
                                }
                                for (ITmfProjectModelElement parent : elementsToRefresh) {
                                    // remove element if any of its parents is included
                                    if (parent.getPath().isPrefixOf(element.getPath()) && !parent.equals(element)) {
                                        iterator.remove();
                                        break;
                                    }
                                }
                            }
                            if (!deletedTraces.isEmpty() || !changedTraces.isEmpty()) {
                                Job.createSystem("TmfProjectRegistry.resourceChanged Job", monitor -> { //$NON-NLS-1$
                                    try {
                                        if (!deletedTraces.isEmpty()) {
                                            Display.getDefault().syncExec(() -> {
                                                for (TmfTraceElement deletedTrace : deletedTraces) {
                                                    deletedTrace.closeEditors();
                                                }
                                            });
                                        }
                                        ResourcesPlugin.getWorkspace().run((ICoreRunnable) (mon -> {
                                            for (TmfTraceElement deletedTrace : deletedTraces) {
                                                handleTraceDeleted(deletedTrace);
                                            }
                                            for (TmfTraceElement changedTrace : changedTraces) {
                                                handleTraceContentChanged(changedTrace);
                                            }
                                        }), project, IWorkspace.AVOID_UPDATE, null);
                                    } catch (CoreException e) {
                                        Activator.getDefault().logError("Error handling resource change event for " + project.getName(), e); //$NON-NLS-1$
                                    }
                                    if (parentProject != null && parentProject.exists()) {
                                        new TmfProjectElement(parentProject.getName(), parentProject, null).refreshViewer();
                                    }
                                }).schedule();
                            }
                            for (ITmfProjectModelElement element : elementsToRefresh) {
                                element.refresh();
                            }
                            TmfProjectElement projectElement = registry.get(project);
                            if (projectElement != null) {
                                // refresh only the viewer for the affected project
                                projectElement.refreshViewer();
                            }
                        }
                    } catch (CoreException e) {
                        Activator.getDefault().logError("Error handling resource change event for " + project.getName(), e); //$NON-NLS-1$
                    }
                }
            }
        }
    }

    private static boolean isHandleDeleted(IResource resource, IProject parentProject) {
        IPath path = ResourceUtil.getLocation(resource);
        if (path == null) {
            return false;
        }
        return ((parentProject != null && parentProject.getLocation().isPrefixOf(path)) // link from shadow project to parent project
              || resource.getWorkspace().getRoot().getLocation().isPrefixOf(path)) && // link within the workspace
                !path.toFile().exists();
    }

    // -------------------------------------------------------
    // Signal handlers
    // -------------------------------------------------------

    /**
     * Handler for the Trace Opened signal
     *
     * @param signal
     *            The incoming signal
     * @since 3.1
     */
    @TmfSignalHandler
    public void traceOpened(TmfTraceOpenedSignal signal) {
        ITmfProjectModelElement element = findElement(signal.getTrace().getResource(), true);
        if (element != null) {
            element.refresh();
            if (element instanceof TmfExperimentElement) {
                TmfExperimentElement experiment = (TmfExperimentElement) element;
                for (TmfTraceElement trace : experiment.getTraces()) {
                    trace.getElementUnderTraceFolder().refresh();
                }
            }
        }
    }

    // -------------------------------------------------------
    // Helper methods
    // -------------------------------------------------------

    /**
     * Finds an existing project model element that matches the given resource.
     * Elements are not created if they do not already exist. If an exact match is
     * not found and <code>exact</code> is false, returns the nearest existing
     * parent element.
     *
     * @param resource
     *            the resource
     * @param exact
     *            if an exact match is not found, returns <code>null</code> if true,
     *            or the nearest parent if false
     * @return the element, or <code>null</code>
     * @since 3.1
     */
    public static ITmfProjectModelElement findElement(IResource resource, boolean exact) {
        if ((resource == null) || (resource.getProject() == null)) {
            return null;
        }
        ITmfProjectModelElement element = getProject(resource.getProject());
        if (element == null) {
            return null;
        }
        for (String segment : resource.getProjectRelativePath().segments()) {
            List<ITmfProjectModelElement> children = element.getChildren();
            boolean match = false;
            for (ITmfProjectModelElement child : children) {
                IResource childResource = child.getResource();
                if (resource.equals(childResource)) {
                    return child;
                }
                if (childResource != null && segment.equals(childResource.getName())) {
                    element = child;
                    match = true;
                    break;
                }
            }
            if (!match) {
                /* do not return project as nearest parent */
                return exact ? null : element instanceof TmfProjectElement ? null : element;
            }
        }
        return element;
    }

    private static void handleTraceContentChanged(TmfTraceElement traceElement) {
        boolean opened = false;
        for (ITmfTrace openedTrace : TmfTraceManager.getInstance().getOpenedTraces()) {
            for (ITmfTrace trace : TmfTraceManager.getTraceSet(openedTrace)) {
                if (traceElement.getResource().equals(trace.getResource())) {
                    opened = true;
                    break;
                }
            }
        }
        if (!opened) {
            traceElement.deleteSupplementaryResources();
            return;
        }
        synchronized (promptQueue) {
            if (!promptQueue.isEmpty()) {
                /* already prompting the user */
                if (!promptQueue.contains(traceElement)) {
                    promptQueue.add(traceElement);
                }
                return;
            }
            promptQueue.add(traceElement);
        }
        Display.getDefault().asyncExec(() -> {
            TmfTraceElement prompting = traceElement;
            while (prompting != null) {
                boolean always = Activator.getDefault().getPreferenceStore().getBoolean(ITmfUIPreferences.ALWAYS_CLOSE_ON_RESOURCE_CHANGE);
                if (always) {
                    prompting.closeEditors();
                    deleteSupplementaryResources(prompting);
                } else {
                    Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();

                    MessageDialog messageDialog = new MessageDialog(shell,
                            Messages.TmfProjectRegistry_TraceChangedDialogTitle,
                            null,
                            NLS.bind(Messages.TmfProjectRegistry_TraceChangedDialogMessage, prompting.getElementPath()),
                            MessageDialog.QUESTION,
                            2, // Always is the default.
                            IDialogConstants.NO_LABEL,
                            IDialogConstants.YES_LABEL,
                            Messages.TmfProjectRegistry_Always);
                    int returnCode = messageDialog.open();
                    if (returnCode >= 1) {
                        // yes or always.
                        prompting.closeEditors();
                        deleteSupplementaryResources(prompting);
                    }
                    if (returnCode == 2) {
                        // remember to always delete supplementary files and close editors
                        Activator.getDefault().getPreferenceStore().setValue(ITmfUIPreferences.ALWAYS_CLOSE_ON_RESOURCE_CHANGE, true);
                    }
                }
                /*
                 * Poll at end of the loop: The element must remain in the queue while prompting
                 * the user, so that another element being handled will be added to the queue
                 * instead of opening another dialog, and also to prevent the user from being
                 * prompted twice for the same element.
                 */
                synchronized (promptQueue) {
                    promptQueue.remove();
                    prompting = promptQueue.peek();
                }
            }
        });
    }

    private static void deleteSupplementaryResources(TmfTraceElement trace) {
        /*
         * Delete supplementary resources as an atomic workspace operation. To ensure no
         * dialog is shown to the user, run it in a job outside of the UI thread.
         */
        Job.createSystem("TmfProjectRegistry.deleteSupplementaryResources Job", monitor -> { //$NON-NLS-1$
            try {
                ResourcesPlugin.getWorkspace().run((ICoreRunnable) (mon -> {
                    trace.deleteSupplementaryResources();
                }), trace.getProject().getResource(), IWorkspace.AVOID_UPDATE, monitor);
            } catch (CoreException e) {
                Activator.getDefault().logError("Error deleting supplementary resources for " + trace.getName(), e); //$NON-NLS-1$
            }
        }).schedule();
    }

    private static void handleTraceDeleted(TmfTraceElement trace) {
        try {
            trace.preDelete(new NullProgressMonitor(), false);
            ResourceUtil.deleteIfBrokenSymbolicLink(trace.getResource());
        } catch (CoreException e) {
            Activator.getDefault().logError("Error deleting trace " + trace.getName(), e); //$NON-NLS-1$
        }
    }

    private static void handleParentProjectOpen(IProject project) {
        Job job = Job.createSystem("TmfProjectRegistry.handleParentProjectOpen Job", monitor -> { //$NON-NLS-1$
            if (!project.exists() || !project.isOpen()) {
                return;
            }
            IProject shadowProject = TmfProjectModelHelper.getShadowProject(project);
            if (shadowProject != null && shadowProject.exists() && !shadowProject.isOpen()) {
                try {
                    shadowProject.open(new NullProgressMonitor());
                } catch (CoreException e) {
                    // Do nothing ... addTracingNature() will handle this
                }
            }
            addTracingNature(project, monitor);
        });
        job.schedule();
    }

    private static void handleParentProjectRefresh(IProject project) {
        Job job = Job.createSystem("TmfProjectRegistry.handleParentProjectRefresh Job", monitor -> { //$NON-NLS-1$
            if (!project.exists() || !project.isOpen()) {
                return;
            }
            IWorkspace workspace = ResourcesPlugin.getWorkspace();
            IProject shadowProject = workspace.getRoot().getProject(TmfProjectModelHelper.getShadowProjectName(project.getName()));
            if (shadowProject.exists()) {
                try {
                    shadowProject.refreshLocal(IResource.DEPTH_INFINITE, monitor);
                } catch (CoreException e) {
                    Activator.getDefault().logError("Error refeshing shadow project " + shadowProject.getName(), e); //$NON-NLS-1$
                }
            }
        });
        job.schedule();
    }

    private static void handleProjectMoved(IProject newProject) {
        Job job = Job.createSystem("TmfProjectRegistry.handleProjectMoved Job", monitor -> { //$NON-NLS-1$
            if (!newProject.exists() || !newProject.isOpen()) {
                return;
            }
            addTracingNature(newProject, monitor);
            IProject shadowProject = TmfProjectModelHelper.getShadowProject(newProject);
            String newShadowProjectName = TmfProjectModelHelper.getShadowProjectName(newProject.getName());
            try {
                if (shadowProject.exists()) {
                    IProjectDescription desc = shadowProject.getDescription();
                    desc.setName(newShadowProjectName);
                    shadowProject.move(desc, true, monitor);
                }
            } catch (CoreException e) {
                Activator.getDefault().logError("Error renaming shadow project " + shadowProject.getName() + " to " + newShadowProjectName, e); //$NON-NLS-1$ //$NON-NLS-2$
            }
        });
        job.schedule();
    }
}
