package org.jboss.windup.config.projecttraversal;

import org.jboss.windup.config.AbstractRuleLifecycleListener;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.ProjectModel;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.WindupConfigurationService;
import org.jboss.windup.graph.traversal.ProjectModelTraversal;

import java.lang.ref.SoftReference;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Maintains a cache of some traversal data to prevent the need for frequent recalculation of this data.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
public class ProjectTraversalCache extends AbstractRuleLifecycleListener
{
    private static final Map<ProjectModel, SoftReference<Set<ProjectModel>>> projectToApplicationCache = new ConcurrentHashMap<>();

    @Override
    public void beforeExecution(GraphRewrite event)
    {
        projectToApplicationCache.clear();
    }

    public static Set<ProjectModel> getApplicationsForProject(GraphContext context, ProjectModel project)
    {
        Set<ProjectModel> results = getFromCache(project);
        if (results != null)
            return results;

        results = new HashSet<>();

        WindupConfigurationModel configurationModel = WindupConfigurationService.getConfigurationModel(context);
        for (FileModel inputFile : configurationModel.getInputPaths())
        {
            ProjectModel application = inputFile.getProjectModel();
            ProjectModelTraversal traversal = new ProjectModelTraversal(application);
            Set<ProjectModel> projectsInApplication = traversal.getAllProjects(true);
            if (projectsInApplication.contains(project))
                results.add(application);
        }

        /*
         * HACK -- In tests it is possible for there to be no true "application". This can result
         *  in no results for this particular project.
         *
         *  In a case like this, the best that we can do is to simply return the project itself as the Set.
         */
        if (results.isEmpty())
            results.add(project);

        putInCache(project, results);

        return results;
    }

    private static Set<ProjectModel> getFromCache(ProjectModel project)
    {
        if (project == null)
            return null;

        synchronized (projectToApplicationCache)
        {
            SoftReference<Set<ProjectModel>> referenceProjectsSet = projectToApplicationCache.get(project);
            return referenceProjectsSet == null ? null : referenceProjectsSet.get();
        }
    }

    private static void putInCache(ProjectModel project, Set<ProjectModel> projects)
    {
        if (project == null)
            return;

        synchronized (projectToApplicationCache)
        {
            SoftReference<Set<ProjectModel>> referenceProjectsSet = new SoftReference<>(projects);
            projectToApplicationCache.put(project, referenceProjectsSet);
        }
    }
}
