package bndtools.views.repository;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;

import aQute.bnd.service.RemoteRepositoryPlugin;
import aQute.bnd.service.RepositoryPlugin;
import aQute.bnd.service.ResourceHandle;
import aQute.bnd.service.ResourceHandle.Location;
import aQute.bnd.service.Strategy;
import aQute.bnd.version.Version;
import bndtools.Plugin;
import bndtools.model.repo.RepositoryBundle;
import bndtools.model.repo.RepositoryBundleVersion;

public class RepoDownloadJob extends Job {

    private static final Lock LOCK = new ReentrantLock(true);

    private final Collection<RemoteRepositoryPlugin> repos;
    private final Collection<RepositoryBundle> bundles;
    private final Collection<RepositoryBundleVersion> bundleVersions;

    public RepoDownloadJob(Collection<RemoteRepositoryPlugin> repos, Collection<RepositoryBundle> bundles, Collection<RepositoryBundleVersion> bundleVersions) {
        super("Downloading Repository Contents");
        this.repos = repos;
        this.bundles = bundles;
        this.bundleVersions = bundleVersions;
    }

    @Override
    protected IStatus run(IProgressMonitor progress) {
        SubMonitor monitor = SubMonitor.convert(progress);

        boolean locked = LOCK.tryLock();
        try {
            while (!locked) {
                monitor.setBlocked(new Status(IStatus.INFO, Plugin.PLUGIN_ID, 0, "Waiting for other download jobs to complete.", null));
                if (progress.isCanceled())
                    return Status.CANCEL_STATUS;

                try {
                    locked = LOCK.tryLock(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {}
            }
            monitor.clearBlocked();

            MultiStatus status = new MultiStatus(Plugin.PLUGIN_ID, 0, "One or more repository files failed to download.", null);
            monitor.setTaskName("Expanding repository contents");
            List<RepositoryBundleVersion> rbvs = new LinkedList<RepositoryBundleVersion>();
            try {
                for (RemoteRepositoryPlugin repo : repos) {
                    expandContentsInto(repo, rbvs);
                }
                for (RepositoryBundle bundle : bundles) {
                    expandContentsInto(bundle, rbvs);
                }
                rbvs.addAll(bundleVersions);
            } catch (Exception e) {
                return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Error listing repository contents", e);
            }

            monitor.setWorkRemaining(rbvs.size());
            for (RepositoryBundleVersion rbv : rbvs) {
                if (monitor.isCanceled())
                    return Status.CANCEL_STATUS;

                String resourceName = "<<unknown>>";
                try {
                    RemoteRepositoryPlugin repo = (RemoteRepositoryPlugin) rbv.getRepo();
                    ResourceHandle handle = repo.getHandle(rbv.getBsn(), rbv.getVersion()
                        .toString(), Strategy.EXACT, Collections.<String, String> emptyMap());
                    resourceName = handle.getName();
                    Location location = handle.getLocation();

                    if (location == Location.remote) {
                        monitor.setTaskName("Downloading " + handle.getName());
                        handle.request();
                    }
                } catch (Exception e) {
                    status.add(new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, String.format("Download of %s:%s with remote name %s failed", rbv.getBsn(), rbv.getVersion(), resourceName), e));
                } finally {
                    monitor.worked(1);
                }
            }
            return status;
        } finally {
            if (locked)
                LOCK.unlock();
        }

    }

    private void expandContentsInto(RemoteRepositoryPlugin repo, List<RepositoryBundleVersion> rbvs) throws Exception {
        List<String> bsns = repo.list(null);
        if (bsns != null) {
            for (String bsn : bsns) {
                RepositoryBundle bundle = new RepositoryBundle(repo, bsn);
                expandContentsInto(bundle, rbvs);
            }
        }
    }

    private void expandContentsInto(RepositoryBundle bundle, List<RepositoryBundleVersion> rbvs) throws Exception {
        RepositoryPlugin repo = bundle.getRepo();
        SortedSet<Version> versions = repo.versions(bundle.getBsn());
        if (versions != null) {
            for (Version version : versions) {
                RepositoryBundleVersion rbv = new RepositoryBundleVersion(bundle, version);
                rbvs.add(rbv);
            }
        }
    }

    @Override
    protected void canceling() {
        Thread myThread = getThread();
        if (myThread != null)
            myThread.interrupt();
    }

}
