const std = @import("std");
const mem = std.mem;
const LibCInstallation = std.zig.LibCInstallation;

const usage_libc =
    \\Usage: zig libc
    \\
    \\    Detect the native libc installation and print the resulting
    \\    paths to stdout. You can save this into a file and then edit
    \\    the paths to create a cross compilation libc kit. Then you
    \\    can pass `--libc [file]` for Zig to use it.
    \\
    \\Usage: zig libc [paths_file]
    \\
    \\    Parse a libc installation text file and validate it.
    \\
    \\Options:
    \\  -h, --help             Print this help and exit
    \\  -target [name]         <arch><sub>-<os>-<abi> see the targets command
    \\  -includes              Print the libc include directories for the target
    \\
;

var stdout_buffer: [4096]u8 = undefined;

pub fn main() !void {
    var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena_instance.deinit();
    const arena = arena_instance.allocator();
    const gpa = arena;

    const args = try std.process.argsAlloc(arena);
    const zig_lib_directory = args[1];

    var input_file: ?[]const u8 = null;
    var target_arch_os_abi: []const u8 = "native";
    var print_includes: bool = false;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;
    {
        var i: usize = 2;
        while (i < args.len) : (i += 1) {
            const arg = args[i];
            if (mem.startsWith(u8, arg, "-")) {
                if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
                    try stdout.writeAll(usage_libc);
                    try stdout.flush();
                    return std.process.cleanExit();
                } else if (mem.eql(u8, arg, "-target")) {
                    if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
                    i += 1;
                    target_arch_os_abi = args[i];
                } else if (mem.eql(u8, arg, "-includes")) {
                    print_includes = true;
                } else {
                    fatal("unrecognized parameter: '{s}'", .{arg});
                }
            } else if (input_file != null) {
                fatal("unexpected extra parameter: '{s}'", .{arg});
            } else {
                input_file = arg;
            }
        }
    }

    const target_query = std.zig.parseTargetQueryOrReportFatalError(gpa, .{
        .arch_os_abi = target_arch_os_abi,
    });
    const target = std.zig.resolveTargetQueryOrFatal(target_query);

    if (print_includes) {
        const libc_installation: ?*LibCInstallation = libc: {
            if (input_file) |libc_file| {
                const libc = try arena.create(LibCInstallation);
                libc.* = LibCInstallation.parse(arena, libc_file, &target) catch |err| {
                    fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) });
                };
                break :libc libc;
            } else {
                break :libc null;
            }
        };

        const is_native_abi = target_query.isNativeAbi();

        const libc_dirs = std.zig.LibCDirs.detect(
            arena,
            zig_lib_directory,
            &target,
            is_native_abi,
            true,
            libc_installation,
        ) catch |err| {
            const zig_target = try target.zigTriple(arena);
            fatal("unable to detect libc for target {s}: {s}", .{ zig_target, @errorName(err) });
        };

        if (libc_dirs.libc_include_dir_list.len == 0) {
            const zig_target = try target.zigTriple(arena);
            fatal("no include dirs detected for target {s}", .{zig_target});
        }

        for (libc_dirs.libc_include_dir_list) |include_dir| {
            try stdout.writeAll(include_dir);
            try stdout.writeByte('\n');
        }
        try stdout.flush();
        return std.process.cleanExit();
    }

    if (input_file) |libc_file| {
        var libc = LibCInstallation.parse(gpa, libc_file, &target) catch |err| {
            fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) });
        };
        defer libc.deinit(gpa);
    } else {
        if (!target_query.canDetectLibC()) {
            fatal("unable to detect libc for non-native target", .{});
        }
        var libc = LibCInstallation.findNative(.{
            .allocator = gpa,
            .verbose = true,
            .target = &target,
        }) catch |err| {
            fatal("unable to detect native libc: {s}", .{@errorName(err)});
        };
        defer libc.deinit(gpa);

        try libc.render(stdout);
        try stdout.flush();
    }
}

fn fatal(comptime format: []const u8, args: anytype) noreturn {
    std.log.err(format, args);
    std.process.exit(1);
}
