package p.studio.compiler.pbs.stdlib;

import org.junit.jupiter.api.Test;
import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.tables.FileTable;
import p.studio.compiler.source.tables.ProjectTable;
import p.studio.utilities.structures.ReadOnlyList;

import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.*;

class InterfaceModuleLoaderTest {

    @Test
    void shouldRegisterSyntheticStdlibFilesInFileTable() {
        final var projectTable = new ProjectTable();
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .name("app")
                .version("1.0.0")
                .rootPath(Path.of("/tmp/app"))
                .sourceRoots(ReadOnlyList.wrap(java.util.List.of(Path.of("/tmp/app/src"))))
                .build());
        final var fileTable = new FileTable(1);
        final var diagnostics = DiagnosticSink.empty();
        final var moduleSource = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(java.util.List.of("gfx")),
                ReadOnlyList.wrap(java.util.List.of(new StdlibModuleSource.SourceFile(
                        "source.pbs",
                        "declare host Gfx { fn draw() -> void; }"))),
                "pub host Gfx;");

        final var module = new InterfaceModuleLoader().load(moduleSource, projectId, fileTable, diagnostics);

        assertTrue(diagnostics.isEmpty());
        assertEquals(1, module.sourceFiles().size());
        assertEquals(1, module.barrelFiles().size());
        assertFalse(module.sourceFiles().getFirst().fileId().isNone());
        assertFalse(module.barrelFiles().getFirst().fileId().isNone());
        assertTrue(fileTable.get(module.sourceFiles().getFirst().fileId()).readUtf8().isPresent());
        assertTrue(fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().isPresent());
        assertEquals(
                "declare host Gfx { fn draw() -> void; }",
                fileTable.get(module.sourceFiles().getFirst().fileId()).readUtf8().orElseThrow());
        assertEquals(
                "pub host Gfx;",
                fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow());
    }

    @Test
    void shouldLoadAssetStdlibModuleWithPublicHostBarrel() {
        final var projectTable = new ProjectTable();
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .name("app")
                .version("1.0.0")
                .rootPath(Path.of("/tmp/app"))
                .sourceRoots(ReadOnlyList.wrap(java.util.List.of(Path.of("/tmp/app/src"))))
                .build());
        final var fileTable = new FileTable(1);
        final var diagnostics = DiagnosticSink.empty();
        final var moduleSource = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(java.util.List.of("asset")),
                ReadOnlyList.wrap(java.util.List.of(new StdlibModuleSource.SourceFile(
                        "main.pbs",
                        """
                        declare host LowAssets {
                          [Host(module = "asset", name = "load", version = 1)]
                          [Capability(name = "asset")]
                          [AssetLowering(param = 0)]
                          fn load(addressable: Addressable, slot: int) -> (status: int, loading_handle: int);
                        }
                        """))),
                "pub host LowAssets;");

        final var module = new InterfaceModuleLoader().load(moduleSource, projectId, fileTable, diagnostics);

        assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        assertEquals(1, module.sourceFiles().size());
        assertEquals(1, module.barrelFiles().size());
        assertEquals(
                "pub host LowAssets;",
                fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow());
    }

    @Test
    void shouldLoadComposerStdlibModuleWithPublicServiceBarrel() {
        final var projectTable = new ProjectTable();
        final var projectId = projectTable.register(ProjectDescriptor.builder()
                .name("app")
                .version("1.0.0")
                .rootPath(Path.of("/tmp/app"))
                .sourceRoots(ReadOnlyList.wrap(java.util.List.of(Path.of("/tmp/app/src"))))
                .build());
        final var fileTable = new FileTable(1);
        final var diagnostics = DiagnosticSink.empty();
        final var moduleSource = new StdlibModuleSource(
                "sdk",
                ReadOnlyList.wrap(java.util.List.of("composer")),
                ReadOnlyList.wrap(java.util.List.of(new StdlibModuleSource.SourceFile(
                        "main.pbs",
                        """
                        declare host LowComposer {
                          [Host(module = "composer", name = "bind_scene", version = 1)]
                          [Capability(name = "gfx")]
                          fn bind_scene(scene_bank_id: int) -> int;

                          [Host(module = "composer", name = "unbind_scene", version = 1)]
                          [Capability(name = "gfx")]
                          fn unbind_scene() -> int;

                          [Host(module = "composer", name = "emit_sprite", version = 1)]
                          [Capability(name = "gfx")]
                          fn emit_sprite(
                            glyph_id: int,
                            palette_id: int,
                            x: int,
                            y: int,
                            layer: int,
                            bank_id: int,
                            flip_x: bool,
                            flip_y: bool,
                            priority: int
                          ) -> int;
                        }

                        declare service Composer {
                          fn bind_scene(scene_bank_id: int) -> int {
                            return LowComposer.bind_scene(scene_bank_id);
                          }

                          fn unbind_scene() -> int {
                            return LowComposer.unbind_scene();
                          }

                          fn emit_sprite(
                            glyph_id: int,
                            palette_id: int,
                            x: int,
                            y: int,
                            layer: int,
                            bank_id: int,
                            flip_x: bool,
                            flip_y: bool,
                            priority: int
                          ) -> int {
                            return LowComposer.emit_sprite(glyph_id, palette_id, x, y, layer, bank_id, flip_x, flip_y, priority);
                          }
                        }
                        """))),
                """
                mod host LowComposer;
                pub service Composer;
                """);

        final var module = new InterfaceModuleLoader().load(moduleSource, projectId, fileTable, diagnostics);

        assertTrue(diagnostics.isEmpty(), diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList().toString());
        assertEquals(1, module.sourceFiles().size());
        assertEquals(1, module.barrelFiles().size());
        assertEquals(
                """
                mod host LowComposer;
                pub service Composer;
                """.strip(),
                fileTable.get(module.barrelFiles().getFirst().fileId()).readUtf8().orElseThrow().strip());
    }
}
