package p.studio.compiler.pbs.semantics;

import org.junit.jupiter.api.Test;
import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer;
import p.studio.compiler.pbs.parser.PbsParser;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ModuleId;
import p.studio.utilities.structures.ReadOnlyList;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PbsGlobalSemanticsValidatorTest {

    @Test
    void shouldProduceDeterministicGlobalOrderIndependentOfSourceOrder() {
        final var firstAst = parse("""
                declare global B: int = A + 1;
                declare global C: int = B + 1;
                declare global A: int = 1;
                """);
        final var secondAst = parse("""
                declare global C: int = B + 1;
                declare global A: int = 1;
                declare global B: int = A + 1;
                """);

        final var validator = new PbsGlobalSemanticsValidator();
        final var firstOrder = validator.analyse(firstAst, ModuleId.none(), ReadOnlyList.empty())
                .graphAnalysis()
                .traversalOrder()
                .asList()
                .stream()
                .map(PbsGlobalDependencySupport.GlobalRef::globalName)
                .toList();
        final var secondOrder = validator.analyse(secondAst, ModuleId.none(), ReadOnlyList.empty())
                .graphAnalysis()
                .traversalOrder()
                .asList()
                .stream()
                .map(PbsGlobalDependencySupport.GlobalRef::globalName)
                .toList();

        assertEquals(List.of("A", "B", "C"), firstOrder);
        assertEquals(firstOrder, secondOrder);
    }

    @Test
    void shouldRejectCyclicGlobalDependencies() {
        final var diagnostics = DiagnosticSink.empty();

        new PbsFrontendCompiler().compileFile(new FileId(0), """
                declare global A: int = B + 1;
                declare global B: int = C + 1;
                declare global C: int = A + 1;
                """, diagnostics);

        final var cycleCount = diagnostics.stream()
                .filter(d -> d.getCode().equals(PbsSemanticsErrors.E_SEM_GLOBAL_CYCLIC_DEPENDENCY.name()))
                .count();
        assertEquals(3, cycleCount);
        assertFalse(diagnostics.isEmpty());
    }

    private PbsAst.File parse(final String source) {
        final var diagnostics = DiagnosticSink.empty();
        final var fileId = new FileId(0);
        final var ast = PbsParser.parse(
                PbsLexer.lex(source, fileId, diagnostics),
                fileId,
                diagnostics,
                PbsParser.ParseMode.ORDINARY);
        assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        return ast;
    }
}
