package bndtools.launch;

import java.io.Closeable;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;

import org.bndtools.build.api.BuildListener;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

import aQute.bnd.osgi.Jar;
import aQute.bnd.service.RepositoryListenerPlugin;
import aQute.bnd.service.RepositoryPlugin;

/**
 * This class watches the build and allows you to schedule an update some time after the last build operation ended.
 */
public abstract class UpdateGuard implements Closeable {

    private static final int GRACE_PERIOD = 500;
    private final static Timer timer = new Timer(true);
    private final BundleContext context;
    private final AtomicBoolean closed = new AtomicBoolean(true);
    private ServiceRegistration<BuildListener> buildListener;
    private ServiceRegistration<RepositoryListenerPlugin> repositoryListener;
    private TimerTask trigger;

    public UpdateGuard(BundleContext context) {
        this.context = context;
    }

    public void open() {
        if (!closed.getAndSet(false))
            throw new IllegalStateException("Already opened");

        //
        // We wait for build changes. We never update during a build
        // and we will wait a bit after a build ends.
        //

        buildListener = context.registerService(BuildListener.class, new BuildListener() {

            @Override
            public void buildStarting(IProject project) {
                off();
            }

            @Override
            public void builtBundles(IProject project, IPath[] paths) {}

            @Override
            public void released(IProject project) {
                on();
            }
        }, null);

        //
        // We also wait for repository changes, though they will generally cause a rebuild as well.
        //
        repositoryListener = context.registerService(RepositoryListenerPlugin.class, new RepositoryListenerPlugin() {

            @Override
            public void repositoryRefreshed(RepositoryPlugin repository) {
                on();
            }

            @Override
            public void repositoriesRefreshed() {
                on();
            }

            @Override
            public void bundleRemoved(RepositoryPlugin repository, Jar jar, File file) {
                on();
            }

            @Override
            public void bundleAdded(RepositoryPlugin repository, Jar jar, File file) {
                on();
            }
        }, null);

    }

    private void off() {
        if (closed.get())
            return;

        synchronized (timer) {
            if (trigger != null)
                trigger.cancel();
            trigger = null;
        }
    }

    private void on() {
        if (closed.get())
            return;

        synchronized (timer) {
            if (trigger != null)
                trigger.cancel();
            trigger = new TimerTask() {

                @Override
                public void run() {
                    if (closed.get())
                        return;
                    update();
                }
            };
            timer.schedule(trigger, GRACE_PERIOD);
        }
    }

    protected abstract void update();

    public void kick() {
        on();
    }

    @Override
    public void close() {
        if (closed.getAndSet(true))
            return;

        try {
            buildListener.unregister();
        } catch (Exception e) {
            // ignore
        }
        try {
            repositoryListener.unregister();
        } catch (Exception e) {
            // ignore
        }
    }
}
