const std = @import("std");

// WARNING: Has to be in sync with `runner`

pub const RUNNER_CTL_FIFO = "/tmp/runner.ctl.fifo";
pub const RUNNER_ACK_FIFO = "/tmp/runner.ack.fifo";

// The different markers that can be set in the perf.data.
//
// `SampleStart/End`: Marks the start and end of a sampling period. This is used to differentiate between benchmarks.
// `BenchmarkStart/End`: Marks the start and end of a benchmark. This is used to measure the duration of a benchmark, without the benchmark harness code.
pub const MarkerType = union(enum) {
    SampleStart: u64,
    SampleEnd: u64,
    BenchmarkStart: u64,
    BenchmarkEnd: u64,

    pub fn format(
        self: MarkerType,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = fmt;
        _ = options;
        switch (self) {
            .SampleStart => |ts| try writer.print("SampleStart({d})", .{ts}),
            .SampleEnd => |ts| try writer.print("SampleEnd({d})", .{ts}),
            .BenchmarkStart => |ts| try writer.print("BenchmarkStart({d})", .{ts}),
            .BenchmarkEnd => |ts| try writer.print("BenchmarkEnd({d})", .{ts}),
        }
    }

    pub fn equal(self: MarkerType, other: MarkerType) bool {
        return std.meta.eql(self, other);
    }
};

pub const IntegrationMode = enum {
    Perf,
    Simulation,
    Analysis,
};

pub const Command = union(enum) {
    ExecutedBenchmark: struct {
        pid: u32,
        uri: []const u8,
    },
    StartBenchmark,
    StopBenchmark,
    Ack,
    PingPerf,
    SetIntegration: struct {
        name: []const u8,
        version: []const u8,
    },
    Err,
    AddMarker: struct {
        pid: u32,
        marker: MarkerType,
    },
    SetVersion: u64,
    GetIntegrationMode,
    IntegrationModeResponse: IntegrationMode,

    pub fn deinit(self: Command, allocator: std.mem.Allocator) void {
        switch (self) {
            .SetIntegration => |data| {
                allocator.free(data.name);
                allocator.free(data.version);
            },
            .ExecutedBenchmark => |data| allocator.free(data.uri),
            .SetVersion => {},
            .GetIntegrationMode => {},
            .IntegrationModeResponse => {},
            else => {},
        }
    }

    pub fn format(
        self: Command,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = fmt;
        _ = options;
        switch (self) {
            .ExecutedBenchmark => |data| try writer.print("ExecutedBenchmark {{ pid: {d}, uri: {s} }}", .{ data.pid, data.uri }),
            .StartBenchmark => try writer.writeAll("StartBenchmark"),
            .StopBenchmark => try writer.writeAll("StopBenchmark"),
            .Ack => try writer.writeAll("Ack"),
            .PingPerf => try writer.writeAll("PingPerf"),
            .SetIntegration => |data| try writer.print("SetIntegration {{ name: {s}, version: {s} }}", .{ data.name, data.version }),
            .Err => try writer.writeAll("Err"),
            .AddMarker => |data| try writer.print("AddMarker {{ pid: {d}, marker: {} }}", .{ data.pid, data.marker }),
            .SetVersion => |data| try writer.print("SetVersion {{ protocol_version: {d} }}", .{data}),
            .GetIntegrationMode => try writer.writeAll("GetIntegrationMode"),
            .IntegrationModeResponse => |mode| try writer.print("IntegrationModeResponse {}", .{mode}),
        }
    }

    pub fn equal(self: Command, other: Command) bool {
        return switch (self) {
            .ExecutedBenchmark => |self_data| switch (other) {
                .ExecutedBenchmark => |other_data| self_data.pid == other_data.pid and
                    std.mem.eql(u8, self_data.uri, other_data.uri),
                else => false,
            },
            .StartBenchmark => switch (other) {
                .StartBenchmark => true,
                else => false,
            },
            .StopBenchmark => switch (other) {
                .StopBenchmark => true,
                else => false,
            },
            .Ack => switch (other) {
                .Ack => true,
                else => false,
            },
            .PingPerf => switch (other) {
                .PingPerf => true,
                else => false,
            },
            .SetIntegration => |self_data| switch (other) {
                .SetIntegration => |other_data| std.mem.eql(u8, self_data.name, other_data.name) and
                    std.mem.eql(u8, self_data.version, other_data.version),
                else => false,
            },
            .Err => switch (other) {
                .Err => true,
                else => false,
            },
            .AddMarker => |self_data| switch (other) {
                .AddMarker => |other_data| self_data.pid == other_data.pid and self_data.marker.equal(other_data.marker),
                else => false,
            },
            .SetVersion => |self_data| switch (other) {
                .SetVersion => |other_data| self_data == other_data,
                else => false,
            },
            .GetIntegrationMode => switch (other) {
                .GetIntegrationMode => true,
                else => false,
            },
            .IntegrationModeResponse => |self_mode| switch (other) {
                .IntegrationModeResponse => |other_mode| self_mode == other_mode,
                else => false,
            },
        };
    }
};
