package bndtools.central;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.bndtools.utils.log.LogServiceAdapter;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.log.LogService;

import aQute.bnd.build.Project;
import aQute.bnd.deployer.repository.CapabilityIndex;
import aQute.bnd.deployer.repository.api.IRepositoryContentProvider;
import aQute.bnd.deployer.repository.api.IRepositoryIndexProcessor;
import aQute.bnd.deployer.repository.api.Referral;
import aQute.bnd.deployer.repository.providers.R5RepoContentProvider;
import aQute.bnd.osgi.repository.BaseRepository;
import aQute.lib.io.IO;

public class WorkspaceR5Repository extends BaseRepository {

    private static final String NAME = "Workspace";

    private final Map<IProject, CapabilityIndex> projectMap = new HashMap<IProject, CapabilityIndex>();
    private final IRepositoryContentProvider contentProvider = new R5RepoContentProvider();

    private final ILogger logger = Logger.getLogger(WorkspaceR5Repository.class);
    private final LogService logAdapter = new LogServiceAdapter(logger);

    /**
     * Can only be instantiated within the package.
     */
    WorkspaceR5Repository() {}

    void init() throws Exception {
        Central.onWorkspace(workspace -> setupProjects());
    }

    void setupProjects() throws Exception {
        IProject[] projects = ResourcesPlugin.getWorkspace()
            .getRoot()
            .getProjects();
        for (IProject project : projects) {
            Project model = Central.getProject(project);
            if (model != null) {
                File targetDir = getTarget(model);
                if (targetDir != null) {
                    File indexFile = new File(targetDir, ".index");
                    if (indexFile.isFile()) {
                        loadProjectIndex(project, IO.stream(indexFile), project.getLocation()
                            .toFile()
                            .toURI());
                    }
                }
            }
        }
    }

    // This is equivalent to Project.getTarget0(). It gets the target dir without a prepare,
    // which would initialise the plugins too early.
    private File getTarget(Project project) throws IOException {
        File target = project.getFile(project.getProperty("target", "generated"));
        if (!target.exists()) {
            if (!target.mkdirs()) {
                throw new IOException("Could not create directory " + target);
            }
            project.getWorkspace()
                .changedFile(target);
        }
        return target;
    }

    public void loadProjectIndex(final IProject project, InputStream index, URI baseUri) {
        synchronized (projectMap) {
            try {
                cleanProject(project);

                IRepositoryIndexProcessor processor = new IRepositoryIndexProcessor() {
                    @Override
                    public void processResource(Resource resource) {
                        addResource(project, resource);
                    }

                    @Override
                    public void processReferral(URI parentUri, Referral referral, int maxDepth, int currentDepth) {
                        // ignore: we don't create any referrals
                    }
                };
                contentProvider.parseIndex(index, baseUri, processor, logAdapter);
            } catch (Exception e) {
                logger.logError(MessageFormat.format("Failed to process index file for bundles in project {0}.", project.getName()), e);
            } finally {
                IO.close(index);
            }
        }
    }

    private void cleanProject(IProject project) {
        CapabilityIndex index = projectMap.get(project);
        if (index != null)
            index.clear();
    }

    private void addResource(IProject project, Resource resource) {
        CapabilityIndex index = projectMap.get(project);
        if (index == null) {
            index = new CapabilityIndex();
            projectMap.put(project, index);
        }
        index.addResource(resource);
    }

    @Override
    public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
        Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>();
        for (Requirement requirement : requirements) {
            List<Capability> matches = new LinkedList<Capability>();
            result.put(requirement, matches);

            for (Entry<IProject, CapabilityIndex> entry : projectMap.entrySet()) {
                IProject project = entry.getKey();
                if (project.exists() && project.isOpen()) {
                    CapabilityIndex capabilityIndex = entry.getValue();
                    capabilityIndex.appendMatchingCapabilities(requirement, matches);
                }
            }
        }
        return result;
    }

    @Override
    public String toString() {
        return NAME;
    }

}
