package p.studio.compiler.workspaces;

import lombok.extern.slf4j.Slf4j;
import p.studio.compiler.exceptions.BuildException;
import p.studio.compiler.messages.BuildingIssue;
import p.studio.compiler.models.AnalysisSnapshot;
import p.studio.compiler.models.BuildResult;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.models.CompileResult;
import p.studio.compiler.workspaces.stages.*;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyCollection;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class BuilderPipelineService {
    public final static BuilderPipelineService INSTANCE;

    static {

        final var analyses = List.<PipelineStage>of(
                new ResolveDepsPipelineStage(),
                new LoadSourcesPipelineStage(),
                new FrontendPhasePipelineStage()
        );

        final var compile = List.<PipelineStage>of(
                new LowerToIRVMPipelineStage(),
                new OptimizeIRVMPipelineStage(),
                new EmitBytecodePipelineStage(),
                new LinkBytecodePipelineStage(),
                new VerifyBytecodePipelineStage()
        );

        final var build = List.<PipelineStage>of(
                new WriteBytecodeArtifactPipelineStage()
        );

        INSTANCE = new BuilderPipelineService(analyses, compile, build);
    }

    private final List<PipelineStage> analyses;
    private final List<PipelineStage> compile;
    private final List<PipelineStage> build;

    BuilderPipelineService(
            final List<PipelineStage> analyses,
            final List<PipelineStage> compile,
            final List<PipelineStage> build) {
        this.analyses = analyses;
        this.compile = compile;
        this.build = build;
    }

    public AnalysisSnapshot analyze(
            final BuilderPipelineContext ctx,
            final LogAggregator logs) {
        final var diagnostics = run(ctx, logs, analyses, new ArrayList<>(), false);
        return new AnalysisSnapshot(
                ctx.resolvedWorkspace.frontendSpec(),
                diagnostics,
                ctx.resolvedWorkspace,
                ctx.fileTable,
                ctx.irBackend);
    }

    public CompileResult compile(
            final BuilderPipelineContext ctx,
            final LogAggregator logs) {
        final var analysisSnapshot = this.analyze(ctx, logs);
        if (hasErrors(analysisSnapshot.diagnostics())) {
            throw new BuildException("issues found on pipeline stage: analyze");
        }
        final var diagnostics = run(ctx, logs, compile, new ArrayList<>(analysisSnapshot.diagnostics()), true);
        return new CompileResult(
                analysisSnapshot,
                diagnostics,
                ctx.irvm,
                ctx.optimizedIrvm,
                ctx.bytecodeModule,
                ctx.bytecodeBytes);
    }

    public BuildResult build(
            final BuilderPipelineContext ctx,
            final LogAggregator logs) {
        final var compileResult = this.compile(ctx, logs);
        final var diagnostics = run(ctx, logs, build, new ArrayList<>(compileResult.diagnostics()), true);
        return new BuildResult(
                compileResult,
                diagnostics,
                ctx.bytecodeArtifactPath);
    }

    private List<BuildingIssue> run(
            final BuilderPipelineContext ctx,
            final LogAggregator logs,
            final List<PipelineStage> stages,
            final List<BuildingIssue> diagnostics,
            final boolean failFast) {
        for (final var builderPipelineStage : stages) {
            final var issues = builderPipelineStage.run(ctx, logs);
            diagnostics.addAll(issues.asCollection());
            printIssues(issues, logs);
            if (issues.hasErrors()) {
                if (failFast) {
                    throw new BuildException("issues found on pipeline stage: " + builderPipelineStage.getClass().getSimpleName());
                }
                break;
            }
        }
        logs.using(log).info("builder pipeline completed successfully");
        return List.copyOf(diagnostics);
    }

    private boolean hasErrors(final List<BuildingIssue> diagnostics) {
        for (final var diagnostic : diagnostics) {
            if (diagnostic.isError()) {
                return true;
            }
        }
        return false;
    }

    private void printIssues(
            final ReadOnlyCollection<BuildingIssue> issues,
            final LogAggregator logs) {
        if (ReadOnlyCollection.isEmpty(issues)) {
            return;
        }
        for (final var issue : issues) {
            issue.print(logs);
        }
    }
}
