const std = @import("std");

allocator: std.mem.Allocator,

magic: u32,
minor_version: u16,
major_version: u16,
constant_pool: []?CpInfo,
access_flags: u16,
this_class: u16,
super_class: u16,
interfaces: []u16,
fields: []FieldInfo,
methods: []MethodInfo,
attributes: []AttributeInfo,

const Self = @This();

const ParseError = std.Io.Reader.Error || std.mem.Allocator.Error || error {
    InvalidMagicNumber,
    InvalidTag,
};

const ResolveError = error {
    InvalidConstantPoolIndex,
    InvalidConstantType,
};

pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!Self {
    const magic = try input.takeInt(u32, .big);
    if (magic != 0xCAFEBABE) {
        return ParseError.InvalidMagicNumber;
    }

    const minor_version = try input.takeInt(u16, .big);
    const major_version = try input.takeInt(u16, .big);

    const constant_pool = try parseConstantPool(input, allocator);
    errdefer {
        for (constant_pool) |*cp_info| {
            if (cp_info.*) |*c| c.deinit(allocator);
        }
        allocator.free(constant_pool);
    }

    const access_flags = try input.takeInt(u16, .big);

    const this_class = try input.takeInt(u16, .big);
    const super_class = try input.takeInt(u16, .big);

    const interfaces = try parseInterfaces(input, allocator);
    errdefer allocator.free(interfaces);

    const fields = try parseFields(input, allocator);
    errdefer {
        for (fields) |*field| {
            field.deinit(allocator);
        }
        allocator.free(fields);
    }

    const methods = try parseMethods(input, allocator);
    errdefer {
        for (methods) |*method| {
            method.deinit(allocator);
        }
        allocator.free(methods);
    }

    const attributes = try parseAttributes(input, allocator);
    errdefer {
        for (attributes) |*attribute| {
            attribute.deinit(allocator);
        }
        allocator.free(attributes);
    }

    return .{
        .allocator = allocator,
        .magic = magic,
        .minor_version = minor_version,
        .major_version = major_version,
        .constant_pool = constant_pool,
        .access_flags = access_flags,
        .this_class = this_class,
        .super_class = super_class,
        .interfaces = interfaces,
        .fields = fields,
        .methods = methods,
        .attributes = attributes,
    };
}

fn parseConstantPool(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]?CpInfo {
    const constant_pool_size = try input.takeInt(u16, .big);
    const constant_pool = try allocator.alloc(?CpInfo, constant_pool_size - 1);
    errdefer allocator.free(constant_pool);

    var i: usize = 0;
    while (i < constant_pool_size - 1) {
        defer i += 1;

        const cp_info = try CpInfo.parse(input, allocator);
        constant_pool[i] = cp_info;

        switch (cp_info) {
            .long, .double => {
                i += 1;
                constant_pool[i] = null;
            },
            else => {},
        }
    }

    return constant_pool;
}

fn parseInterfaces(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]u16 {
    const interfaces_count = try input.takeInt(u16, .big);
    const interfaces = try allocator.alloc(u16, interfaces_count);
    errdefer allocator.free(interfaces);

    for (0..interfaces_count) |i| {
        interfaces[i] = try input.takeInt(u16, .big);
    }

    return interfaces;
}

fn parseFields(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]FieldInfo {
    const fields_count = try input.takeInt(u16, .big);
    const fields = try allocator.alloc(FieldInfo, fields_count);
    errdefer allocator.free(fields);

    for (0..fields_count) |i| {
        fields[i] = try FieldInfo.parse(input, allocator);
    }

    return fields;
}

fn parseMethods(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]MethodInfo {
    const methods_count = try input.takeInt(u16, .big);
    const methods = try allocator.alloc(MethodInfo, methods_count);
    errdefer allocator.free(methods);

    for (0..methods_count) |i| {
        methods[i] = try MethodInfo.parse(input, allocator);
    }

    return methods;
}

fn parseAttributes(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError![]AttributeInfo {
    const attributes_count = try input.takeInt(u16, .big);
    const attributes = try allocator.alloc(AttributeInfo, attributes_count);
    errdefer allocator.free(attributes);

    for (0..attributes_count) |i| {
        attributes[i] = try AttributeInfo.parse(input, allocator);
    }

    return attributes;
}

pub fn deinit(self: *Self) void {
    self.freeConstantPool();
    self.freeInterfaces();
    self.freeFields();
    self.freeMethods();
    self.freeAttributes();
}

fn freeConstantPool(self: *Self) void {
    for (self.constant_pool) |*cp_info| {
        if (cp_info.*) |*c| c.deinit(self.allocator);
    }
    self.allocator.free(self.constant_pool);
}

fn freeInterfaces(self: *Self) void {
    self.allocator.free(self.interfaces);
}

fn freeFields(self: *Self) void {
    for (self.fields) |*field_info| {
        field_info.deinit(self.allocator);
    }
    self.allocator.free(self.fields);
}

fn freeMethods(self: *Self) void {
    for (self.methods) |*method_info| {
        method_info.deinit(self.allocator);
    }
    self.allocator.free(self.methods);
}

fn freeAttributes(self: *Self) void {
    for (self.attributes) |*attr| {
        attr.deinit(self.allocator);
    }
    self.allocator.free(self.attributes);
}

pub fn jsonStringify(self: *const Self, jws: *std.json.Stringify) !void {
    try jws.beginObject();

    inline for (std.meta.fields(Self)) |field| {
        if (comptime std.mem.eql(u8, field.name, "allocator")) continue;
        try jws.objectField(field.name);
        try jws.write(@field(self, field.name));
    }

    try jws.endObject();
}

pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
    const depth = 2;
    var indent: u8 = 0;

    try w.print("magic: 0x{X}\n", .{ self.magic });

    try w.print("version: {d}.{d}\n", .{ self.major_version, self.minor_version });

    const this_class = self.thisClass() catch return error.WriteFailed;
    _ = try w.write("this class:\n");
    try self.formatCpInfo(w, depth, indent + 1, &.{ .class = this_class });

    if (self.superClass() catch return error.WriteFailed) |super_class| {
        _ = try w.write("super class:\n");
        try self.formatCpInfo(w, depth, indent + 1, &.{ .class = super_class });
    }

    _ = try w.write("interfaces:\n");
    for (self.interfaces, 0..) |interface, i| {
        indent += 1;
        defer indent -= 1;

        if (interface >= self.constant_pool.len) return error.WriteFailed;
        const cp_info = self.constant_pool[interface - 1];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return error.WriteFailed;

        try w.splatByteAll(' ', depth * indent);
        try w.print("{d}:\n", .{ i });
        try self.formatCpInfo(w, depth, indent + 1, &cp_info.?);
    }

    _ = try w.write("fields:\n");
    for (self.fields, 0..) |field, i| {
        indent += 1;
        defer indent -= 1;

        try w.splatByteAll(' ', depth * indent);
        try w.print("{d}:\n", .{ i });

        indent += 1;
        defer indent -= 1;

        try w.splatByteAll(' ', depth * indent);
        try w.print("flags: {d}\n", .{ field.access_flags });

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("name:\n");
        const field_name = field.name(self.constant_pool) catch return error.WriteFailed;
        try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = field_name });

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("descriptor:\n");
        const field_descriptor = field.descriptor(self.constant_pool) catch return error.WriteFailed;
        try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = field_descriptor });

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("attributes:\n");
        for (field.attributes, 0..) |*attribute, j| {
            indent += 1;
            defer indent -= 1;

            try w.splatByteAll(' ', depth * indent);
            try w.print("{d}:\n", .{ j });

            try self.formatAttribute(w, depth, indent + 1, attribute);
        }
    }

    _ = try w.write("methods:\n");
    for (self.methods, 0..) |method, i| {
        indent += 1;
        defer indent -= 1;

        try w.splatByteAll(' ', depth * indent);
        try w.print("{d}:\n", .{ i });

        indent += 1;
        defer indent -= 1;

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("name:\n");
        const method_name = method.name(self.constant_pool) catch return error.WriteFailed;
        try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = method_name });

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("descriptor:\n");
        const method_descriptor = method.descriptor(self.constant_pool) catch return error.WriteFailed;
        try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = method_descriptor });

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("attributes:\n");
        for (method.attributes, 0..) |*attribute, j| {
            indent += 1;
            defer indent -= 1;

            try w.splatByteAll(' ', depth * indent);
            try w.print("{d}:\n", .{ j });

            try self.formatAttribute(w, depth, indent + 1, attribute);
        }
    }

    _ = try w.write("attributes:\n");
    for (self.attributes, 0..) |*attribute, i| {
        indent += 1;
        defer indent -= 1;

        try w.splatByteAll(' ', depth * indent);
        try w.print("{d}:\n", .{ i });

        try self.formatAttribute(w, depth, indent + 1, attribute);
    }
}

fn formatAttribute(self: *const Self, w: *std.Io.Writer, depth: usize, indent: u8, attribute: *const AttributeInfo) std.Io.Writer.Error!void {
    try w.splatByteAll(' ', depth * indent);
    _ = try w.write("name:\n");
    const attribute_name = attribute.attributeName(self.constant_pool) catch return error.WriteFailed;
    try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = attribute_name });

    var predefined = attribute.resolve(self.allocator, self.constant_pool) catch return error.WriteFailed;
    if (predefined) |*p| {
        defer p.deinit(self.allocator);

        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("predefined: yes\n");

        try w.splatByteAll(' ', depth * indent);
        try w.print("type: {s}\n", .{ @tagName(p.*) });

        switch (p.*) {
            .constant_value => |attr| {
                const constant_value = attr.constantValue(self.constant_pool) catch return error.WriteFailed;
                try self.formatCpInfo(w, depth, indent + 1, &constant_value);
            },
            .code => |attr| {
                try w.splatByteAll(' ', depth * indent);
                try w.print("max_stack: {d}\n", .{ attr.max_stack });

                try w.splatByteAll(' ', depth * indent);
                try w.print("max_locals: {d}\n", .{ attr.max_locals });

                // TODO: Properly parse and format instructions
            },
            //.stack_map_table,
            //.exceptions,
            //.inner_classes,
            //.enclosing_method,
            .synthetic => {},
            .signature => |attr| {
                const signature = attr.signature(self.constant_pool) catch return error.WriteFailed;
                try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = signature });
            },
            .source_file => |attr| {
                const source_file = attr.sourceFile(self.constant_pool) catch return error.WriteFailed;
                try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = source_file });
            },
            //.source_debug_extension,
            //.line_number_table,
            //.local_variable_table,
            //.local_variable_type_table,
            //.deprecated,
            //.runtime_visible_annotations,
            //.runtime_invisible_annotations,
            //.runtime_visible_parameter_annotations,
            //.runtime_invisible_parameter_annotations,
            //.annotation_default,
            //.bootstrap_methods,
            else => {},
        }
    } else {
        try w.splatByteAll(' ', depth * indent);
        _ = try w.write("predefined: no\n");
    }
}

fn formatCpInfo(self: *const Self, w: *std.Io.Writer, depth: usize, indent: u8, cp_info: *const CpInfo) std.Io.Writer.Error!void {
    try w.splatByteAll(' ', depth * indent);
    try w.print("constant_value_type: {s}\n", .{ @tagName(cp_info.*) });

    switch (cp_info.*) {
        .class => |class| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("name:\n");
            const class_name = class.name(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = class_name });
        },
        .field_ref => |field_ref| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("class:\n");
            const class = field_ref.class(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .class = class });

            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("name_and_type:\n");
            const name_and_type = field_ref.nameAndType(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type });
        },
        .method_ref => |method_ref| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("class:\n");
            const class = method_ref.class(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .class = class });

            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("name_and_type:\n");
            const name_and_type = method_ref.nameAndType(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type });
        },
        .interface_method_ref => |interface_method_ref| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("class:\n");
            const class = interface_method_ref.class(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .class = class });

            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("name_and_type:\n");
            const name_and_type = interface_method_ref.nameAndType(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type });
        },
        .string => |string| {
            const s = string.string(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = s });
        },
        .integer => |integer| {
            try w.splatByteAll(' ', depth * indent);
            try w.print("integer: {d}\n", .{ integer.bytes });
        },
        .float => |float| {
            const f: f32 = @bitCast(float.bytes);

            try w.splatByteAll(' ', depth * indent);
            try w.print("float: {d}\n", .{ f });
        },
        .long => |long| {
            const l: u64 = (@as(u64, long.high_bytes) << 32) | long.low_bytes;

            try w.splatByteAll(' ', depth * indent);
            try w.print("long: {d}\n", .{ l });
        },
        .double => |double| {
            const l: u64 = (@as(u64, double.high_bytes) << 32) | double.low_bytes;
            const d: f64 = @bitCast(l);

            try w.splatByteAll(' ', depth * indent);
            try w.print("double: {d}\n", .{ d });
        },
        .name_and_type => |name_and_type| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("name:\n");
            const name = name_and_type.name(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = name });

            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("descriptor:\n");
            const descriptor = name_and_type.descriptor(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = descriptor });
        },
        .utf8 => |utf8| {
            try w.splatByteAll(' ', depth * indent);
            try w.print("bytes: \"{s}\"\n", .{ utf8.bytes });
        },
        .method_handle => |method_handle| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("reference:\n");
            const reference = method_handle.reference(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &reference);
        },
        .method_type => |method_type| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("descriptor:\n");
            const descriptor = method_type.descriptor(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .utf8 = descriptor });
        },
        .invoke_dynamic => |invoke_dynamic| {
            try w.splatByteAll(' ', depth * indent);
            _ = try w.write("name_and_type:\n");
            const name_and_type = invoke_dynamic.nameAndType(self.constant_pool) catch return error.WriteFailed;
            try self.formatCpInfo(w, depth, indent + 1, &.{ .name_and_type = name_and_type });
        },
    }
}

pub fn thisClass(self: *const Self) ResolveError!CpInfo.Class {
    const this_class = self.this_class - 1;
    if (this_class >= self.constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
    const cp_info = self.constant_pool[this_class];
    if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType;
    return cp_info.?.class;
}

pub fn superClass(self: *const Self) ResolveError!?CpInfo.Class {
    // TODO: Determine whether this is an interface or a regular class.
    //       When this is an interface, assert that the super class must be Object.

    if (self.super_class == 0) {
        const this_class = try self.thisClass();
        const this_class_name = try this_class.name(self.constant_pool);
        if (!std.mem.eql(u8, this_class_name.bytes, "Object")) return ResolveError.InvalidConstantType;
        return null;
    } else {
        const super_class = self.super_class - 1;
        if (super_class >= self.constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
        const cp_info = self.constant_pool[super_class];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType;
        // TODO: Assert that superclass does not have ACC_FINAL flag set.
        return cp_info.?.class;
    }
}

pub const CpInfo = union(CpInfo.Tag) {
    class: Class,
    field_ref: FieldRef,
    method_ref: MethodRef,
    interface_method_ref: InterfaceMethodRef,
    string: String,
    integer: Integer,
    float: Float,
    long: Long,
    double: Double,
    name_and_type: NameAndType,
    utf8: Utf8,
    method_handle: MethodHandle,
    method_type: MethodType,
    invoke_dynamic: InvokeDynamic,

    pub const Tag = enum(u8) {
        class = 7,
        field_ref = 9,
        method_ref = 10,
        interface_method_ref = 11,
        string = 8,
        integer = 3,
        float = 4,
        long = 5,
        double = 6,
        name_and_type = 12,
        utf8 = 1,
        method_handle = 15,
        method_type = 16,
        invoke_dynamic = 18,
    };

    pub const Class = struct {
        name_index: u16,

        pub fn name(self: *const Class, constant_pool: []const ?CpInfo) ResolveError!Utf8 {
            const name_index = self.name_index - 1;
            if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[name_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
            return cp_info.?.utf8;
        }
    };

    pub const FieldRef = struct {
        class_index: u16,
        name_and_type_index: u16,

        pub fn class(self: *const FieldRef, constant_pool: []const ?CpInfo) ResolveError!Class {
            const class_index = self.class_index - 1;
            if (class_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[class_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType;
            return cp_info.?.class;
        }

        pub fn nameAndType(self: *const FieldRef, constant_pool: []const ?CpInfo) ResolveError!NameAndType {
            const name_and_type_index = self.name_and_type_index - 1;
            if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[name_and_type_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType;
            return cp_info.?.name_and_type;
        }
    };

    pub const MethodRef = struct {
        class_index: u16,
        name_and_type_index: u16,

        pub fn class(self: *const MethodRef, constant_pool: []const ?CpInfo) ResolveError!Class {
            const class_index = self.class_index - 1;
            if (class_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[class_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType;
            return cp_info.?.class;
        }

        pub fn nameAndType(self: *const MethodRef, constant_pool: []const ?CpInfo) ResolveError!NameAndType {
            const name_and_type_index = self.name_and_type_index - 1;
            if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[name_and_type_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType;
            return cp_info.?.name_and_type;
        }
    };

    pub const InterfaceMethodRef = struct {
        class_index: u16,
        name_and_type_index: u16,

        pub fn class(self: *const InterfaceMethodRef, constant_pool: []const ?CpInfo) ResolveError!Class {
            const class_index = self.class_index - 1;
            if (class_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[class_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .class) return ResolveError.InvalidConstantType;
            return cp_info.?.class;
        }

        pub fn nameAndType(self: *const InterfaceMethodRef, constant_pool: []const ?CpInfo) ResolveError!NameAndType {
            const name_and_type_index = self.name_and_type_index - 1;
            if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[name_and_type_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType;
            return cp_info.?.name_and_type;
        }
    };

    pub const String = struct {
        string_index: u16,

        pub fn string(self: *const String, constant_pool: []const ?CpInfo) ResolveError!Utf8 {
            const string_index = self.string_index - 1;
            if (string_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[string_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
            return cp_info.?.utf8;
        }
    };

    pub const Integer = struct {
        bytes: u32,
    };

    pub const Float = struct {
        bytes: u32,
    };

    pub const Long = struct {
        high_bytes: u32,
        low_bytes: u32,
    };

    pub const Double = struct {
        high_bytes: u32,
        low_bytes: u32,
    };

    pub const NameAndType = struct {
        name_index: u16,
        descriptor_index: u16,

        pub fn name(self: *const NameAndType, constant_pool: []const ?CpInfo) ResolveError!Utf8 {
            const name_index = self.name_index - 1;
            if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[name_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
            return cp_info.?.utf8;
        }

        pub fn descriptor(self: *const NameAndType, constant_pool: []const ?CpInfo) ResolveError!Utf8 {
            const descriptor_index = self.descriptor_index - 1;
            if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[descriptor_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
            return cp_info.?.utf8;
        }
    };

    pub const Utf8 = struct {
        bytes: []const u8,
    };

    pub const MethodHandle = struct {
        reference_kind: Kind,
        reference_index: u16,

        pub const Kind = enum(u8) {
            get_field = 1,
            get_static = 2,
            put_field = 3,
            put_static = 4,
            invoke_virtual = 5,
            invoke_static = 6,
            invoke_special = 7,
            new_invoke_special = 8,
            invoke_interface = 9,
        };

        pub fn reference(self: *const MethodHandle, constant_pool: []const ?CpInfo) ResolveError!CpInfo {
            const reference_index = self.reference_index - 1;
            if (reference_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[reference_index];
            return switch (self.reference_kind) {
                .get_field, .get_static, .put_field, .put_static => blk: {
                    if (cp_info == null or @as(Tag, cp_info.?) != .field_ref) break :blk ResolveError.InvalidConstantType;
                    break :blk cp_info.?;
                },
                .invoke_virtual, .invoke_static, .invoke_special, .new_invoke_special => blk: {
                    if (cp_info == null or @as(Tag, cp_info.?) != .method_ref) break :blk ResolveError.InvalidConstantType;
                    break :blk cp_info.?;
                },
                .invoke_interface => blk: {
                    if (cp_info == null or @as(Tag, cp_info.?) != .interface_method_ref) break :blk ResolveError.InvalidConstantType;
                    break :blk cp_info.?;
                },
            };
        }
    };

    pub const MethodType = struct {
        descriptor_index: u16,

        pub fn descriptor(self: *const MethodType, constant_pool: []const ?CpInfo) ResolveError!Utf8 {
            const descriptor_index = self.descriptor_index - 1;
            if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[descriptor_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
            return cp_info.?.utf8;
        }
    };

    pub const InvokeDynamic = struct {
        bootstrap_method_attr_index: u16,
        name_and_type_index: u16,

        pub fn nameAndType(self: *const InvokeDynamic, constant_pool: []const ?CpInfo) ResolveError!NameAndType {
            const name_and_type_index = self.name_and_type_index - 1;
            if (name_and_type_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
            const cp_info = constant_pool[name_and_type_index];
            if (cp_info == null or @as(Tag, cp_info.?) != .name_and_type) return ResolveError.InvalidConstantType;
            return cp_info.?.name_and_type;
        }
    };

    pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!CpInfo {
        const tag_byte = try input.takeByte();
        const tag = std.enums.fromInt(Tag, tag_byte) orelse return ParseError.InvalidTag;

        return switch (tag) {
            .class => .{
                .class = .{
                    .name_index = try input.takeInt(u16, .big),
                },
            },
            .field_ref => .{
                .field_ref = .{
                    .class_index = try input.takeInt(u16, .big),
                    .name_and_type_index = try input.takeInt(u16, .big),
                },
            },
            .method_ref => .{
                .method_ref = .{
                    .class_index = try input.takeInt(u16, .big),
                    .name_and_type_index = try input.takeInt(u16, .big),
                },
            },
            .interface_method_ref => .{
                .interface_method_ref = .{
                    .class_index = try input.takeInt(u16, .big),
                    .name_and_type_index = try input.takeInt(u16, .big),
                },
            },
            .string => .{
                .string = .{
                    .string_index = try input.takeInt(u16, .big),
                },
            },
            .integer => .{
                .integer = .{
                    .bytes = try input.takeInt(u32, .big),
                },
            },
            .float => .{
                .integer = .{
                    .bytes = try input.takeInt(u32, .big),
                },
            },
            .long => .{
                .long = .{
                    .high_bytes = try input.takeInt(u32, .big),
                    .low_bytes = try input.takeInt(u32, .big),
                },
            },
            .double => .{
                .double = .{
                    .high_bytes = try input.takeInt(u32, .big),
                    .low_bytes = try input.takeInt(u32, .big),
                },
            },
            .name_and_type => .{
                .name_and_type = .{
                    .name_index = try input.takeInt(u16, .big),
                    .descriptor_index = try input.takeInt(u16, .big),
                },
            },
            .utf8 => blk: {
                const length = try input.takeInt(u16, .big);
                const bytes = try input.readAlloc(allocator, length);

                break :blk .{
                    .utf8 = .{
                        .bytes = bytes,
                    },
                };
            },
            .method_handle => blk: {
                const kind_byte = try input.takeByte();
                const kind = std.enums.fromInt(MethodHandle.Kind, kind_byte) orelse return ParseError.InvalidTag;

                break :blk .{
                    .method_handle = .{
                        .reference_kind = kind,
                        .reference_index = try input.takeInt(u16, .big),
                    },
                };
            },
            .method_type => .{
                .method_type = .{
                    .descriptor_index = try input.takeInt(u16, .big),
                },
            },
            .invoke_dynamic => .{
                .invoke_dynamic = .{
                    .bootstrap_method_attr_index = try input.takeInt(u16, .big),
                    .name_and_type_index = try input.takeInt(u16, .big),
                },
            },
        };
    }

    pub fn deinit(self: *CpInfo, allocator: std.mem.Allocator) void {
        switch (self.*) {
            .utf8 => |info| allocator.free(info.bytes),
            else => {},
        }
    }
};

pub const FieldInfo = struct {
    access_flags: u16,
    name_index: u16,
    descriptor_index: u16,
    attributes: []AttributeInfo,

    pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) !FieldInfo {
        const access_flags = try input.takeInt(u16, .big);
        const name_index = try input.takeInt(u16, .big);
        const descriptor_index = try input.takeInt(u16, .big);
        const attributes = try parseAttributes(input, allocator);
        return .{
            .access_flags = access_flags,
            .name_index = name_index,
            .descriptor_index = descriptor_index,
            .attributes = attributes,
        };
    }

    pub fn deinit(self: *FieldInfo, allocator: std.mem.Allocator) void {
        for (self.attributes) |*attr_info| {
            attr_info.deinit(allocator);
        }
        allocator.free(self.attributes);
    }

    pub fn name(self: *const FieldInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
        const name_index = self.name_index - 1;
        if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
        const cp_info = constant_pool[name_index];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
        return cp_info.?.utf8;
    }

    pub fn descriptor(self: *const FieldInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
        const descriptor_index = self.descriptor_index - 1;
        if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
        const cp_info = constant_pool[descriptor_index];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
        return cp_info.?.utf8;
    }
};

pub const MethodInfo = struct {
    access_flags: u16,
    name_index: u16,
    descriptor_index: u16,
    attributes: []AttributeInfo,

    pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) !MethodInfo {
        const access_flags = try input.takeInt(u16, .big);
        const name_index = try input.takeInt(u16, .big);
        const descriptor_index = try input.takeInt(u16, .big);
        const attributes = try parseAttributes(input, allocator);
        return .{
            .access_flags = access_flags,
            .name_index = name_index,
            .descriptor_index = descriptor_index,
            .attributes = attributes,
        };
    }

    pub fn deinit(self: *MethodInfo, allocator: std.mem.Allocator) void {
        for (self.attributes) |*attr_info| {
            attr_info.deinit(allocator);
        }
        allocator.free(self.attributes);
    }

    pub fn name(self: *const MethodInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
        const name_index = self.name_index - 1;
        if (name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
        const cp_info = constant_pool[name_index];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
        return cp_info.?.utf8;
    }

    pub fn descriptor(self: *const MethodInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
        const descriptor_index = self.descriptor_index - 1;
        if (descriptor_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
        const cp_info = constant_pool[descriptor_index];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
        return cp_info.?.utf8;
    }
};

pub const AttributeInfo = struct {
    attribute_name_index: u16,
    info: []u8,

    pub const Predefined = union(enum) {
        constant_value: ConstantValue,
        code: Code,
        stack_map_table: StackMapTable,
        exceptions: Exceptions,
        inner_classes: InnerClasses,
        enclosing_method: EnclosingMethod,
        synthetic: Synthetic,
        signature: Signature,
        source_file: SourceFile,
        source_debug_extension: SourceDebugExtension,
        line_number_table: LineNumberTable,
        local_variable_table: LocalVariableTable,
        local_variable_type_table: LocalVariableTypeTable,
        deprecated: Deprecated,
        runtime_visible_annotations: RuntimeVisibleAnnotations,
        runtime_invisible_annotations: RuntimeInvisibleAnnotations,
        runtime_visible_parameter_annotations: RuntimeVisibleParameterAnnotations,
        runtime_invisible_parameter_annotations: RuntimeInvisibleParameterAnnotations,
        annotation_default: AnnotationDefault,
        bootstrap_methods: BootstrapMethods,

        pub const ConstantValue = struct {
            constant_value_index: u16,

            pub fn constantValue(self: *const ConstantValue, constant_pool: []const ?CpInfo) ResolveError!CpInfo {
                const constant_value_index = self.constant_value_index - 1;
                if (constant_value_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
                const cp_info = constant_pool[constant_value_index];
                if (cp_info == null) return ResolveError.InvalidConstantType;

                return cp_info.?;
            }
        };

        pub const Code = struct {
            max_stack: u16,
            max_locals: u16,
            code: []u8,
            exception_table: []ExceptionHandler,
            attributes: []AttributeInfo,

            pub const ExceptionHandler = struct {
                start_pc: u16,
                end_pc: u16,
                handler_pc: u16,
                catch_type: u16,
            };
        };

        pub const StackMapTable = struct {
            entries: []StackMapFrame,

            pub const StackMapFrame = union(enum) {
                // 0-63
                same_frame,
                // 64-127
                same_locals_1_stack_item_frame: struct {
                    stack: [1]VerificationTypeInfo,
                },
                // 247
                same_locals_1_stack_item_frame_extended: struct {
                    offset_delta: u16,
                    stack: [1]VerificationTypeInfo,
                },
                // 248-250
                chop_frame: struct {
                    offset_delta: u16,
                },
                // 251
                same_frame_extended: struct {
                    offset_delta: u16,
                },
                // 252-254
                append_frame: struct {
                    offset_delta: u16,
                    locals: []const VerificationTypeInfo,
                },
                // 255
                full_frame: struct {
                    offset_delta: u16,
                    locals: []const VerificationTypeInfo,
                    stack: []const VerificationTypeInfo,
                },

                pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!StackMapFrame {
                    const frame_type = try input.takeByte();
                    return switch (frame_type) {
                        0...63 => .same_frame,
                        64...127 => .{
                            .same_locals_1_stack_item_frame = .{
                                .stack = .{ try .parse(input) },
                            },
                        },
                        247 => .{
                            .same_locals_1_stack_item_frame_extended = .{
                                .offset_delta = try input.takeInt(u16, .big),
                                .stack = .{ try .parse(input) },
                            },
                        },
                        248...250 => .{
                            .chop_frame = .{
                                .offset_delta = try input.takeInt(u16, .big),
                            },
                        },
                        252...254 => blk: {
                            const offset_delta = try input.takeInt(u16, .big);

                            const length = frame_type - 251;
                            const locals = try allocator.alloc(Predefined.StackMapTable.VerificationTypeInfo, length);
                            errdefer allocator.free(locals);
                            for (locals) |*local| {
                                local.* = try .parse(input);
                            }

                            break :blk .{
                                .append_frame = .{
                                    .offset_delta = offset_delta,
                                    .locals = locals,
                                },
                            };
                        },
                        255 => blk: {
                            const offset_delta = try input.takeInt(u16, .big);

                            const number_of_locals = try input.takeInt(u16, .big);
                            const locals = try allocator.alloc(Predefined.StackMapTable.VerificationTypeInfo, number_of_locals);
                            errdefer allocator.free(locals);
                            for (locals) |*local| {
                                local.* = try .parse(input);
                            }

                            const number_of_stack_items = try input.takeInt(u16, .big);
                            const stack = try allocator.alloc(Predefined.StackMapTable.VerificationTypeInfo, number_of_stack_items);
                            errdefer allocator.free(stack);
                            for (stack) |*stack_item| {
                                stack_item.* = try .parse(input);
                            }

                            break :blk .{
                                .full_frame = .{
                                    .offset_delta = offset_delta,
                                    .locals = locals,
                                    .stack = stack,
                                },
                            };
                        },
                        else => unreachable,
                    };
                }

                pub fn deinit(self: *StackMapFrame, allocator: std.mem.Allocator) void {
                    switch (self.*) {
                        .append_frame => |frame| allocator.free(frame.locals),
                        .full_frame => |frame| {
                            allocator.free(frame.locals);
                            allocator.free(frame.stack);
                        },
                        else => {},
                    }
                }
            };

            pub const VerificationTypeInfo = union(VerificationTypeInfo.Tag) {
                top,
                integer,
                float,
                double,
                long,
                null,
                uninitialized_this,
                object: struct {
                    cpool_index: u16,
                },
                uninitialized: struct {
                    cpool_index: u16,
                },

                pub const Tag = enum(u8) {
                    top = 0,
                    integer = 1,
                    float = 2,
                    double = 3,
                    long = 4,
                    null = 5,
                    uninitialized_this = 6,
                    object = 7,
                    uninitialized = 8,
                };

                pub fn parse(input: *std.Io.Reader) ParseError!VerificationTypeInfo {
                    const tag_byte = try input.takeByte();
                    const tag = std.enums.fromInt(Predefined.StackMapTable.VerificationTypeInfo.Tag, tag_byte) orelse return ParseError.InvalidTag;

                    return switch (tag) {
                        .top => .top,
                        .integer => .integer,
                        .float => .float,
                        .double => .double,
                        .long => .long,
                        .null => .null,
                        .uninitialized_this => .uninitialized_this,
                        .object => .{
                            .object = .{
                                .cpool_index = try input.takeInt(u16, .big),
                            },
                        },
                        .uninitialized => .{
                            .uninitialized = .{
                                .cpool_index = try input.takeInt(u16, .big),
                            },
                        },
                    };
                }
            };
        };

        pub const Exceptions = struct {
            exception_index_table: []u16,
        };

        pub const InnerClasses = struct {
            classes: []InnerClass,

            pub const InnerClass = struct {
                inner_class_info_index: u16,
                outer_class_info_index: u16,
                inner_name_index: u16,
                inner_class_access_flags: u16,
            };
        };

        pub const EnclosingMethod = struct {
            class_index: u16,
            method_index: u16,
        };

        pub const Synthetic = struct { };

        pub const Signature = struct {
            signature_index: u16,

            pub fn signature(self: *const Signature, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
                const signature_index = self.signature_index - 1;
                if (signature_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
                const cp_info = constant_pool[signature_index];
                if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
                return cp_info.?.utf8;
            }
        };

        pub const SourceFile = struct {
            source_file_index: u16,

            pub fn sourceFile(self: *const SourceFile, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
                const source_file_index = self.source_file_index - 1;
                if (source_file_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
                const cp_info = constant_pool[source_file_index];
                if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
                return cp_info.?.utf8;
            }
        };

        pub const SourceDebugExtension = struct {
            debug_extension: []u8,
        };

        pub const LineNumberTable = struct {
            line_number_table: []const LineNumber,

            pub const LineNumber = struct {
                start_pc: u16,
                line_number: u16,
            };
        };

        pub const LocalVariableTable = struct {
            local_variable_table: []LocalVariable,

            pub const LocalVariable = struct {
                start_pc: u16,
                length: u16,
                name_index: u16,
                descriptor_index: u16,
                index: u16,
            };
        };

        pub const LocalVariableTypeTable = struct {
            local_variable_type_table: []LocalVariableType,

            pub const LocalVariableType = struct {
                start_pc: u16,
                length: u16,
                name_index: u16,
                signature_index: u16,
                index: u16,
            };
        };

        pub const Deprecated = struct { };

        pub const ElementValue = union(enum) {
            // B, C, D, F, I, J, S, Z, s
            const_value_index: u16,
            // e
            enum_const_value: struct {
                type_name_index: u16,
                const_name_index: u16,
            },
            // c
            class_info_index: u16,
            // @
            annotation_value: Annotation,
            // [
            array_value: struct {
                values: []ElementValue,
            },

            pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!ElementValue {
                const tag = try input.takeByte();
                return switch (tag) {
                    'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's' => .{
                        .const_value_index = try input.takeInt(u16, .big),
                    },
                    'e' => .{
                        .enum_const_value = .{
                            .type_name_index = try input.takeInt(u16, .big),
                            .const_name_index = try input.takeInt(u16, .big),
                        },
                        },
                    'c' => .{
                        .class_info_index = try input.takeInt(u16, .big),
                    },
                    '@' => .{
                        .annotation_value = try .parse(input, allocator),
                    },
                    '[' => .{
                        .array_value = blk: {
                            const num_values = try input.takeInt(u16, .big);
                            const values = try allocator.alloc(ElementValue, num_values);
                            errdefer allocator.free(values);
                            for (values) |*value| {
                                value.* = try .parse(input, allocator);
                            }

                            break :blk .{
                                .values = values,
                            };
                        },
                    },
                    else => unreachable,
                };
            }

            pub fn deinit(self: *ElementValue, allocator: std.mem.Allocator) void {
                switch (self.*) {
                    .annotation_value => |element_value| element_value.deinit(allocator),
                    .array_value => |element_value| {
                        for (element_value.values) |value| {
                            value.deinit(allocator);
                        }
                        allocator.free(element_value.values);
                    },
                    else => {},
                }
            }
        };

        pub const Annotation = struct {
            type_index: u16,
            element_value_pairs: []ElementValuePair,

            pub const ElementValuePair = struct {
                element_name_index: u16,
                value: ElementValue,
            };

            pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!Annotation {
                const type_index = try input.takeInt(u16, .big);

                const num_element_value_pairs = try input.takeInt(u16, .big);
                const element_value_pairs = try allocator.alloc(ElementValuePair, num_element_value_pairs);
                errdefer allocator.free(element_value_pairs);
                for (element_value_pairs) |*element_value_pair| {
                    element_value_pair.* = .{
                        .element_name_index = try input.takeInt(u16, .big),
                        .value = try .parse(input, allocator),
                    };
                }

                return .{
                    .type_index = type_index,
                    .element_value_pairs = element_value_pairs,
                };
            }

            pub fn deinit(self: *Annotation, allocator: std.mem.Allocator) void {
                for (self.element_value_pairs) |*element_value_pair| {
                    element_value_pair.value.deinit(allocator);
                }
                allocator.free(self.element_value_pairs);
            }
        };

        pub const RuntimeVisibleAnnotations = struct {
            annotations: []Annotation,
        };

        pub const RuntimeInvisibleAnnotations = struct {
            annotations: []Annotation,
        };

        pub const ParameterAnnotation = struct {
            annotations: []Annotation,
        };

        pub const RuntimeVisibleParameterAnnotations = struct {
            parameter_annotations: []ParameterAnnotation,
        };

        pub const RuntimeInvisibleParameterAnnotations = struct {
            parameter_annotations: []ParameterAnnotation,
        };

        pub const AnnotationDefault = struct {
            default_value: ElementValue,
        };

        pub const BootstrapMethods = struct {
            bootstrap_methods: []BootstrapMethod,

            pub const BootstrapMethod = struct {
                bootstrap_method_ref: u16,
                bootstrap_arguments: []u16,
            };
        };

        pub fn deinit(self: *Predefined, allocator: std.mem.Allocator) void {
            switch (self.*) {
                .code => |attribute| {
                    allocator.free(attribute.code);
                    allocator.free(attribute.exception_table);
                    for (attribute.attributes) |*attr| {
                        attr.deinit(allocator);
                    }
                    allocator.free(attribute.attributes);
                },
                .stack_map_table => |attribute| {
                    for (attribute.entries) |*entry| {
                        entry.deinit(allocator);
                    }
                    allocator.free(attribute.entries);
                },
                .exceptions => |attribute| allocator.free(attribute.exception_index_table),
                .inner_classes => |attribute| allocator.free(attribute.classes),
                .source_debug_extension => |attribute| allocator.free(attribute.debug_extension),
                .line_number_table => |attribute| allocator.free(attribute.line_number_table),
                .local_variable_table => |attribute| allocator.free(attribute.local_variable_table),
                .local_variable_type_table => |attribute| allocator.free(attribute.local_variable_type_table),
                .runtime_visible_annotations => |attribute| allocator.free(attribute.annotations),
                .runtime_invisible_annotations => |attribute| allocator.free(attribute.annotations),
                .runtime_visible_parameter_annotations => |attribute| {
                    for (attribute.parameter_annotations) |parameter_annotation| {
                        allocator.free(parameter_annotation.annotations);
                    }
                    allocator.free(attribute.parameter_annotations);
                },
                .runtime_invisible_parameter_annotations => |attribute| {
                    for (attribute.parameter_annotations) |parameter_annotation| {
                        allocator.free(parameter_annotation.annotations);
                    }
                    allocator.free(attribute.parameter_annotations);
                },
                .bootstrap_methods => |attribute| {
                    for (attribute.bootstrap_methods) |bootstrap_method| {
                        allocator.free(bootstrap_method.bootstrap_arguments);
                    }
                    allocator.free(attribute.bootstrap_methods);
                },
                else => {},
            }
        }
    };

    pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!AttributeInfo {
        const attribute_name_index = try input.takeInt(u16, .big);
        const attribute_length = try input.takeInt(u32, .big);
        const info = try input.readAlloc(allocator, attribute_length);
        return .{
            .attribute_name_index = attribute_name_index,
            .info = info,
        };
    }

    pub fn deinit(self: *AttributeInfo, allocator: std.mem.Allocator) void {
        allocator.free(self.info);
    }

    pub fn attributeName(self: *const AttributeInfo, constant_pool: []const ?CpInfo) ResolveError!CpInfo.Utf8 {
        const attribute_name_index = self.attribute_name_index - 1;
        if (attribute_name_index >= constant_pool.len) return ResolveError.InvalidConstantPoolIndex;
        const cp_info = constant_pool[attribute_name_index];
        if (cp_info == null or @as(CpInfo.Tag, cp_info.?) != .utf8) return ResolveError.InvalidConstantType;
        return cp_info.?.utf8;
    }

    pub fn resolve(self: *const AttributeInfo, allocator: std.mem.Allocator, constant_pool: []const ?CpInfo) (ResolveError || ParseError)!?Predefined {
        const attribute_name = try self.attributeName(constant_pool);

        var r: std.Io.Reader = .fixed(self.info);

        const eql = std.mem.eql;
        if (eql(u8, attribute_name.bytes, "ConstantValue")) {
            return .{
                .constant_value = .{
                    .constant_value_index = try r.takeInt(u16, .big),
                },
            };
        } else if (eql(u8, attribute_name.bytes, "Code")) {
            const max_stack = try r.takeInt(u16, .big);
            const max_locals = try r.takeInt(u16, .big);

            const code_length = try r.takeInt(u32, .big);
            const code = try r.readAlloc(allocator, code_length);
            errdefer allocator.free(code);

            const exception_table_length = try r.takeInt(u16, .big);
            const exception_table = try allocator.alloc(Predefined.Code.ExceptionHandler, exception_table_length);
            errdefer allocator.free(exception_table);
            for (exception_table) |*exception_handler| {
                exception_handler.* = .{
                    .start_pc = try r.takeInt(u16, .big),
                    .end_pc = try r.takeInt(u16, .big),
                    .handler_pc = try r.takeInt(u16, .big),
                    .catch_type = try r.takeInt(u16, .big),
                };
            }

            const attribute_count = try r.takeInt(u16, .big);
            const attributes = try allocator.alloc(AttributeInfo, attribute_count);
            errdefer allocator.free(attributes);
            for (attributes) |*attribute| {
                attribute.* = try .parse(&r, allocator);
            }

            return .{
                .code = .{
                    .max_stack = max_stack,
                    .max_locals = max_locals,
                    .code = code,
                    .exception_table = exception_table,
                    .attributes = attributes,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "StackMapTable")) {
            const number_of_entries = try r.takeInt(u16, .big);
            const entries = try allocator.alloc(Predefined.StackMapTable.StackMapFrame, number_of_entries);
            errdefer allocator.free(entries);
            for (entries) |*entry| {
                entry.* = try .parse(&r, allocator);
            }

            return .{
                .stack_map_table = .{
                    .entries = entries,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "Exceptions")) {
            const number_of_exceptions = try r.takeInt(u16, .big);
            const exception_index_table = try allocator.alloc(u16, number_of_exceptions);
            errdefer allocator.free(exception_index_table);
            for (exception_index_table) |*exception_index| {
                exception_index.* = try r.takeInt(u16, .big);
            }

            return .{
                .exceptions = .{
                    .exception_index_table = exception_index_table,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "InnerClasses")) {
            const number_of_classes = try r.takeInt(u16, .big);
            const classes = try allocator.alloc(Predefined.InnerClasses.InnerClass, number_of_classes);
            errdefer allocator.free(classes);
            for (classes) |*class| {
                class.* = .{
                    .inner_class_info_index = try r.takeInt(u16, .big),
                    .outer_class_info_index = try r.takeInt(u16, .big),
                    .inner_name_index = try r.takeInt(u16, .big),
                    .inner_class_access_flags = try r.takeInt(u16, .big),
                };
            }

            return .{
                .inner_classes = .{
                    .classes = classes,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "EnclosingMethod")) {
            return .{
                .enclosing_method = .{
                    .class_index = try r.takeInt(u16, .big),
                    .method_index = try r.takeInt(u16, .big),
                },
            };
        } else if (eql(u8, attribute_name.bytes, "Synthetic")) {
            return .{
                .synthetic = .{},
            };
        } else if (eql(u8, attribute_name.bytes, "Signature")) {
            return .{
                .signature = .{
                    .signature_index = try r.takeInt(u16, .big),
                },
            };
        } else if (eql(u8, attribute_name.bytes, "SourceFile")) {
            return .{
                .source_file = .{
                    .source_file_index = try r.takeInt(u16, .big),
                },
            };
        } else if (eql(u8, attribute_name.bytes, "SourceDebugExtension")) {
            return .{
                .source_debug_extension = .{
                    .debug_extension = try allocator.dupe(u8, self.info),
                },
            };
        } else if (eql(u8, attribute_name.bytes, "LineNumberTable")) {
            const line_number_table_length = try r.takeInt(u16, .big);
            const line_number_table = try allocator.alloc(Predefined.LineNumberTable.LineNumber, line_number_table_length);
            errdefer allocator.free(line_number_table);
            for (line_number_table) |*line_number| {
                line_number.* = .{
                    .start_pc = try r.takeInt(u16, .big),
                    .line_number = try r.takeInt(u16, .big),
                };
            }

            return .{
                .line_number_table = .{
                    .line_number_table = line_number_table,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "LocalVariableTable")) {
            const local_variable_table_length = try r.takeInt(u16, .big);
            const local_variable_table = try allocator.alloc(Predefined.LocalVariableTable.LocalVariable, local_variable_table_length);
            errdefer allocator.free(local_variable_table);
            for (local_variable_table) |*local_variable| {
                local_variable.* = .{
                    .start_pc = try r.takeInt(u16, .big),
                    .length = try r.takeInt(u16, .big),
                    .name_index = try r.takeInt(u16, .big),
                    .descriptor_index = try r.takeInt(u16, .big),
                    .index = try r.takeInt(u16, .big),
                };
            }

            return .{
                .local_variable_table = .{
                    .local_variable_table = local_variable_table,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "LocalVariableTypeTable")) {
            const local_variable_type_table_length = try r.takeInt(u16, .big);
            const local_variable_type_table = try allocator.alloc(Predefined.LocalVariableTypeTable.LocalVariableType, local_variable_type_table_length);
            errdefer allocator.free(local_variable_type_table);
            for (local_variable_type_table) |*local_variable_type| {
                local_variable_type.* = .{
                    .start_pc = try r.takeInt(u16, .big),
                    .length = try r.takeInt(u16, .big),
                    .name_index = try r.takeInt(u16, .big),
                    .signature_index = try r.takeInt(u16, .big),
                    .index = try r.takeInt(u16, .big),
                };
            }

            return .{
                .local_variable_type_table = .{
                    .local_variable_type_table = local_variable_type_table,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "Deprecated")) {
            return .{
                .deprecated = .{},
            };
        } else if (eql(u8, attribute_name.bytes, "RuntimeVisibleAnnotations")) {
            const num_annotations = try r.takeInt(u16, .big);
            const annotations = try allocator.alloc(Predefined.Annotation, num_annotations);
            errdefer allocator.free(annotations);
            for (annotations) |*annotation| {
                annotation.* = try .parse(&r, allocator);
            }

            return .{
                .runtime_visible_annotations = .{
                    .annotations = annotations,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "RuntimeInvisibleAnnotations")) {
            const num_annotations = try r.takeInt(u16, .big);
            const annotations = try allocator.alloc(Predefined.Annotation, num_annotations);
            errdefer allocator.free(annotations);
            for (annotations) |*annotation| {
                annotation.* = try .parse(&r, allocator);
            }

            return .{
                .runtime_invisible_annotations = .{
                    .annotations = annotations,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "RuntimeVisibleParameterAnnotations")) {
            const num_parameters = try r.takeByte();
            const parameter_annotations = try allocator.alloc(Predefined.ParameterAnnotation, num_parameters);
            errdefer allocator.free(parameter_annotations);
            for (parameter_annotations) |*parameter_annotation| {
                const num_annotations = try r.takeInt(u16, .big);
                const annotations = try allocator.alloc(Predefined.Annotation, num_annotations);
                errdefer allocator.free(annotations);
                for (annotations) |*annotation| {
                    annotation.* = try .parse(&r, allocator);
                }

                parameter_annotation.* = .{
                    .annotations = annotations,
                };
            }

            return .{
                .runtime_visible_parameter_annotations = .{
                    .parameter_annotations = parameter_annotations,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "RuntimeInvisibleParameterAnnotations")) {
            const num_parameters = try r.takeByte();
            const parameter_annotations = try allocator.alloc(Predefined.ParameterAnnotation, num_parameters);
            errdefer allocator.free(parameter_annotations);
            for (parameter_annotations) |*parameter_annotation| {
                const num_annotations = try r.takeInt(u16, .big);
                const annotations = try allocator.alloc(Predefined.Annotation, num_annotations);
                errdefer allocator.free(annotations);
                for (annotations) |*annotation| {
                    annotation.* = try .parse(&r, allocator);
                }

                parameter_annotation.* = .{
                    .annotations = annotations,
                };
            }

            return .{
                .runtime_invisible_parameter_annotations = .{
                    .parameter_annotations = parameter_annotations,
                },
            };
        } else if (eql(u8, attribute_name.bytes, "AnnotationDefault")) {
            unreachable;
        } else if (eql(u8, attribute_name.bytes, "BootstrapMethods")) {
            const num_bootstrap_methods = try r.takeInt(u16, .big);
            const bootstrap_methods = try allocator.alloc(Predefined.BootstrapMethods.BootstrapMethod, num_bootstrap_methods);
            errdefer allocator.free(bootstrap_methods);
            for (bootstrap_methods) |*bootstrap_method| {
                const bootstrap_method_ref = try r.takeInt(u16, .big);

                const num_bootstrap_arguments = try r.takeInt(u16, .big);
                const bootstrap_arguments = try allocator.alloc(u16, num_bootstrap_arguments);
                errdefer allocator.free(bootstrap_arguments);
                for (bootstrap_arguments) |*bootstrap_argument| {
                    bootstrap_argument.* = try r.takeInt(u16, .big);
                }

                bootstrap_method.* = .{
                    .bootstrap_method_ref = bootstrap_method_ref,
                    .bootstrap_arguments = bootstrap_arguments,
                };
            }

            return .{
                .bootstrap_methods = .{
                    .bootstrap_methods = bootstrap_methods,
                },
            };
        } else {
            return null;
        }
    }
};

