package p.studio.compiler.workspaces.phases;

import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.BuildStack;
import p.studio.compiler.models.DependencyContext;
import p.studio.compiler.models.ProjectInfoId;
import p.studio.compiler.source.identifiers.ProjectId;
import p.studio.compiler.workspaces.DependencyPhase;
import p.studio.utilities.structures.DependencyGraphAnaliser;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;

import static java.util.stream.Collectors.joining;

public final class StackPhase implements DependencyPhase {
    /**
     * Implements topological sort; detects dependency cycles; sets build stack
     */
    @Override
    public BuildingIssueSink run(final DependencyContext ctx) {
        final var analysis = new DependencyGraphAnaliser<ProjectId>(java.util.Comparator.comparingInt(ProjectId::getIndex))
                .analyse(buildDependencyGraph(ctx));

        if (!analysis.acyclic()) {
            final var msg = "[DEPS]: cycle(s) detected:\n" + analysis.cycleComponents().stream()
                    .map(projectIds -> " * " + projectIds.stream()
                        .map(pId -> ctx.projectInfoTable.get(new ProjectInfoId(pId.getIndex())).manifest.name())
                        .collect(joining(" -> "))
                    )
                    .collect(joining("\n"));

            return BuildingIssueSink.empty()
                    .report(builder -> builder.error(true).message(msg));
        }

        ctx.stack = new BuildStack(analysis.traversalOrder());

        return BuildingIssueSink.empty();
    }

    private LinkedHashMap<ProjectId, LinkedHashSet<ProjectId>> buildDependencyGraph(final DependencyContext ctx) {
        final var graph = new LinkedHashMap<ProjectId, LinkedHashSet<ProjectId>>();
        for (int index = 0; index < ctx.projectTable.size(); index++) {
            graph.put(new ProjectId(index), new LinkedHashSet<>(ctx.dependenciesByProject.get(index)));
        }
        return graph;
    }
}
