package org.jboss.windup.rules.apps.java.scan.ast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.source.Import;
import org.jboss.forge.roaster.model.source.Importer;
import org.jboss.forge.roaster.spi.WildcardImportResolver;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.service.JavaClassService;

/**
 * Provides a wildcard resolver for imports that attempts to search the graph for related types
 */
public class WindupWildcardImportResolver implements WildcardImportResolver, org.jboss.windup.ast.java.WildcardImportResolver {
    private static GraphContext context;

    /**
     * Contains a map of class short names (eg, MyClass) to qualified names (eg, com.example.MyClass)
     */
    private final Map<String, String> classNameToFQCN = new ConcurrentHashMap<>();

    /**
     * Indicates that we have already attempted to query the graph for this particular shortname. The shortname will exist here even if no results
     * were found.
     */
    private final Set<String> classNameLookedUp = Collections.synchronizedSet(new HashSet<String>());

    @Override
    public String resolve(JavaType<?> source, String type) {
        GraphContext graphContext = getContext();
        if (graphContext == null) {
            return type;
        }

        Importer<?> importer = (Importer<?>) source;
        List<String> wildcardImports = new ArrayList<>();
        for (Import importDeclaration : importer.getImports()) {
            if (importDeclaration.isWildcard()) {
                wildcardImports.add(importDeclaration.getQualifiedName());
            }
        }
        return resolve(wildcardImports, type);
    }

    @Override
    public String resolve(List<String> wildcardImports, String type) {
        GraphContext graphContext = getContext();
        // If the type contains a "." assume that it is fully qualified.
        // FIXME - This is a carryover from the original Windup code, and I don't think
        // that this assumption is valid.
        // Check if we have already looked this one up
        if (classNameLookedUp.contains(type)) {
            // if yes, then just use the looked up name from the map
            String qualifiedName = classNameToFQCN.get(type);
            if (qualifiedName != null) {
                return qualifiedName;
            } else {
                // otherwise, just return the provided name (unchanged)
                return type;
            }
        } else {
            // if this name has not been resolved before, go ahead and resolve it from the graph (if possible)
            classNameLookedUp.add(type);

            // search every wildcard import for this name
            for (String wildcardImport : wildcardImports) {
                String candidateQualifiedName = wildcardImport + "." + type;

                JavaClassService javaClassService = new JavaClassService(graphContext);
                Iterable<JavaClassModel> models = javaClassService.findAllByProperty(JavaClassModel.QUALIFIED_NAME,
                        candidateQualifiedName);
                if (models.iterator().hasNext()) {
                    classNameToFQCN.put(type, candidateQualifiedName);
                    return candidateQualifiedName;
                }
            }
            // nothing was found, so just return the original value
            return type;
        }
    }

    @Override
    public String[] resolve(String wildcardImportPackageName) {
        JavaClassService javaClassService = new JavaClassService(getContext());
        Iterable<JavaClassModel> classModels = javaClassService.findByJavaPackage(wildcardImportPackageName);
        List<String> results = new ArrayList<>();
        for (JavaClassModel classModel : classModels) {
            results.add(classModel.getQualifiedName());
        }
        return results.toArray(new String[results.size()]);
    }

    private GraphContext getContext() {
        return WindupWildcardImportResolver.context;
    }

    public static void setContext(GraphContext context) {
        WindupWildcardImportResolver.context = context;
    }
}
