//! Process-level runtime types: what a user program receives at entry (`Init`,
//! the argv contract) and the process end of the lifecycle
//! (docs/process-lifecycle.md) — today the exit reason a supervisor reads to
//! decide restart; signals and the stop sequence land here with M17.4. Mirrors
//! the spirit of `std.process.Init.Minimal` in danos terms — std's `Args` holds
//! no data on freestanding targets, so the type is danos's own.

const std = @import("std");
const abi = @import("abi");
const sc = @import("system-call.zig");
const ipc = @import("ipc.zig");
const system = @import("system.zig");

/// Everything a program receives at entry. Passed to
/// `pub fn main(init: runtime.process.Init)`; programs that need nothing keep
/// `pub fn main() void`. An `environment` field is added here once the kernel
/// passes a non-empty envp (today it is always empty — see docs/sysv.md).
pub const Init = struct {
    arguments: Arguments,
};

/// The process arguments (argc/argv), parsed from the kernel-built System V
/// entry block. The bytes live in the entry block at the top of the stack page,
/// NUL-terminated, valid for the process's lifetime.
pub const Arguments = struct {
    /// argc — at least 1: argument 0 is the path or name this binary was
    /// spawned as.
    count: usize,
    /// The argv pointers in the entry block (NULL-terminated after `count`
    /// entries).
    vector: [*]const [*:0]const u8,

    /// Argument `index` (0 = the program's own path/name), or null if out of
    /// range.
    pub fn get(arguments: Arguments, index: usize) ?[:0]const u8 {
        if (index >= arguments.count) return null;
        return std.mem.span(arguments.vector[index]);
    }

    pub fn iterate(arguments: Arguments) Iterator {
        return .{ .arguments = arguments };
    }

    pub const Iterator = struct {
        arguments: Arguments,
        index: usize = 0,

        pub fn next(iterator: *Iterator) ?[:0]const u8 {
            const argument = iterator.arguments.get(iterator.index) orelse return null;
            iterator.index += 1;
            return argument;
        }
    };
};

/// How a process ended — what a supervisor's restart policy reads: a clean exit
/// meant to stop, a fault wants a restart with backoff, killed means the
/// supervisor did it itself (docs/process-lifecycle.md).
pub const ExitReason = abi.ExitReason;

/// How dead child `id` ended. Ask after the exit notification arrives — the
/// kernel records the reason before it posts the notification, so this never
/// races it. Returns null for an id that never lived, is still alive, was
/// evicted from the kernel's bounded record, or is not this process's child
/// (the same authority gate as `kill`).
pub fn exitReason(id: u32) ?ExitReason {
    const r = sc.systemCall1(.process_exit_reason, id);
    if (r > ~@as(usize, 0) - 4095) return null; // a wrapped -errno
    return @enumFromInt(r);
}

/// The signal vocabulary (docs/process-lifecycle.md): POSIX's concepts, danos's
/// names, message delivery. A signal is a one-way coalescing statement — never a
/// question (liveness is the zero-length ping call) and never kill (that is
/// `system.kill`, unhandleable by definition).
pub const Signal = abi.Signal;

/// The coalesced set of signals one notification delivered: two pending
/// terminates arrive as one. Decode a received badge with `signalsFrom`.
pub const SignalSet = struct {
    pending: u32,

    pub fn has(set: SignalSet, signal: Signal) bool {
        return set.pending & (@as(u32, 1) << @intFromEnum(signal)) != 0;
    }
};

/// Nominate `endpoint` as this process's signal endpoint. Signals posted while
/// unbound have pended; they are delivered immediately on bind, coalesced.
pub fn bindSignals(endpoint: usize) bool {
    return sc.systemCall1(.signal_bind, endpoint) == 0;
}

/// Decode a received badge into the signals it delivered, or null if it is not
/// a signal notification.
pub fn signalsFrom(badge: u64) ?SignalSet {
    if (badge & abi.notify_badge_bit == 0 or badge & abi.notify_signal_bit == 0) return null;
    return .{ .pending = @truncate(badge & ~(abi.notify_badge_bit | abi.notify_signal_bit)) };
}

/// Post `signal` to child `id` (or to yourself). Supervisor-gated, like kill;
/// non-blocking, always — a statement, not a conversation.
pub fn sendSignal(id: u32, signal: Signal) bool {
    return sc.systemCall2(.process_signal, id, @intFromEnum(signal)) == 0;
}

/// The standard stop sequence (docs/process-lifecycle.md): terminate, wait up to
/// `deadline_ms` for the exit notification on `exit_endpoint` (the endpoint the
/// child was spawned with), then kill. Any *other* notifications arriving on
/// that endpoint while stopping are consumed and dropped — a supervisor with
/// concurrent traffic implements the same sequence inside its own event loop
/// (arm `system.timerOnce`, keep serving) instead of calling this.
pub fn stop(id: u32, deadline_ms: u64, exit_endpoint: usize) void {
    _ = sendSignal(id, .terminate);
    _ = system.timerOnce(exit_endpoint, deadline_ms);
    var receive: [8]u8 = undefined;
    while (true) {
        const got = ipc.replyWait(exit_endpoint, &.{}, &receive, null);
        if (got.isChildExit() and got.childProcessId() == id) return;
        if (got.isTimer()) break; // the deadline passed first — escalate
    }
    _ = system.kill(id);
    while (true) {
        const got = ipc.replyWait(exit_endpoint, &.{}, &receive, null);
        if (got.isChildExit() and got.childProcessId() == id) return;
    }
}

/// Subscribe `endpoint` to published exit events: every process death posts an
/// asynchronous notification with the same badge encoding as a supervisor's exit
/// notice (decode with `ipc.Received.isChildExit`/`childProcessId`). For stateful
/// services: release what the dead client held — file handles, subscriptions —
/// because a service must never depend on clients cleaning up after themselves
/// (docs/process-lifecycle.md). Ungated, like `system.processes`.
pub fn subscribeExits(endpoint: usize) bool {
    return sc.systemCall1(.process_subscribe, endpoint) == 0;
}
