const std = @import("../std.zig");
const build = std.build;
const Step = build.Step;
const Builder = build.Builder;
const WriteFileStep = build.WriteFileStep;
const LibExeObjStep = build.LibExeObjStep;
const CheckFileStep = build.CheckFileStep;
const fs = std.fs;
const mem = std.mem;
const CrossTarget = std.zig.CrossTarget;

pub const TranslateCStep = struct {
    step: Step,
    builder: *Builder,
    source: build.FileSource,
    include_dirs: std.ArrayList([]const u8),
    output_dir: ?[]const u8,
    out_basename: []const u8,
    target: CrossTarget = CrossTarget{},

    pub fn create(builder: *Builder, source: build.FileSource) *TranslateCStep {
        const self = builder.allocator.create(TranslateCStep) catch unreachable;
        self.* = TranslateCStep{
            .step = Step.init(.TranslateC, "translate-c", builder.allocator, make),
            .builder = builder,
            .source = source,
            .include_dirs = std.ArrayList([]const u8).init(builder.allocator),
            .output_dir = null,
            .out_basename = undefined,
        };
        source.addStepDependencies(&self.step);
        return self;
    }

    /// Unless setOutputDir was called, this function must be called only in
    /// the make step, from a step that has declared a dependency on this one.
    /// To run an executable built with zig build, use `run`, or create an install step and invoke it.
    pub fn getOutputPath(self: *TranslateCStep) []const u8 {
        return fs.path.join(
            self.builder.allocator,
            &[_][]const u8{ self.output_dir.?, self.out_basename },
        ) catch unreachable;
    }

    pub fn setTarget(self: *TranslateCStep, target: CrossTarget) void {
        self.target = target;
    }

    /// Creates a step to build an executable from the translated source.
    pub fn addExecutable(self: *TranslateCStep) *LibExeObjStep {
        return self.builder.addExecutableSource("translated_c", @as(build.FileSource, .{ .translate_c = self }));
    }

    pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void {
        self.include_dirs.append(include_dir) catch unreachable;
    }

    pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {
        return CheckFileStep.create(self.builder, .{ .translate_c = self }, expected_matches);
    }

    fn make(step: *Step) !void {
        const self = @fieldParentPtr(TranslateCStep, "step", step);

        var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
        try argv_list.append(self.builder.zig_exe);
        try argv_list.append("translate-c");
        try argv_list.append("-lc");

        try argv_list.append("--cache");
        try argv_list.append("on");

        if (!self.target.isNative()) {
            try argv_list.append("-target");
            try argv_list.append(try self.target.zigTriple(self.builder.allocator));
        }

        for (self.include_dirs.items) |include_dir| {
            try argv_list.append("-I");
            try argv_list.append(include_dir);
        }

        try argv_list.append(self.source.getPath(self.builder));

        const output_path_nl = try self.builder.execFromStep(argv_list.span(), &self.step);
        const output_path = mem.trimRight(u8, output_path_nl, "\r\n");

        self.out_basename = fs.path.basename(output_path);
        if (self.output_dir) |output_dir| {
            const full_dest = try fs.path.join(self.builder.allocator, &[_][]const u8{ output_dir, self.out_basename });
            try self.builder.updateFile(output_path, full_dest);
        } else {
            self.output_dir = fs.path.dirname(output_path).?;
        }
    }
};
