package org.implab.gradle.variants.artifacts.internal;

import java.util.HashSet;
import java.util.Set;

import org.gradle.api.file.ConfigurableFileCollection;
import org.implab.gradle.variants.artifacts.ArtifactSlot;
import org.implab.gradle.variants.sources.CompileUnit;
import org.implab.gradle.variants.sources.CompileUnitsView;
import org.implab.gradle.variants.sources.RoleProjectionsView;
import org.implab.gradle.variants.sources.SourceSetMaterializer;

/**
 * Сборщик входящих элементов из разных источников {@link SlotContribution}.
 * Хранит {@link ConfigurableFileCollection} и добавляет в него новые элементы,
 * при этом делается дедупликация по {@link SlotInputKey}.
 *
 */
public class SlotInputsAssembler implements SlotContributionVisitor {

    // sources context
    private final CompileUnitsView compileUnitsView;

    private final RoleProjectionsView roleProjectionsView;

    private final SourceSetMaterializer sourceSetMaterializer;

    // artifact slot for this assembly
    private final ArtifactSlot artifactSlot;

    // content for this assembly
    private final ConfigurableFileCollection artifactInputs;

    // seen inputs, used for deduplication
    private final Set<SlotInputKey> seen = new HashSet<>();

    public SlotInputsAssembler(
            ArtifactSlot artifactSlot,
            ConfigurableFileCollection artifactInputs,
            CompileUnitsView compileUnitsView,
            RoleProjectionsView roleProjectionsView,
            SourceSetMaterializer sourceSetMaterializer) {
        this.compileUnitsView = compileUnitsView;
        this.roleProjectionsView = roleProjectionsView;
        this.sourceSetMaterializer = sourceSetMaterializer;
        this.artifactSlot = artifactSlot;
        this.artifactInputs = artifactInputs;
    }

    @Override
    public void visit(DirectContribution contribution) {
        contribute(
                SlotInputKey.newUniqueKey("Direct input for " + artifactSlot),
                contribution.input());
    }

    @Override
    public void visit(VariantOutputsContribution contribution) {
        var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant());
        contributeCompileUnits(units, contribution.outputs());
    }

    @Override
    public void visit(RoleOutputsContribution contribution) {
        var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(),
                contribution.role());
        var units = roleProjectionsView.getUnits(roleProjection);

        contributeCompileUnits(units, contribution.outputs());

    }

    @Override
    public void visit(LayerOutputsContribution contribution) {
        var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer());
        contributeCompileUnits(Set.of(unit), contribution.outputs());
    }

    private void contributeCompileUnits(Set<CompileUnit> units, Set<String> outputs) {
        units.stream()
                // expand variant compile units, make (compileUnit, outputName) pairs
                .flatMap(unit -> outputs.stream()
                        .map(output -> new CompileUnitOutputKey(unit, output)))
                .forEach(key -> contribute(
                        key,
                        sourceSetMaterializer.getSourceSet(key.unit())
                                .map(s -> s.output(key.outputName()))));
    }

    private void contribute(SlotInputKey key, Object resolvedInput) {
        if (!seen.add(key))
            return;
        artifactInputs.from(resolvedInput);
    }

}
