package p.studio.compiler.backend.irvm;

import org.junit.jupiter.api.Test;
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
import p.studio.utilities.structures.ReadOnlyList;

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

class IRVMProgramTest {

    @Test
    void constructorMustRejectModuleAndEmissionPlanMismatch() {
        final var module = new IRVMModule(
                "core-v1",
                ReadOnlyList.from(new IRVMFunction(
                        "main",
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(new IRVMInstruction(IRVMOp.HALT, null)))));
        final var mismatchedPlan = new BytecodeEmitter.EmissionPlan(
                0,
                ReadOnlyList.empty(),
                ReadOnlyList.empty(),
                ReadOnlyList.from(new BytecodeEmitter.FunctionPlan(
                        "main",
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(BytecodeEmitter.Operation.ret()))));

        assertThrows(IllegalArgumentException.class, () -> new IRVMProgram(module, mismatchedPlan));
    }

    @Test
    void coherentEmissionPlanMayBeDerivedFromModuleWhenPlanIsEmpty() {
        final var module = new IRVMModule(
                "core-v1",
                ReadOnlyList.from(new IRVMFunction(
                        "main",
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(new IRVMInstruction(IRVMOp.HALT, null)))));
        final var program = new IRVMProgram(module, BytecodeEmitter.EmissionPlan.empty());

        final var plan = program.coherentEmissionPlan();
        assertEquals(1, plan.functions().size());
        assertEquals(BytecodeEmitter.OperationKind.HALT, plan.functions().getFirst().operations().getFirst().kind());
    }

    @Test
    void coherentEmissionPlanMustRejectRawSyscallOperationInCanonicalPlan() {
        final var plan = new BytecodeEmitter.EmissionPlan(
                0,
                ReadOnlyList.empty(),
                ReadOnlyList.empty(),
                ReadOnlyList.from(new BytecodeEmitter.FunctionPlan(
                        "main",
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(BytecodeEmitter.Operation.rawSyscall(0x1001)))));
        final var program = new IRVMProgram(false, plan);

        assertThrows(IllegalArgumentException.class, program::coherentEmissionPlan);
    }

    @Test
    void coherentEmissionPlanMustRejectHostcallWithoutMetadataWhenDerived() {
        final var module = new IRVMModule(
                "core-v1",
                ReadOnlyList.from(new IRVMFunction(
                        "main",
                        0,
                        0,
                        0,
                        1,
                        ReadOnlyList.from(new IRVMInstruction(IRVMOp.HOSTCALL, 0)))));
        final var program = new IRVMProgram(module, BytecodeEmitter.EmissionPlan.empty());

        assertThrows(IllegalArgumentException.class, program::coherentEmissionPlan);
    }
}
