const std = @import("std");
const assert = std.debug.assert;
const posix = std.posix;
const fs = std.fs;
const fmt = std.fmt;
const io = std.io;
const mem = std.mem;
const fmtId = std.zig.fmtId;

const log = std.log.scoped(.@"zig-wayland");

const xml = @import("xml.zig");

const gpa = general_purpose_allocator.allocator();
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};

pub const Target = struct {
    /// Name of the target global interface
    name: []const u8,
    /// Interface version for which to generate code.
    /// If the version found in the protocol xml is less than this version,
    /// an error will be printed and code generation will fail.
    /// This version applies to interfaces that may be created through the
    /// global interface as well.
    version: u32,
};

pub fn main() !void {
    defer assert(general_purpose_allocator.deinit() == .ok);

    var protocols: std.ArrayList([]const u8) = .empty;
    defer protocols.deinit(gpa);

    var targets: std.ArrayList(Target) = .empty;
    defer targets.deinit(gpa);

    var out_path_opt: ?[]const u8 = null;

    var args = std.process.args();

    while (args.next()) |arg| {
        if (mem.eql(u8, arg, "-i")) {
            const protocol_path = args.next() orelse return error.MissingArg;
            try protocols.append(gpa, protocol_path);
        } else if (mem.eql(u8, arg, "-g")) {
            const name = args.next() orelse return error.MissingArg;
            const version = args.next() orelse return error.MissingArg;
            try targets.append(gpa, .{
                .name = name,
                .version = try fmt.parseInt(u32, version, 10),
            });
        } else if (mem.eql(u8, arg, "-o")) {
            out_path_opt = args.next() orelse return error.MissingArg;
        }
    }

    const out_path = out_path_opt orelse return error.MissingArg;

    var buffer: io.Writer.Allocating = .init(gpa);

    try scan(&buffer.writer, protocols.items, targets.items);

    const generated = try buffer.toOwnedSliceSentinel(0);
    defer gpa.free(generated);

    var tree = try std.zig.Ast.parse(gpa, generated, .zig);
    defer tree.deinit(gpa);

    const out_file = try std.fs.createFileAbsolute(out_path, .{});
    defer out_file.close();

    var out_buf: [4096]u8 = undefined;
    var out_writer = out_file.writer(&out_buf);

    try tree.render(gpa, &out_writer.interface, .{});

    try out_writer.interface.flush();
}

fn scan(
    writer: *io.Writer,
    protocols: []const []const u8,
    targets: []const Target,
) !void {
    var scanner = try Scanner.init(targets);
    defer scanner.deinit();

    for (protocols) |xml_path| {
        try scanner.scanProtocol(xml_path);
    }

    if (scanner.remaining_targets.items.len != 0) {
        fatal("requested global interface '{s}' not found in provided protocol xml", .{
            scanner.remaining_targets.items[0].name,
        });
    }

    try writer.writeAll(
        \\// Generated by zig-wayland
        \\
        \\const std = @import("std");
        \\const assert = std.debug.assert;
        \\const posix = std.posix;
        \\
        \\pub const client = struct {
    );

    {
        var iter = scanner.client.iterator();
        while (iter.next()) |entry| {
            try writer.print("pub const {s} = struct {{", .{entry.key_ptr.*});
            if (mem.eql(u8, entry.key_ptr.*, "wl")) {
                try writer.writeAll(@embedFile("wayland_client_core.zig"));
            }
            try writer.writeAll(entry.value_ptr.written());
            try writer.writeAll("};");
        }
    }

    try writer.writeAll(
        \\};
        \\
        \\pub const server = struct {
    );

    {
        var iter = scanner.server.iterator();
        while (iter.next()) |entry| {
            try writer.print("pub const {s} = struct {{", .{entry.key_ptr.*});
            if (mem.eql(u8, entry.key_ptr.*, "wl")) {
                try writer.writeAll(@embedFile("wayland_server_core.zig"));
            }
            try writer.writeAll(entry.value_ptr.written());
            try writer.writeAll("};");
        }
    }

    try writer.writeAll(
        \\};
        \\
        \\const common = struct {
    );

    {
        try writer.writeAll(@embedFile("common_core.zig"));

        var iter = scanner.common.iterator();
        while (iter.next()) |entry| {
            try writer.print("const {s} = struct {{", .{entry.key_ptr.*});
            try writer.writeAll(entry.value_ptr.written());
            try writer.writeAll("};");
        }
    }
    try writer.writeAll("};");
}

const Side = enum {
    client,
    server,
};

const Scanner = struct {
    /// Map from namespace to source code content of the namespace.
    const Map = std.StringArrayHashMap(io.Writer.Allocating);
    client: Map = Map.init(gpa),
    server: Map = Map.init(gpa),
    common: Map = Map.init(gpa),

    remaining_targets: std.ArrayListUnmanaged(Target),

    fn init(targets: []const Target) !Scanner {
        return Scanner{
            .remaining_targets = .{
                .items = try gpa.dupe(Target, targets),
                .capacity = targets.len,
            },
        };
    }

    fn deinit(scanner: *Scanner) void {
        deinit_map(&scanner.client);
        deinit_map(&scanner.server);
        deinit_map(&scanner.common);

        scanner.remaining_targets.deinit(gpa);
    }

    fn deinit_map(map: *Map) void {
        for (map.keys()) |namespace| gpa.free(namespace);
        for (map.values()) |*list| {
            list.deinit();
        }
        map.deinit();
    }

    fn scanProtocol(scanner: *Scanner, xml_path: []const u8) !void {
        const xml_file = try fs.cwd().openFile(xml_path, .{});
        defer xml_file.close();

        var arena = std.heap.ArenaAllocator.init(gpa);
        defer arena.deinit();

        const xml_bytes = try xml_file.readToEndAlloc(arena.allocator(), 512 * 4096);
        const protocol = Protocol.parseXML(arena.allocator(), xml_bytes) catch |err| {
            fatal("failed to parse {s}: {s}", .{ xml_path, @errorName(err) });
        };

        {
            const gop = try scanner.client.getOrPutValue(protocol.namespace, .init(gpa));
            if (!gop.found_existing) {
                gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
            }
            try protocol.emit(.client, scanner.remaining_targets.items, &gop.value_ptr.writer);
        }

        {
            const gop = try scanner.server.getOrPutValue(protocol.namespace, .init(gpa));
            if (!gop.found_existing) {
                gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
            }
            try protocol.emit(.server, scanner.remaining_targets.items, &gop.value_ptr.writer);
        }

        {
            const gop = try scanner.common.getOrPutValue(protocol.namespace, .init(gpa));
            if (!gop.found_existing) {
                gop.key_ptr.* = try gpa.dupe(u8, protocol.namespace);
            }
            try protocol.emitCommon(scanner.remaining_targets.items, &gop.value_ptr.writer);
        }

        {
            var i: usize = 0;
            outer: while (i < scanner.remaining_targets.items.len) {
                const target = scanner.remaining_targets.items[i];
                for (protocol.globals) |global| {
                    if (mem.eql(u8, target.name, global.interface.name)) {
                        // We check this in emitClient() which is called first.
                        assert(global.interface.version >= target.version);
                        _ = scanner.remaining_targets.swapRemove(i);
                        continue :outer;
                    }
                }
                i += 1;
            }
        }
    }
};

/// All data in this struct is immutable after creation in parse().
const Protocol = struct {
    const Global = struct {
        interface: Interface,
        children: []const Interface,
    };

    name: []const u8,
    namespace: []const u8,
    copyright: ?[]const u8,
    toplevel_description: ?[]const u8,

    version_locked_interfaces: []const Interface,
    globals: []const Global,

    fn parseXML(arena: mem.Allocator, xml_bytes: []const u8) !Protocol {
        var parser = xml.Parser.init(xml_bytes);
        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| if (mem.eql(u8, tag, "protocol")) return parse(arena, &parser),
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Protocol {
        var name: ?[]const u8 = null;
        var copyright: ?[]const u8 = null;
        var toplevel_description: ?[]const u8 = null;
        var version_locked_interfaces: std.ArrayList(Interface) = .empty;
        defer version_locked_interfaces.deinit(gpa);
        var interfaces = std.StringArrayHashMap(Interface).init(gpa);
        defer interfaces.deinit();

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                if (mem.eql(u8, tag, "copyright")) {
                    if (copyright != null)
                        return error.DuplicateCopyright;
                    const e = parser.next() orelse return error.UnexpectedEndOfFile;
                    switch (e) {
                        .character_data => |data| copyright = try arena.dupe(u8, data),
                        else => return error.BadCopyright,
                    }
                } else if (mem.eql(u8, tag, "description")) {
                    if (toplevel_description != null)
                        return error.DuplicateToplevelDescription;
                    while (parser.next()) |e| {
                        switch (e) {
                            .character_data => |data| {
                                toplevel_description = try arena.dupe(u8, data);
                                break;
                            },
                            .attribute => continue,
                            else => return error.BadToplevelDescription,
                        }
                    } else {
                        return error.UnexpectedEndOfFile;
                    }
                } else if (mem.eql(u8, tag, "interface")) {
                    const interface = try Interface.parse(arena, parser);
                    if (Interface.version_locked(interface.name)) {
                        try version_locked_interfaces.append(gpa, interface);
                    } else {
                        const gop = try interfaces.getOrPut(interface.name);
                        if (gop.found_existing) return error.DuplicateInterfaceName;
                        gop.value_ptr.* = interface;
                    }
                }
            },
            .attribute => |attr| if (mem.eql(u8, attr.name, "name")) {
                if (name != null) return error.DuplicateName;
                name = try attr.dupeValue(arena);
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "protocol")) {
                if (interfaces.count() == 0) return error.NoInterfaces;

                const globals = try find_globals(arena, interfaces);
                if (globals.len == 0) return error.NoGlobals;

                const namespace = prefix(interfaces.values()[0].name) orelse return error.NoNamespace;
                for (interfaces.values()) |interface| {
                    const other = prefix(interface.name) orelse return error.NoNamespace;
                    if (!mem.eql(u8, namespace, other)) return error.InconsistentNamespaces;
                }

                return Protocol{
                    .name = name orelse return error.MissingName,
                    .namespace = namespace,

                    // Missing copyright or toplevel description is bad style, but not illegal.
                    .copyright = copyright,
                    .toplevel_description = toplevel_description,
                    .version_locked_interfaces = try arena.dupe(Interface, version_locked_interfaces.items),
                    .globals = globals,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn find_globals(arena: mem.Allocator, interfaces: std.StringArrayHashMap(Interface)) ![]const Global {
        var non_globals = std.StringHashMap(void).init(gpa);
        defer non_globals.deinit();

        for (interfaces.values()) |interface| {
            assert(!Interface.version_locked(interface.name));
            for (interface.requests) |message| {
                if (message.kind == .constructor) {
                    if (message.kind.constructor) |child_interface_name| {
                        try non_globals.put(child_interface_name, {});
                    }
                }
            }
            for (interface.events) |message| {
                if (message.kind == .constructor) {
                    if (message.kind.constructor) |child_interface_name| {
                        try non_globals.put(child_interface_name, {});
                    }
                }
            }
        }

        var globals: std.ArrayList(Global) = .empty;
        defer globals.deinit(gpa);

        for (interfaces.values()) |interface| {
            if (!non_globals.contains(interface.name)) {
                var children = std.StringArrayHashMap(Interface).init(gpa);
                defer children.deinit();

                try find_children(interface, interfaces, &children);

                try globals.append(gpa, .{
                    .interface = interface,
                    .children = try arena.dupe(Interface, children.values()),
                });
            }
        }

        return arena.dupe(Global, globals.items);
    }

    fn find_children(
        parent: Interface,
        interfaces: std.StringArrayHashMap(Interface),
        children: *std.StringArrayHashMap(Interface),
    ) error{ OutOfMemory, InvalidInterface }!void {
        for ([_][]const Message{ parent.requests, parent.events }) |messages| {
            for (messages) |message| {
                if (message.kind == .constructor) {
                    if (message.kind.constructor) |child_name| {
                        if (Interface.version_locked(child_name)) continue;

                        const child = interfaces.get(child_name) orelse {
                            log.err("interface '{s}' constructed by message '{s}' not defined in the protocol and not wl_callback or wl_buffer", .{
                                child_name,
                                message.name,
                            });
                            return error.InvalidInterface;
                        };
                        try children.put(child_name, child);
                        try find_children(child, interfaces, children);
                    }
                }
            }
        }
    }

    fn emit(protocol: Protocol, side: Side, targets: []const Target, writer: *io.Writer) !void {
        for (protocol.version_locked_interfaces) |interface| {
            assert(interface.version == 1);
            try interface.emit(side, 1, protocol.namespace, writer);
        }

        for (targets) |target| {
            for (protocol.globals) |global| {
                if (mem.eql(u8, target.name, global.interface.name)) {
                    if (global.interface.version < target.version) {
                        fatal("requested {s} version {d} but only version {d} is available in provided xml", .{
                            target.name,
                            target.version,
                            global.interface.version,
                        });
                    }
                    try global.interface.emit(side, target.version, protocol.namespace, writer);
                    for (global.children) |child| {
                        try child.emit(side, target.version, protocol.namespace, writer);
                    }
                }
            }
        }
    }

    fn emitCommon(protocol: Protocol, targets: []const Target, writer: *io.Writer) !void {
        for (protocol.version_locked_interfaces) |interface| {
            assert(interface.version == 1);
            try interface.emitCommon(1, writer);
        }

        for (protocol.globals) |global| {
            for (targets) |target| {
                if (mem.eql(u8, target.name, global.interface.name)) {
                    // We check this in emitClient() which is called first.
                    assert(global.interface.version >= target.version);

                    try global.interface.emitCommon(target.version, writer);
                    for (global.children) |child| {
                        try child.emitCommon(target.version, writer);
                    }
                    break;
                }
            } else {
                try global.interface.emitCommon(null, writer);
                for (global.children) |child| {
                    try child.emitCommon(null, writer);
                }
            }
        }
    }
};

/// All data in this struct is immutable after creation in parse().
const Interface = struct {
    name: []const u8,
    version: u32,
    requests: []const Message,
    events: []const Message,
    enums: []const Enum,

    // These interfaces are special in that their version may never be increased.
    // That is, they are pinned to version 1 forever. They also may break the
    // normally required tree object creation hierarchy.
    const version_locked_interfaces = std.StaticStringMap(void).initComptime(.{
        .{"wl_display"},
        .{"wl_registry"},
        .{"wl_callback"},
        .{"wl_buffer"},
    });
    fn version_locked(interface_name: []const u8) bool {
        return version_locked_interfaces.has(interface_name);
    }

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Interface {
        var name: ?[]const u8 = null;
        var version: ?u32 = null;
        var requests: std.ArrayList(Message) = .empty;
        defer requests.deinit(gpa);
        var events: std.ArrayList(Message) = .empty;
        defer events.deinit(gpa);
        var enums: std.ArrayList(Enum) = .empty;
        defer enums.deinit(gpa);

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                // TODO: parse description
                if (mem.eql(u8, tag, "request"))
                    try requests.append(gpa, try Message.parse(arena, parser))
                else if (mem.eql(u8, tag, "event"))
                    try events.append(gpa, try Message.parse(arena, parser))
                else if (mem.eql(u8, tag, "enum"))
                    try enums.append(gpa, try Enum.parse(arena, parser));
            },
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "version")) {
                    if (version != null) return error.DuplicateVersion;
                    version = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "interface")) {
                return Interface{
                    .name = name orelse return error.MissingName,
                    .version = version orelse return error.MissingVersion,
                    .requests = try arena.dupe(Message, requests.items),
                    .events = try arena.dupe(Message, events.items),
                    .enums = try arena.dupe(Enum, enums.items),
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emit(interface: Interface, side: Side, target_version: u32, namespace: []const u8, writer: *io.Writer) !void {
        try writer.print(
            \\pub const {[type]f} = opaque {{
            \\ pub const generated_version = {[version]};
            \\ pub const interface = &common.{[namespace]f}.{[interface]f}.interface;
        , .{
            .type = titleCaseTrim(interface.name),
            .version = @min(interface.version, target_version),
            .namespace = fmtId(namespace),
            .interface = fmtId(trimPrefix(interface.name)),
        });

        for (interface.enums) |e| {
            if (e.since <= target_version) {
                try writer.print("pub const {[type]f} = common.{[namespace]f}.{[interface]f}.{[type]f};\n", .{
                    .type = titleCase(e.name),
                    .namespace = fmtId(namespace),
                    .interface = fmtId(trimPrefix(interface.name)),
                });
            }
        }

        if (side == .client) {
            inline for (.{
                .{ .name = "getId", .return_type = "u32" },
                .{ .name = "getVersion", .return_type = "u32" },
                .{ .name = "getUserData", .return_type = "?*anyopaque" },
            }) |func| {
                try writer.print(
                    \\pub fn {[function]s}(_{[interface]f}: *{[type]f}) {[return_type]s} {{
                    \\    return @as(*client.wl.Proxy, @ptrCast(_{[interface]f})).{[function]s}();
                    \\}}
                , .{
                    .function = func.name,
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                    .return_type = func.return_type,
                });
            }

            try writer.print(
                \\pub fn setQueue(_{[interface]f}: *{[type]f}, _queue: *client.wl.EventQueue) void {{
                \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]f});
                \\    _proxy.setQueue(_queue);
                \\}}
            , .{
                .interface = fmtId(trimPrefix(interface.name)),
                .type = titleCaseTrim(interface.name),
            });

            const has_event = for (interface.events) |event| {
                if (event.since <= target_version) break true;
            } else false;

            if (has_event) {
                try writer.writeAll("pub const Event = union(enum) {");
                for (interface.events) |event| {
                    if (event.since <= target_version) {
                        try event.emitField(.client, writer);
                    }
                }
                try writer.writeAll("};\n");
                try writer.print(
                    \\pub inline fn setListener(
                    \\    _{[interface]f}: *{[type]f},
                    \\    comptime T: type,
                    \\    _listener: *const fn ({[interface]f}: *{[type]f}, event: Event, data: T) void,
                    \\    _data: T,
                    \\) void {{
                    \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]f});
                    \\    const _mut_data: ?*anyopaque = @ptrFromInt(@intFromPtr(_data));
                    \\    _proxy.addDispatcher(common.Dispatcher({[type]f}, T).dispatcher, _listener, _mut_data);
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }

            var has_destroy = false;
            for (interface.requests, 0..) |request, opcode| {
                if (request.since <= target_version) {
                    if (mem.eql(u8, request.name, "destroy")) has_destroy = true;
                    try request.emitFn(side, writer, interface, opcode);
                }
            }

            if (mem.eql(u8, interface.name, "wl_display")) {
                try writer.writeAll(@embedFile("client_display_functions.zig"));
            } else if (!has_destroy) {
                try writer.print(
                    \\pub fn destroy(_{[interface]f}: *{[type]f}) void {{
                    \\    const _proxy: *client.wl.Proxy = @ptrCast(_{[interface]f});
                    \\    _proxy.destroy();
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }
        } else {
            try writer.print(
                \\pub fn create(_client: *server.wl.Client, _version: u32, _id: u32) !*{[type]f} {{
                \\    return @ptrCast(try server.wl.Resource.create(_client, {[type]f}, _version, _id));
                \\}}pub fn destroy(_{[interface]f}: *{[type]f}) void {{
                \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]f})).destroy();
                \\}}pub fn fromLink(_link: *server.wl.list.Link) *{[type]f} {{
                \\    return @ptrCast(server.wl.Resource.fromLink(_link));
                \\}}
            , .{
                .type = titleCaseTrim(interface.name),
                .interface = fmtId(trimPrefix(interface.name)),
            });

            inline for (.{
                .{ .name = "getLink", .return_type = "*server.wl.list.Link" },
                .{ .name = "getClient", .return_type = "*server.wl.Client" },
                .{ .name = "getId", .return_type = "u32" },
                .{ .name = "getVersion", .return_type = "u32" },
                .{ .name = "postNoMemory", .return_type = "void" },
                .{ .name = "getUserData", .return_type = "?*anyopaque" },
            }) |func|
                try writer.print(
                    \\pub fn {[function]s}(_{[interface]f}: *{[type]f}) {[return_type]s} {{
                    \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]f})).{[function]s}();
                    \\}}
                , .{
                    .function = func.name,
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                    .return_type = func.return_type,
                });

            const has_error = for (interface.enums) |e| {
                if (mem.eql(u8, e.name, "error")) break true;
            } else false;
            if (has_error) {
                try writer.print(
                    \\pub fn postError(_{[interface]f}: *{[type]f}, _err: Error, _message: [*:0]const u8) void {{
                    \\    return @as(*server.wl.Resource, @ptrCast(_{[interface]f})).postError(@intCast(@intFromEnum(_err)), _message);
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }

            const has_request = for (interface.requests) |request| {
                if (request.since <= target_version) break true;
            } else false;

            if (has_request) {
                try writer.writeAll("pub const Request = union(enum) {");
                for (interface.requests) |request| {
                    if (request.since <= target_version) {
                        try request.emitField(.server, writer);
                    }
                }
                try writer.writeAll("};\n");
                @setEvalBranchQuota(2500);
                try writer.print(
                    \\pub inline fn setHandler(
                    \\    _{[interface]f}: *{[type]f},
                    \\    comptime T: type,
                    \\    handle_request: *const fn (_{[interface]f}: *{[type]f}, request: Request, data: T) void,
                    \\    comptime handle_destroy: ?fn (_{[interface]f}: *{[type]f}, data: T) void,
                    \\    _data: T,
                    \\) void {{
                    \\    const _resource: *server.wl.Resource = @ptrCast(_{[interface]f});
                    \\    _resource.setDispatcher(
                    \\        common.Dispatcher({[type]f}, T).dispatcher,
                    \\        handle_request,
                    \\        @ptrFromInt(@intFromPtr(_data)),
                    \\        if (handle_destroy) |_handler| struct {{
                    \\            fn _wrapper(__resource: *server.wl.Resource) callconv(.c) void {{
                    \\                @call(.always_inline, _handler, .{{
                    \\                    @as(*{[type]f}, @ptrCast(__resource)),
                    \\                    @as(T, @ptrCast(@alignCast(__resource.getUserData()))),
                    \\                }});
                    \\            }}
                    \\        }}._wrapper else null,
                    \\    );
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            } else {
                try writer.print(
                    \\pub inline fn setHandler(
                    \\    _{[interface]f}: *{[type]f},
                    \\    comptime T: type,
                    \\    comptime handle_destroy: ?fn (_{[interface]f}: *{[type]f}, data: T) void,
                    \\    _data: T,
                    \\) void {{
                    \\    const _resource: *server.wl.Resource = @ptrCast(_{[interface]f});
                    \\    _resource.setDispatcher(
                    \\        null,
                    \\        null,
                    \\        @ptrFromInt(@intFromPtr(_data)),
                    \\        if (handle_destroy) |_handler| struct {{
                    \\            fn _wrapper(__resource: *server.wl.Resource) callconv(.c) void {{
                    \\                @call(.always_inline, _handler, .{{
                    \\                    @as(*{[type]f}, @ptrCast(__resource)),
                    \\                    @as(?*anyopaque, @ptrFromInt(@intFromPtr(__resource.getUserData()))),
                    \\                }});
                    \\            }}
                    \\        }}._wrapper else null,
                    \\    );
                    \\}}
                , .{
                    .interface = fmtId(trimPrefix(interface.name)),
                    .type = titleCaseTrim(interface.name),
                });
            }

            for (interface.events, 0..) |event, opcode| {
                if (event.since <= target_version) {
                    try event.emitFn(side, writer, interface, opcode);
                }
            }
        }

        try writer.writeAll("};\n");
    }

    fn emitCommon(interface: Interface, target_version: ?u32, writer: *io.Writer) !void {
        try writer.print("const {f} = struct {{", .{fmtId(trimPrefix(interface.name))});

        try writer.print(
            \\const interface: common.Interface = .{{
            \\    .name = "{[name]s}",
            \\    .version = {[version]d},
            \\    .method_count = {[requests_len]d},
        , .{
            .name = interface.name,
            .version = interface.version,
            .requests_len = interface.requests.len,
        });
        if (interface.requests.len == 0) {
            try writer.writeAll(".methods = null,");
        } else {
            try writer.writeAll(".methods = &.{");
            for (interface.requests) |request| {
                try request.emitCommon(writer);
            }
            try writer.writeAll("},");
        }
        try writer.print(".event_count = {d},", .{interface.events.len});
        if (interface.events.len == 0) {
            try writer.writeAll(".events = null,");
        } else {
            try writer.writeAll(".events = &.{");
            for (interface.events) |event| {
                try event.emitCommon(writer);
            }
            try writer.writeAll("},");
        }
        try writer.writeAll("};");

        if (target_version) |target| {
            for (interface.enums) |e| {
                if (e.since <= target) {
                    try e.emit(target, writer);
                }
            }
        }

        try writer.writeAll("};");
    }
};

/// All data in this struct is immutable after creation in parse().
const Message = struct {
    name: []const u8,
    since: u32,
    args: []const Arg,
    kind: union(enum) {
        normal: void,
        constructor: ?[]const u8,
        destructor: void,
    },

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Message {
        var name: ?[]const u8 = null;
        var since: ?u32 = null;
        var args: std.ArrayList(Arg) = .empty;
        defer args.deinit(gpa);
        var destructor = false;

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                // TODO: parse description
                if (mem.eql(u8, tag, "arg"))
                    try args.append(gpa, try Arg.parse(arena, parser));
            },
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "since")) {
                    if (since != null) return error.DuplicateSince;
                    since = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                } else if (mem.eql(u8, attr.name, "type")) {
                    if (attr.valueEql("destructor")) {
                        destructor = true;
                    } else {
                        return error.InvalidType;
                    }
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "request") or mem.eql(u8, tag, "event")) {
                return Message{
                    .name = name orelse return error.MissingName,
                    .since = since orelse 1,
                    .args = try arena.dupe(Arg, args.items),
                    .kind = blk: {
                        if (destructor) break :blk .destructor;
                        for (args.items) |arg|
                            if (arg.kind == .new_id) break :blk .{ .constructor = arg.kind.new_id };
                        break :blk .normal;
                    },
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emitField(message: Message, side: Side, writer: *io.Writer) !void {
        try writer.print("{f}", .{fmtId(message.name)});
        if (message.args.len == 0) {
            try writer.writeAll(": void,");
            return;
        }
        try writer.writeAll(": struct {");
        for (message.args) |arg| {
            if (side == .server and arg.kind == .new_id and arg.kind.new_id == null) {
                try writer.print("interface_name: [*:0]const u8, version: u32,{f}: u32", .{fmtId(arg.name)});
            } else if (side == .client and arg.kind == .new_id) {
                try writer.print("{f}: *", .{fmtId(arg.name)});
                try printAbsolute(.client, writer, arg.kind.new_id.?);
                assert(!arg.allow_null);
            } else {
                try writer.print("{f}:", .{fmtId(arg.name)});
                // See notes on NULL in doc comment for wl_message in wayland-util.h
                if (side == .client and arg.kind == .object and !arg.allow_null)
                    try writer.writeByte('?');
                try arg.emitType(side, writer);
            }
            try writer.writeByte(',');
        }
        try writer.writeAll("},\n");
    }

    fn emitFn(message: Message, side: Side, writer: *io.Writer, interface: Interface, opcode: usize) !void {
        try writer.writeAll("pub fn ");
        if (side == .server) {
            if (message.kind == .destructor) {
                try writer.print("destroySend{f}", .{titleCase(message.name)});
            } else {
                try writer.print("send{f}", .{titleCase(message.name)});
            }
        } else {
            try writer.print("{f}", .{camelCase(message.name)});
        }
        try writer.print("(_{f}: *{f}", .{
            fmtId(trimPrefix(interface.name)),
            titleCaseTrim(interface.name),
        });
        for (message.args) |arg| {
            if (side == .server and arg.kind == .new_id) {
                try writer.print(", _{s}:", .{arg.name});
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeByte('*');
                if (arg.kind.new_id) |iface| {
                    try printAbsolute(side, writer, iface);
                } else {
                    try writer.writeAll("server.wl.Resource");
                }
            } else if (side == .client and arg.kind == .new_id) {
                if (arg.kind.new_id == null) try writer.writeAll(", comptime T: type, _version: u32");
            } else {
                try writer.print(", _{s}:", .{arg.name});
                try arg.emitType(side, writer);
            }
        }
        if (side == .server or message.kind != .constructor) {
            try writer.writeAll(") void {");
        } else if (message.kind.constructor) |new_iface| {
            try writer.writeAll(") !*");
            try printAbsolute(side, writer, new_iface);
            try writer.writeByte('{');
        } else {
            try writer.writeAll(") !*T {");
        }
        if (side == .server) {
            try writer.writeAll("const _resource: *server.wl.Resource = @ptrCast(_");
        } else {
            // wl_registry.bind for example needs special handling
            if (message.kind == .constructor and message.kind.constructor == null) {
                try writer.writeAll("const version_to_construct = @min(T.generated_version, _version);");
            }
            try writer.writeAll("const _proxy: *client.wl.Proxy = @ptrCast(_");
        }
        try writer.print("{f});", .{fmtId(trimPrefix(interface.name))});
        if (message.args.len > 0) {
            try writer.writeAll("var _args = [_]common.Argument{");
            for (message.args) |arg| {
                switch (arg.kind) {
                    .int, .uint, .fixed, .string, .array, .fd => {
                        try writer.writeAll(switch (arg.kind) {
                            .int => ".{ .i = ",
                            .uint => ".{ .u = ",
                            .fixed => ".{ .f = ",
                            .string => ".{ .s = ",
                            .array => ".{ .a = ",
                            .fd => ".{ .h = ",
                            else => unreachable,
                        });
                        if (arg.enum_name != null) {
                            try writer.writeAll("switch (@typeInfo(");
                            try arg.emitType(side, writer);

                            // TODO We know the type of the enum at scanning time, but it's
                            //      currently a bit difficult to access it.
                            const c_type = if (arg.kind == .uint) "u32" else "i32";
                            try writer.print(
                                \\ )) {{
                                \\    .@"enum" => @as({[ct]s}, @intCast(@intFromEnum(_{[an]f}))),
                                \\    .@"struct" => @bitCast(_{[an]f}),
                                \\    else => unreachable,
                                \\ }}
                            , .{ .ct = c_type, .an = fmtId(arg.name) });
                        } else {
                            try writer.print("_{s}", .{arg.name});
                        }
                        try writer.writeAll("},\n");
                    },
                    .object, .new_id => |new_iface| {
                        if (arg.kind == .object or side == .server) {
                            try writer.print(".{{ .o = @ptrCast(_{s}) }},\n", .{arg.name});
                        } else {
                            if (new_iface == null) {
                                try writer.writeAll(".{ .s = T.interface.name },\n");
                                try writer.writeAll(".{ .u = version_to_construct },\n");
                            }
                            try writer.writeAll(".{ .o = null },\n");
                        }
                    },
                }
            }
            try writer.writeAll("};\n");
        }
        const args = if (message.args.len > 0) "&_args" else "null";
        if (side == .server) {
            try writer.print("_resource.postEvent({}, {s});", .{ opcode, args });
            if (message.kind == .destructor) try writer.writeAll("_resource.destroy();");
        } else switch (message.kind) {
            .normal, .destructor => {
                try writer.print("_proxy.marshal({}, {s});", .{ opcode, args });
                if (message.kind == .destructor) try writer.writeAll("_proxy.destroy();");
            },
            .constructor => |new_iface| {
                if (new_iface) |i| {
                    try writer.print("return @ptrCast(try _proxy.marshalConstructor({}, &_args, ", .{opcode});
                    try printAbsolute(side, writer, i);
                    try writer.writeAll(".interface));");
                } else {
                    try writer.print(
                        \\return @ptrCast(try _proxy.marshalConstructorVersioned({[opcode]}, &_args, T.interface, version_to_construct));
                    , .{
                        .opcode = opcode,
                    });
                }
            },
        }
        try writer.writeAll("}\n");
    }

    fn emitCommon(message: Message, writer: *io.Writer) !void {
        try writer.print(
            \\.{{ .name = "{s}", .signature = "
        , .{message.name});
        if (message.since > 1) {
            try writer.print("{d}", .{message.since});
        }
        for (message.args) |arg| {
            try arg.emitSignature(writer);
        }
        try writer.writeAll("\",");
        if (message.args.len == 0) {
            try writer.writeAll(".types = null,");
        } else {
            try writer.writeAll(".types = &.{");
            for (message.args) |arg| {
                switch (arg.kind) {
                    .new_id, .object => |interface| {
                        if (interface) |name| {
                            try writer.print("&common.{s}.{s}.interface,", .{
                                prefix(name).?,
                                trimPrefix(name),
                            });
                        } else if (arg.kind == .new_id) {
                            try writer.writeAll("null,null,null,");
                        } else {
                            try writer.writeAll("null,");
                        }
                    },
                    .int,
                    .uint,
                    .fixed,
                    .string,
                    .array,
                    .fd,
                    => try writer.writeAll("null,"),
                }
            }
            try writer.writeAll("},");
        }
        try writer.writeAll("},");
    }
};

/// All data in this struct is immutable after creation in parse().
const Arg = struct {
    const Type = union(enum) {
        int,
        uint,
        fixed,
        string,
        new_id: ?[]const u8,
        object: ?[]const u8,
        array,
        fd,
    };
    name: []const u8,
    kind: Type,
    allow_null: bool,
    enum_name: ?[]const u8,

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Arg {
        var name: ?[]const u8 = null;
        var kind: ?std.meta.Tag(Type) = null;
        var interface: ?[]const u8 = null;
        var allow_null: ?bool = null;
        var enum_name: ?[]const u8 = null;

        while (parser.next()) |ev| switch (ev) {
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "type")) {
                    if (kind != null) return error.DuplicateType;
                    kind = std.meta.stringToEnum(std.meta.Tag(Type), try attr.dupeValue(arena)) orelse
                        return error.InvalidType;
                } else if (mem.eql(u8, attr.name, "interface")) {
                    if (interface != null) return error.DuplicateInterface;
                    interface = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "allow-null")) {
                    if (allow_null != null) return error.DuplicateAllowNull;
                    if (!attr.valueEql("true") and !attr.valueEql("false")) return error.InvalidBoolValue;
                    allow_null = attr.valueEql("true");
                } else if (mem.eql(u8, attr.name, "enum")) {
                    if (enum_name != null) return error.DuplicateEnum;
                    enum_name = try attr.dupeValue(arena);
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "arg")) {
                return Arg{
                    .name = name orelse return error.MissingName,
                    .kind = switch (kind orelse return error.MissingType) {
                        .object => .{ .object = interface },
                        .new_id => .{ .new_id = interface },
                        .int => .int,
                        .uint => .uint,
                        .fixed => .fixed,
                        .string => .string,
                        .array => .array,
                        .fd => .fd,
                    },
                    .allow_null = allow_null orelse false,
                    .enum_name = enum_name,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emitSignature(arg: Arg, writer: *io.Writer) !void {
        switch (arg.kind) {
            .int => try writer.writeByte('i'),
            .uint => try writer.writeByte('u'),
            .fixed => try writer.writeByte('f'),
            .string => {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeByte('s');
            },
            .new_id => |interface| if (interface == null)
                try writer.writeAll("sun")
            else
                try writer.writeByte('n'),
            .object => {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeByte('o');
            },
            .array => try writer.writeByte('a'),
            .fd => try writer.writeByte('h'),
        }
    }

    fn emitType(arg: Arg, side: Side, writer: *io.Writer) !void {
        switch (arg.kind) {
            .int, .uint => {
                if (arg.enum_name) |name| {
                    if (mem.indexOfScalar(u8, name, '.')) |dot_index| {
                        // Turn a reference like wl_shm.format into common.wl.shm.Format
                        const us_index = mem.indexOfScalar(u8, name, '_') orelse 0;
                        try writer.print("common.{s}.{s}{f}", .{
                            name[0..us_index],
                            name[us_index + 1 .. dot_index + 1],
                            titleCase(name[dot_index + 1 ..]),
                        });
                    } else {
                        try writer.print("{f}", .{titleCase(name)});
                    }
                } else if (arg.kind == .int) {
                    try writer.writeAll("i32");
                } else {
                    try writer.writeAll("u32");
                }
            },
            .new_id => try writer.writeAll("u32"),
            .fixed => try writer.writeAll("common.Fixed"),
            .string => {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeAll("[*:0]const u8");
            },
            .object => |interface| if (interface) |i| {
                if (arg.allow_null) try writer.writeAll("?*") else try writer.writeByte('*');
                try printAbsolute(side, writer, i);
            } else {
                if (arg.allow_null) try writer.writeByte('?');
                try writer.writeAll("*common.Object");
            },
            .array => {
                try writer.writeAll("*common.Array");
            },
            .fd => try writer.writeAll("i32"),
        }
    }
};

/// All data in this struct is immutable after creation in parse().
const Enum = struct {
    name: []const u8,
    since: u32,
    entries: []const Entry,
    bitfield: bool,

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Enum {
        var name: ?[]const u8 = null;
        var since: ?u32 = null;
        var entries: std.ArrayList(Entry) = .empty;
        defer entries.deinit(gpa);
        var bitfield: ?bool = null;

        while (parser.next()) |ev| switch (ev) {
            .open_tag => |tag| {
                // TODO: parse description
                if (mem.eql(u8, tag, "entry"))
                    try entries.append(gpa, try Entry.parse(arena, parser));
            },
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "since")) {
                    if (since != null) return error.DuplicateSince;
                    since = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                } else if (mem.eql(u8, attr.name, "bitfield")) {
                    if (bitfield != null) return error.DuplicateBitfield;
                    if (!attr.valueEql("true") and !attr.valueEql("false")) return error.InvalidBoolValue;
                    bitfield = attr.valueEql("true");
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "enum")) {
                return Enum{
                    .name = name orelse return error.MissingName,
                    .since = since orelse 1,
                    .entries = try arena.dupe(Entry, entries.items),
                    .bitfield = bitfield orelse false,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    fn emit(e: Enum, target_version: u32, writer: *io.Writer) !void {
        try writer.print("const {f}", .{titleCase(e.name)});

        if (e.bitfield) {
            try writer.writeAll(" = packed struct(u32) {");
            for (0..32) |i| {
                for (e.entries) |entry| {
                    if (entry.since > target_version) continue;

                    const value = entry.intValue();
                    if (value == 0) continue;

                    if (value == (@as(u32, 1) << @intCast(i))) {
                        try writer.print("{s}: bool = false,", .{entry.name});
                        break;
                    }
                } else {
                    try writer.print("_padding{}: bool = false,", .{i});
                }
            }

            // Emit the normal C abi enum as well as it may be needed to interface
            // with C code.
            try writer.writeAll("pub const Enum ");
        }

        try writer.writeAll(" = enum(c_int) {");
        for (e.entries) |entry| {
            if (entry.since <= target_version) {
                try writer.print("{f}= {s},", .{ fmtId(entry.name), entry.value });
            }
        }
        // Always generate non-exhaustive enums to ensure forward compatability.
        // Entries have been added to wl_shm.format without bumping the version.
        try writer.writeAll("_,};\n");

        if (e.bitfield) try writer.writeAll("};\n");
    }
};

/// All data in this struct is immutable after creation in parse().
const Entry = struct {
    name: []const u8,
    since: u32,
    value: []const u8,

    fn parse(arena: mem.Allocator, parser: *xml.Parser) !Entry {
        var name: ?[]const u8 = null;
        var since: ?u32 = null;
        var value: ?[]const u8 = null;

        while (parser.next()) |ev| switch (ev) {
            .attribute => |attr| {
                if (mem.eql(u8, attr.name, "name")) {
                    if (name != null) return error.DuplicateName;
                    name = try attr.dupeValue(arena);
                } else if (mem.eql(u8, attr.name, "since")) {
                    if (since != null) return error.DuplicateSince;
                    since = try fmt.parseInt(u32, try attr.dupeValue(arena), 10);
                } else if (mem.eql(u8, attr.name, "value")) {
                    if (value != null) return error.DuplicateName;
                    value = try attr.dupeValue(arena);
                }
            },
            .close_tag => |tag| if (mem.eql(u8, tag, "entry")) {
                return Entry{
                    .name = name orelse return error.MissingName,
                    .since = since orelse 1,
                    .value = value orelse return error.MissingValue,
                };
            },
            else => {},
        };
        return error.UnexpectedEndOfFile;
    }

    // Return numeric value of enum entry. Can be base 10 and hexadecimal notation.
    fn intValue(e: Entry) u32 {
        return fmt.parseInt(u32, e.value, 10) catch blk: {
            const index = mem.indexOfScalar(u8, e.value, 'x').?;
            break :blk fmt.parseInt(u32, e.value[index + 1 ..], 16) catch @panic("Can't parse enum entry.");
        };
    }
};

fn prefix(s: []const u8) ?[]const u8 {
    return s[0 .. mem.indexOfScalar(u8, s, '_') orelse return null];
}

fn trimPrefix(s: []const u8) []const u8 {
    return s[mem.indexOfScalar(u8, s, '_').? + 1 ..];
}

const Case = enum { title, camel };

fn formatCaseImpl(comptime case: Case, comptime trim: bool) type {
    return struct {
        pub fn f(bytes: []const u8, writer: *io.Writer) io.Writer.Error!void {
            if (case == .camel and std.zig.Token.getKeyword(bytes) != null) {
                try writer.print("@\"{s}\"", .{bytes});
                return;
            }
            var upper = case == .title;
            const str = if (trim) trimPrefix(bytes) else bytes;
            for (str) |c| {
                if (c == '_') {
                    upper = true;
                    continue;
                }
                try writer.writeByte(if (upper) std.ascii.toUpper(c) else c);
                upper = false;
            }
        }
    };
}

fn titleCase(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.title, false).f) {
    return .{ .data = bytes };
}

fn titleCaseTrim(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.title, true).f) {
    return .{ .data = bytes };
}

fn camelCase(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.camel, false).f) {
    return .{ .data = bytes };
}

fn camelCaseTrim(bytes: []const u8) fmt.Alt([]const u8, formatCaseImpl(.camel, true).f) {
    return .{ .data = bytes };
}

fn printAbsolute(side: Side, writer: *io.Writer, interface: []const u8) !void {
    try writer.print("{s}.{s}.{f}", .{
        @tagName(side),
        prefix(interface) orelse return error.MissingPrefix,
        titleCaseTrim(interface),
    });
}

inline fn fatal(comptime msg: []const u8, args: anytype) noreturn {
    log.err(msg, args);
    posix.exit(1);
}
