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.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;

public final class RoleProjectionsView {
    private final VariantsView variants;

    private final Map<Variant, Set<RoleProjection>> projectionsByVariant = new HashMap<>();

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

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

    public Set<RoleProjection> getProjectionsForVariant(Variant variant) {
        Objects.requireNonNull(variant, "Variant can't be null");
        return projectionsByVariant.computeIfAbsent(variant, key -> variants
                .getEntriesForVariant(variant).stream()
                .map(RoleProjection::of)
                .collect(Collectors.toUnmodifiableSet()));
    }

    public Set<RoleProjection> getProjectionsForRole(Role role) {
        Objects.requireNonNull(role, "Role can't be null");
        return variants.getEntriesForRole(role).stream()
                .map(RoleProjection::of)
                .collect(Collectors.toUnmodifiableSet());
    }

    public Optional<RoleProjection> findProjection(Variant variant, Role role) {
        Objects.requireNonNull(variant, "Variant can't be null");
        Objects.requireNonNull(role, "Role can't be null");
        return variants.getEntriesForVariant(variant).stream()
                .filter(entry -> entry.role().equals(role))
                .map(RoleProjection::of)
                .findAny();
    }

    public boolean contains(Variant variant, Role role) {
        return findProjection(variant, role).isPresent();
    }

    public Set<CompileUnit> getUnits(RoleProjection projection) {
        Objects.requireNonNull(projection, "Role projection can't be null");
        return variants.getEntriesForVariant(projection.variant()).stream()
                .filter(entry -> entry.role().equals(projection.role()))
                .map(CompileUnit::of)
                .collect(Collectors.toUnmodifiableSet());

    }

    public RoleProjection requireProjection(Variant variant, Role role) {
        return findProjection(variant, role)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Role projection for variant '" + variant.getName()
                                + "' and role '" + role.getName() + "' not found"));
    }

    public Set<Layer> getLayers(RoleProjection projection) {
        return getUnits(projection).stream()
                .map(CompileUnit::layer)
                .collect(java.util.stream.Collectors.toUnmodifiableSet());
    }

    public static RoleProjectionsView of(VariantsView variantsView) {
        return new RoleProjectionsView(variantsView);
    }
}