package p.studio.compiler.workspaces.stages;

import org.junit.jupiter.api.Test;
import p.studio.compiler.backend.bytecode.BytecodeEmitter;
import p.studio.compiler.backend.irvm.IRVMProgram;
import p.studio.compiler.messages.BuilderPipelineConfig;
import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.utilities.logs.LogAggregator;
import p.studio.utilities.structures.ReadOnlyList;

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

class EmitBytecodePipelineStageTest {

    @Test
    void runMustFailWhenOptimizedIrvmIsMissing() {
        final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
        final var stage = new EmitBytecodePipelineStage();

        final var issues = stage.run(ctx, LogAggregator.empty());
        final var firstIssue = issues.asCollection().iterator().next();

        assertTrue(issues.hasErrors());
        assertEquals(1, issues.size());
        assertEquals("BACKEND_EMIT_BYTECODE", firstIssue.getPhase());
        assertEquals("MARSHAL_FORMAT_STAGE_INPUT_MISSING", firstIssue.getCode());
    }

    @Test
    void runMustFailWhenInternalOpcodesRemain() {
        final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
        ctx.optimizedIrvm = new IRVMProgram(true, BytecodeEmitter.EmissionPlan.empty());
        final var stage = new EmitBytecodePipelineStage();

        final var issues = stage.run(ctx, LogAggregator.empty());
        final var firstIssue = issues.asCollection().iterator().next();

        assertTrue(issues.hasErrors());
        assertEquals(1, issues.size());
        assertEquals("MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL", firstIssue.getCode());
    }

    @Test
    void runMustFailWhenInternalOpcodesRemainEvenWithNonEmptyEmissionPlan() {
        final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
        ctx.optimizedIrvm = new IRVMProgram(
                new p.studio.compiler.backend.irvm.IRVMModule(
                        "experimental-v1",
                        ReadOnlyList.from(new p.studio.compiler.backend.irvm.IRVMFunction(
                                "main",
                                0,
                                0,
                                0,
                                1,
                                ReadOnlyList.from(
                                        new p.studio.compiler.backend.irvm.IRVMInstruction(p.studio.compiler.backend.irvm.IRVMOp.INTERNAL_EXT, null),
                                        new p.studio.compiler.backend.irvm.IRVMInstruction(p.studio.compiler.backend.irvm.IRVMOp.RET, null))))),
                new BytecodeEmitter.EmissionPlan(
                        0,
                        ReadOnlyList.empty(),
                        ReadOnlyList.empty(),
                        ReadOnlyList.from(new BytecodeEmitter.FunctionPlan(
                                "main",
                                0,
                                0,
                                0,
                                1,
                                ReadOnlyList.from(
                                        BytecodeEmitter.Operation.halt(),
                                        BytecodeEmitter.Operation.ret())))));
        final var stage = new EmitBytecodePipelineStage();

        final var issues = stage.run(ctx, LogAggregator.empty());
        final var firstIssue = issues.asCollection().iterator().next();

        assertTrue(issues.hasErrors());
        assertEquals("MARSHAL_VERIFY_PRECHECK_INTERNAL_OPCODE_RESIDUAL", firstIssue.getCode());
    }

    @Test
    void runMustEmitBytecodeWhenPreconditionsAreSatisfied() {
        final var ctx = BuilderPipelineContext.fromConfig(new BuilderPipelineConfig(false, "."));
        ctx.optimizedIrvm = new IRVMProgram(
                false,
                new BytecodeEmitter.EmissionPlan(
                        0,
                        ReadOnlyList.empty(),
                        ReadOnlyList.empty(),
                        ReadOnlyList.from(
                                new BytecodeEmitter.FunctionPlan(
                                        "main",
                                        0,
                                        0,
                                        0,
                                        1,
                                        ReadOnlyList.from(BytecodeEmitter.Operation.halt())))));
        final var stage = new EmitBytecodePipelineStage();

        final var issues = stage.run(ctx, LogAggregator.empty());

        assertFalse(issues.hasErrors());
        assertNotNull(ctx.bytecodeModule);
        assertNotNull(ctx.bytecodeBytes);
        assertTrue(ctx.bytecodeBytes.length > 0);
        assertEquals(0, ctx.bytecodeModule.syscalls().size());
    }
}
