package p.studio.compiler.backend.bytecode;

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

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

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

class BytecodeModuleTest {

    @Test
    void serializeMustAlwaysEmitSyscallsSection() {
        final var module = BytecodeModule.empty();

        final var bytes = module.serialize();

        assertEquals('P', bytes[0]);
        assertEquals('B', bytes[1]);
        assertEquals('S', bytes[2]);
        assertEquals(0, bytes[3]);

        final var sectionCount = readU32(bytes, 8);
        assertEquals(1, sectionCount);

        final var sectionKind = readU32(bytes, 32);
        final var sectionOffset = readU32(bytes, 36);
        final var sectionLength = readU32(bytes, 40);
        assertEquals(5, sectionKind);
        assertEquals(44, sectionOffset);
        assertEquals(4, sectionLength);

        final var syscCount = readU32(bytes, sectionOffset);
        assertEquals(0, syscCount);
    }

    @Test
    void serializeMustBeDeterministicForSameInput() {
        final var module = new BytecodeModule(
                0,
                ReadOnlyList.from(
                        new BytecodeModule.NullConstant(),
                        new BytecodeModule.Int32Constant(7),
                        new BytecodeModule.StringConstant("x")),
                ReadOnlyList.from(
                        new BytecodeModule.FunctionMeta(0, 6, 0, 0, 0, 0)),
                new byte[] { 1, 2, 3, 4, 5, 6 },
                null,
                ReadOnlyList.from(
                        new BytecodeModule.Export("main", 0)),
                ReadOnlyList.empty());

        final var first = module.serialize();
        final var second = module.serialize();

        assertArrayEquals(first, second);
    }

    @Test
    void serializeMustRejectSyscallModuleNameAboveU16() {
        final var tooLong = "x".repeat(70_000);
        final var module = new BytecodeModule(
                0,
                ReadOnlyList.empty(),
                ReadOnlyList.empty(),
                new byte[0],
                null,
                ReadOnlyList.empty(),
                ReadOnlyList.from(new BytecodeModule.SyscallDecl(tooLong, "draw", 1, 1, 0)));

        final var thrown = assertThrows(BytecodeMarshalingException.class, module::serialize);
        assertEquals(BytecodeMarshalingErrorCode.MARSHAL_FORMAT_SYSC_MODULE_TOO_LONG, thrown.code());
    }

    private static int readU32(final byte[] bytes, final int offset) {
        return ByteBuffer.wrap(bytes, offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
    }
}

