package p.studio.compiler.backend.irvm;

import java.util.Objects;

enum IRVMIntrinsicDefinition {
    VEC2_DOT("vec2.dot", 1, 0x1000, "vec2", "dot", 4, 1, "float|float|float|float", "float", true, false),
    VEC2_LENGTH("vec2.length", 1, 0x1001, "vec2", "length", 2, 1, "float|float", "float", true, false),
    INPUT_PAD("input.pad", 1, 0x2000, "input", "pad", 0, 1, "", "builtin:input.pad", true, false),
    INPUT_TOUCH("input.touch", 1, 0x2001, "input", "touch", 0, 1, "", "builtin:input.touch", true, false),
    INPUT_PAD_UP("input.pad.up", 1, 0x2010, "input.pad", "up", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_DOWN("input.pad.down", 1, 0x2011, "input.pad", "down", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_LEFT("input.pad.left", 1, 0x2012, "input.pad", "left", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_RIGHT("input.pad.right", 1, 0x2013, "input.pad", "right", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_A("input.pad.a", 1, 0x2014, "input.pad", "a", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_B("input.pad.b", 1, 0x2015, "input.pad", "b", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_X("input.pad.x", 1, 0x2016, "input.pad", "x", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_Y("input.pad.y", 1, 0x2017, "input.pad", "y", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_L("input.pad.l", 1, 0x2018, "input.pad", "l", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_R("input.pad.r", 1, 0x2019, "input.pad", "r", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_START("input.pad.start", 1, 0x201A, "input.pad", "start", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_PAD_SELECT("input.pad.select", 1, 0x201B, "input.pad", "select", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
    INPUT_TOUCH_BUTTON("input.touch.button", 1, 0x2020, "input.touch", "button", 1, 1, "builtin:input.touch", "builtin:input.button", true, false),
    INPUT_TOUCH_X("input.touch.x", 1, 0x2021, "input.touch", "x", 1, 1, "builtin:input.touch", "int", true, false),
    INPUT_TOUCH_Y("input.touch.y", 1, 0x2022, "input.touch", "y", 1, 1, "builtin:input.touch", "int", true, false),
    INPUT_BUTTON_PRESSED("input.button.pressed", 1, 0x2030, "input.button", "pressed", 1, 1, "builtin:input.button", "bool", true, false),
    INPUT_BUTTON_RELEASED("input.button.released", 1, 0x2031, "input.button", "released", 1, 1, "builtin:input.button", "bool", true, false),
    INPUT_BUTTON_DOWN("input.button.down", 1, 0x2032, "input.button", "down", 1, 1, "builtin:input.button", "bool", true, false),
    INPUT_BUTTON_HOLD("input.button.hold", 1, 0x2033, "input.button", "hold", 1, 1, "builtin:input.button", "int", true, false);

    private final String canonicalName;
    private final long canonicalVersion;
    private final int finalId;
    private final String owner;
    private final String name;
    private final int argSlots;
    private final int retSlots;
    private final String argLayout;
    private final String retLayout;
    private final boolean deterministic;
    private final boolean mayAllocate;

    IRVMIntrinsicDefinition(
            final String canonicalName,
            final long canonicalVersion,
            final int finalId,
            final String owner,
            final String name,
            final int argSlots,
            final int retSlots,
            final String argLayout,
            final String retLayout,
            final boolean deterministic,
            final boolean mayAllocate) {
        this.canonicalName = requireNonBlank(canonicalName, "canonicalName");
        this.canonicalVersion = requireNonNegative(canonicalVersion, "canonicalVersion");
        this.finalId = finalId;
        this.owner = requireNonBlank(owner, "owner");
        this.name = requireNonBlank(name, "name");
        this.argSlots = requireNonNegative(argSlots, "argSlots");
        this.retSlots = requireNonNegative(retSlots, "retSlots");
        this.argLayout = Objects.requireNonNull(argLayout, "argLayout");
        this.retLayout = Objects.requireNonNull(retLayout, "retLayout");
        this.deterministic = deterministic;
        this.mayAllocate = mayAllocate;
    }

    String canonicalName() {
        return canonicalName;
    }

    long canonicalVersion() {
        return canonicalVersion;
    }

    int finalId() {
        return finalId;
    }

    String owner() {
        return owner;
    }

    String intrinsicName() {
        return name;
    }

    int argSlots() {
        return argSlots;
    }

    int retSlots() {
        return retSlots;
    }

    String argLayout() {
        return argLayout;
    }

    String retLayout() {
        return retLayout;
    }

    boolean deterministic() {
        return deterministic;
    }

    boolean mayAllocate() {
        return mayAllocate;
    }

    String canonicalIdentity() {
        return canonicalName + "@" + canonicalVersion;
    }

    private static String requireNonBlank(
            final String value,
            final String fieldName) {
        final var normalized = Objects.requireNonNull(value, fieldName);
        if (normalized.isBlank()) {
            throw new IllegalArgumentException(fieldName + " must not be blank");
        }
        return normalized;
    }

    private static int requireNonNegative(
            final int value,
            final String fieldName) {
        if (value < 0) {
            throw new IllegalArgumentException(fieldName + " must be non-negative");
        }
        return value;
    }

    private static long requireNonNegative(
            final long value,
            final String fieldName) {
        if (value < 0) {
            throw new IllegalArgumentException(fieldName + " must be non-negative");
        }
        return value;
    }
}
