package de.renew.plugin.jpms.impl;

import java.lang.module.ModuleDescriptor;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import de.renew.plugin.jpms.ExportsStrategy;


/**
 * A {@code ExportsStrategy} that tries to resolve all qualified exports.
 *
 * @author Kjell Ehlers
 * @since Renew 4.2
 */
public class ResolveExportsStrategy implements ExportsStrategy {

    private final Map<String, Set<UnresolvedExports>> _modulesUnresolvedExports = new HashMap<>();

    @Override
    public void putUnresolvedExportsFromBoot() {
        Module loader = getClass().getModule();
        if (!loader.isNamed()) {
            // Renew was not started as a modular application i.e. there won't
            // be any qualified exports.
            return;
        }

        getQualifiedExportsByTarget(loader).forEach((k, v) -> {
            _modulesUnresolvedExports.putIfAbsent(k, new HashSet<>());
            _modulesUnresolvedExports.get(k)
                .add(UnresolvedExports.withTarget(k).fromBoot().addPackages(v));
        });
    }

    @Override
    public void putUnresolvedExportsFrom(Module m, ModuleLayer.Controller c) {
        getQualifiedExportsByTarget(m).forEach((k, v) -> {
            _modulesUnresolvedExports.putIfAbsent(k, new HashSet<>());
            var target = _modulesUnresolvedExports.get(k);
            UnresolvedExports moduleExports =
                UnresolvedExports.withTarget(k).from(c).addPackages(v);

            if (!target.add(moduleExports)) {
                // The plugin this module belongs to was reloaded and the
                // old source controller is likely no longer alive
                target.stream().filter(moduleExports::equals).findFirst()
                    .ifPresent(e -> e.from(c).addPackages(v));
            }
        });
    }

    @Override
    public void tryResolveExportsTo(final Module m) {
        _modulesUnresolvedExports.computeIfPresent(m.getName(), (k, v) -> {
            v.forEach(e -> e.resolve(m));
            return v;
        });
    }

    private static Map<String, Set<String>> getQualifiedExportsByTarget(Module m) {
        return m.getDescriptor().exports().stream().filter(ModuleDescriptor.Exports::isQualified)
            .flatMap(
                pkg -> pkg.targets().stream()
                    .map(s -> new AbstractMap.SimpleEntry<>(s, pkg.source())))
            .collect(
                Collectors.groupingBy(
                    Map.Entry::getKey,
                    Collectors.mapping(Map.Entry::getValue, Collectors.toSet())));
    }
}
