package p.studio.compiler.source.diagnostics;

import p.studio.compiler.source.Span;
import p.studio.utilities.structures.ReadOnlyCollection;

import java.util.*;

public class DiagnosticSink implements ReadOnlyCollection<Diagnostic> {
    private final ArrayList<Diagnostic> diagnostics = new ArrayList<>();
    private int errorCount = 0;
    private int warningCount = 0;

    protected DiagnosticSink() {
    }

    public static DiagnosticSink empty() {
        return new DiagnosticSink();
    }

    @Override
    public Collection<Diagnostic> asCollection() {
        return diagnostics;
    }

    public DiagnosticSink report(final Diagnostic diagnostic) {
        Objects.requireNonNull(diagnostic);
        if (diagnostic.getSeverity().isError()) {
            errorCount++;
        } else {
            warningCount++;
        }
        diagnostics.add(diagnostic);
        return this;
    }

    public DiagnosticSink report(
            final Severity severity,
            final String code,
            final DiagnosticPhase phase,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span) {
        return report(severity, code, phase, templateId, placeholders, message, span, List.of());
    }

    public DiagnosticSink report(
            final Severity severity,
            final String code,
            final DiagnosticPhase phase,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span,
            final List<RelatedSpan> related) {
        return report(new Diagnostic(
                Objects.requireNonNull(severity),
                Objects.requireNonNull(code),
                Objects.requireNonNull(phase),
                Objects.requireNonNull(templateId),
                Map.copyOf(Objects.requireNonNull(placeholders)),
                Objects.requireNonNull(message),
                Objects.requireNonNull(span),
                List.copyOf(Objects.requireNonNull(related))));
    }

    public DiagnosticSink error(
            final String code,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span) {
        return report(Severity.Error, code, DiagnosticPhase.fromCode(code), templateId, placeholders, message, span);
    }

    public DiagnosticSink error(
            final String code,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span,
            final List<RelatedSpan> related) {
        return report(Severity.Error, code, DiagnosticPhase.fromCode(code), templateId, placeholders, message, span, related);
    }

    public DiagnosticSink error(
            final DiagnosticPhase phase,
            final String code,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span) {
        return report(Severity.Error, code, phase, templateId, placeholders, message, span);
    }

    public DiagnosticSink error(
            final DiagnosticPhase phase,
            final String code,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span,
            final List<RelatedSpan> related) {
        return report(Severity.Error, code, phase, templateId, placeholders, message, span, related);
    }

    public DiagnosticSink warning(
            final String code,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span) {
        return report(Severity.Warning, code, DiagnosticPhase.fromCode(code), templateId, placeholders, message, span);
    }

    public DiagnosticSink warning(
            final DiagnosticPhase phase,
            final String code,
            final String templateId,
            final Map<String, String> placeholders,
            final String message,
            final Span span) {
        return report(Severity.Warning, code, phase, templateId, placeholders, message, span);
    }

    public DiagnosticSink merge(final DiagnosticSink diagnostics) {
        Objects.requireNonNull(diagnostics);
        for (final Diagnostic diagnostic : diagnostics) {
            report(diagnostic);
        }
        return this;
    }

    public boolean hasErrors() {
        return errorCount > 0;
    }

    public boolean hasWarnings() {
        return warningCount > 0;
    }

    public int errorCount() {
        return errorCount;
    }

    public int warningCount() {
        return warningCount;
    }
}
