package org.implab.gradle.variants.sources;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.implab.gradle.variants.core.Layer;
import org.implab.gradle.variants.core.Role;
import org.implab.gradle.variants.core.Variant;
import org.implab.gradle.variants.core.VariantsView;
import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;

@NonNullByDefault
public final class CompileUnitsView {

    private final VariantsView variants;
    private final Map<Variant, Set<CompileUnit>> unitsByVariant = new HashMap<>();

    private CompileUnitsView(VariantsView variants) {
        this.variants = variants;
    }

    public Set<CompileUnit> getUnits() {
        return variants.getEntries().stream()
                .map(CompileUnit::of)
                .collect(Collectors.toUnmodifiableSet());
    }

    public Set<CompileUnit> getUnitsForVariant(Variant variant) {
        Objects.requireNonNull(variant, "Variant can't be null");

        return unitsByVariant.computeIfAbsent(variant, key -> variants
                .getEntriesForVariant(variant).stream()
                .map(CompileUnit::of)
                .collect(Collectors.toUnmodifiableSet()));
    }

    public Optional<CompileUnit> findUnit(Variant variant, Layer layer) {
        Objects.requireNonNull(variant, "Variant can't be null");
        Objects.requireNonNull(layer, "Layer can't be null");

        return getUnitsForVariant(variant).stream()
                .filter(u -> u.layer().equals(layer))
                .findAny();
    }

    public boolean contains(Variant variant, Layer layer) {
        return findUnit(variant, layer).isPresent();
    }

    /**
     * In which logical roles this compile unit participates.
     */

    public Set<Role> getRoles(CompileUnit unit) {
        Objects.requireNonNull(unit, "Compile unit can't be null");
        return variants.getEntriesForVariant(unit.variant()).stream()
                .filter(entry -> entry.layer().equals(unit.layer()))
                .map(VariantRoleLayer::role)
                .collect(Collectors.toUnmodifiableSet());
    }

    public CompileUnit getUnit(Variant variant, Layer layer) {
        return findUnit(variant, layer)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Compile unit for variant '" + variant.getName()
                                + "' and layer '" + layer.getName() + "' not found"));
    }

    public static CompileUnitsView of(VariantsView variantsView) {
        Objects.requireNonNull(variantsView, "variantsView can't be null");
        return new CompileUnitsView(variantsView);
    }
}