package p.studio.compiler.backend.bytecode;

import org.junit.jupiter.api.Test;
import p.studio.utilities.structures.ReadOnlyList;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class BytecodeFunctionLayoutBuilderTest {

    @Test
    void buildMustProduceMonotonicOffsets() {
        final var layout = BytecodeFunctionLayoutBuilder.build(ReadOnlyList.from(
                new BytecodeFunctionLayoutBuilder.FunctionFragment(
                        "main",
                        new byte[] { 1, 2, 3 },
                        1,
                        2,
                        0,
                        4,
                        ReadOnlyList.empty()),
                new BytecodeFunctionLayoutBuilder.FunctionFragment(
                        "aux",
                        new byte[] { 4, 5 },
                        0,
                        1,
                        1,
                        2,
                        ReadOnlyList.empty())));

        assertEquals(2, layout.functions().size());
        assertEquals(0, layout.functions().get(0).codeOffset());
        assertEquals(3, layout.functions().get(0).codeLen());
        assertEquals(3, layout.functions().get(1).codeOffset());
        assertEquals(2, layout.functions().get(1).codeLen());
        assertEquals(5, layout.code().length);
    }

    @Test
    void buildMustEmitDebugMinimum() {
        final var layout = BytecodeFunctionLayoutBuilder.build(ReadOnlyList.from(
                new BytecodeFunctionLayoutBuilder.FunctionFragment(
                        "main",
                        new byte[] { 8, 9 },
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(
                                new BytecodeFunctionLayoutBuilder.InstructionSpan(
                                        0,
                                        new BytecodeModule.SourceSpan(10, 0, 1)),
                                new BytecodeFunctionLayoutBuilder.InstructionSpan(
                                        1,
                                        new BytecodeModule.SourceSpan(10, 2, 3))))));

        assertEquals(1, layout.debugInfo().functionNames().size());
        assertEquals("main", layout.debugInfo().functionNames().getFirst().name());
        assertEquals(2, layout.debugInfo().pcToSpan().size());
        assertEquals(0, layout.debugInfo().pcToSpan().get(0).pc());
        assertEquals(1, layout.debugInfo().pcToSpan().get(1).pc());
    }

    @Test
    void buildMustRejectOutOfBoundsDebugPc() {
        final var thrown = assertThrows(BytecodeMarshalingException.class, () -> BytecodeFunctionLayoutBuilder.build(ReadOnlyList.from(
                new BytecodeFunctionLayoutBuilder.FunctionFragment(
                        "main",
                        new byte[] { 1 },
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(
                                new BytecodeFunctionLayoutBuilder.InstructionSpan(
                                        2,
                                        new BytecodeModule.SourceSpan(1, 0, 1)))))));

        assertEquals(BytecodeMarshalingErrorCode.MARSHAL_FORMAT_PC_SPAN_OUT_OF_BOUNDS, thrown.code());
    }
}

