package bndtools.launch.util;

import org.bndtools.api.BndtoolsConstants;
import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.bndtools.api.RunMode;
import org.bndtools.api.RunListener;
import org.bndtools.api.RunProvider;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.ServiceTracker;

import aQute.bnd.build.Project;
import aQute.bnd.build.Run;
import bndtools.central.Central;
import bndtools.launch.LaunchConstants;

public final class LaunchUtils {
    private static final ILogger logger = Logger.getLogger(LaunchUtils.class);

    private static ServiceTracker<RunListener, RunListener> runListeners;
    private static ServiceTracker<RunProvider, RunProvider> runProviders;

    private LaunchUtils() {
    }

    public static IResource getTargetResource(ILaunchConfiguration configuration) throws CoreException {
        String target = getTargetName(configuration);
        if (target == null)
            return null;

        IResource targetResource = ResourcesPlugin.getWorkspace()
            .getRoot()
            .findMember(target);
        return targetResource;
    }

    public static String getLaunchProjectName(IResource launchResource) {
        String result;

        IProject project = launchResource.getProject();
        Project bnd;
        try {
            bnd = Central.getWorkspace()
                .getProject(project.getName());
        } catch (Exception e) {
            bnd = null;
        }

        result = (bnd != null) ? bnd.getName() : Project.BNDCNF;
        return result;
    }

    public static Run createRun(ILaunchConfiguration configuration, RunMode mode) throws Exception {
        IResource targetResource = getTargetResource(configuration);
        if (targetResource == null) {
            String target = getTargetName(configuration);
            throw new IllegalArgumentException(String.format("The run descriptor '%s' could not be found.", target));
        }

        return createRun(targetResource, mode);
    }

    public static Run createRun(IResource targetResource, RunMode mode) throws Exception {
        Run run = null;
        for (RunProvider runProvider : getRunProviders()) {
            try {
                if ((run = runProvider.create(targetResource, mode)) != null) {
                    break;
                }
            } catch (Throwable t) {
                logger.logError("Error in run listener", t);
            }
        }

        if (run == null) {
            throw new Exception(String.format("Cannot load Bnd project for directory %s: no Bnd workspace found", targetResource.getLocation()));
        }

        for (RunListener runListener : getRunListeners()) {
            try {
                runListener.create(run);
            } catch (Throwable t) {
                logger.logError("Error in run listener", t);
            }
        }

        RunMode.set(run, mode);

        return run;
    }

    private static String getTargetName(ILaunchConfiguration configuration) throws CoreException {
        String target = configuration.getAttribute(LaunchConstants.ATTR_LAUNCH_TARGET, (String) null);
        if (target != null && target.isEmpty()) {
            target = null;
        }
        return target;
    }

    public static void endRun(Run run) {
        for (RunListener runListener : getRunListeners()) {
            try {
                runListener.end(run);
            } catch (Throwable t) {
                logger.logError("Error in run listener", t);
            }
        }
    }

    private static synchronized RunListener[] getRunListeners() {
        if (runListeners == null) {
            final BundleContext context = FrameworkUtil.getBundle(LaunchUtils.class)
                .getBundleContext();

            if (context == null) {
                throw new IllegalStateException("Bundle context is null");
            }

            runListeners = new ServiceTracker<RunListener, RunListener>(context, RunListener.class, null);
            runListeners.open();
        }

        return runListeners.getTracked()
            .values()
            .toArray(new RunListener[0]);
    }

    private static synchronized RunProvider[] getRunProviders() {
        if (runProviders == null) {
            final BundleContext context = FrameworkUtil.getBundle(LaunchUtils.class)
                .getBundleContext();

            if (context == null) {
                throw new IllegalStateException("Bundle context is null");
            }

            runProviders = new ServiceTracker<RunProvider, RunProvider>(context, RunProvider.class, null);
            runProviders.open();
        }

        return runProviders.getTracked()
            .values()
            .toArray(new RunProvider[0]);
    }

    public static boolean isInBndWorkspaceProject(IResource resource) throws CoreException {
        if (resource == null) {
            return false;
        }

        IProject project = resource.getProject();

        return project.isOpen() && project.hasNature(BndtoolsConstants.NATURE_ID);
    }
}
