package p.studio.compiler.integration;

import p.studio.compiler.backend.bytecode.BytecodeModule;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdapter {
    private final Set<String> capabilities;
    private final Map<String, String> requiredCapabilityBySyscallIdentity;
    private final String runtimeLine;

    LocalRuntimeCompatibilityAdapter() {
        this(Set.of("gfx", "input", "audio", "system"), Map.of(), "runtime-local-sim-v1");
    }

    LocalRuntimeCompatibilityAdapter(
            final Set<String> capabilities,
            final Map<String, String> requiredCapabilityBySyscallIdentity) {
        this(capabilities, requiredCapabilityBySyscallIdentity, "runtime-local-sim-v1");
    }

    LocalRuntimeCompatibilityAdapter(
            final Set<String> capabilities,
            final Map<String, String> requiredCapabilityBySyscallIdentity,
            final String runtimeLine) {
        this.capabilities = capabilities == null ? Set.of() : new HashSet<>(capabilities);
        this.requiredCapabilityBySyscallIdentity = requiredCapabilityBySyscallIdentity == null
                ? Map.of()
                : new HashMap<>(requiredCapabilityBySyscallIdentity);
        this.runtimeLine = runtimeLine == null || runtimeLine.isBlank() ? "runtime-local-sim-v1" : runtimeLine;
    }

    @Override
    public CompatibilityResult check(final BytecodeModule module) {
        final var used = new boolean[module.syscalls().size()];
        var hostcallCount = 0;
        var pc = 0;
        while (pc < module.code().length) {
            final var opcode = readU16(module.code(), pc);
            switch (opcode) {
                case 0x70 -> {
                    return new CompatibilityResult(
                            CompatibilityError.RAW_SYSCALL_IN_PRELOAD,
                            hostcallCount,
                            runtimeLine,
                            "local",
                            "raw syscall opcode is forbidden");
                }
                case 0x71 -> {
                    final var syscIndex = readU32(module.code(), pc + 2);
                    if (syscIndex < 0 || syscIndex >= module.syscalls().size()) {
                        return new CompatibilityResult(
                                CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS,
                                hostcallCount,
                                runtimeLine,
                                "local",
                                "hostcall index out of bounds");
                    }
                    used[syscIndex] = true;
                    hostcallCount++;
                    final var decl = module.syscalls().get(syscIndex);
                    final var identity = decl.module() + "::" + decl.name() + "::" + decl.version();
                    final var requiredCap = requiredCapabilityBySyscallIdentity.get(identity);
                    if (requiredCap != null && !capabilities.contains(requiredCap)) {
                        return new CompatibilityResult(
                                CompatibilityError.MISSING_CAPABILITY,
                                hostcallCount,
                                runtimeLine,
                                "local",
                                "required capability is missing");
                    }
                    pc += 6;
                }
                case 0x17, 0x50, 0x72, 0x02, 0x03, 0x04, 0x10, 0x18, 0x40, 0x41, 0x42, 0x43, 0x56 -> pc += 6;
                case 0x14, 0x15, 0x52, 0x54 -> pc += 10;
                case 0x16 -> pc += 3;
                default -> pc += 2;
            }
        }
        for (var i = 0; i < used.length; i++) {
            if (!used[i]) {
                return new CompatibilityResult(
                        CompatibilityError.UNUSED_SYSC_DECL,
                        hostcallCount,
                        runtimeLine,
                        "local",
                        "unused syscall declaration");
            }
        }
        return new CompatibilityResult(CompatibilityError.NONE, hostcallCount, runtimeLine, "local", "ok");
    }

    private int readU16(final byte[] code, final int offset) {
        return ByteBuffer.wrap(code, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
    }

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