#!/bin/sh -e
### BEGIN INIT INFO
# Provides:          vdev-settle
# Required-Start:    $local_fs
# Required-Stop:
# Should-Start:      vdev $network
# Default-Start:     S
# Default-Stop:
# Short-Description: Wait for vdev two-phase startup to settle
# X-Start-Before:    display-manager
### END INIT INFO

PATH="/sbin:/bin:/usr/bin"
. /lib/lsb/init-functions

# Top priority: find out the JSON file
# If there is no environment variable, we search the system's default path
_json="${VDEV_GLOBALS_JSON:-/etc/vdev/vdevd.globals.json}"

VDEV_HELPERS_DIR="/lib/vdev"
VDEV_STATE_DIR="/run/vdev"
VDEV_STAMP_DIR="/run/vdev"
VDEV_MODPROBE_STAMP="/run/vdev/vdevd-0-coldplug.ready"
VDEV_EARLY_STAMP="/run/vdev/vdevd-1-coldplug.ready"

if [ -r "$_json" ] && command -v jq >/dev/null 2>&1; then
    # Clean up comments // and also /* */
	VDEV_JSON_DATA="$(sed -E 's|//.*||g' "$_json" |
        sed 's|/\*.*\*/||g' |
        grep -v '^$' |
        jq -r .)"
    export VDEV_JSON_DATA
fi

get_json_get_key() {
    printf '%s' "$VDEV_JSON_DATA" | jq -r --arg k "$1" '
      .globals[$k] // empty'
}

get_json_get_stamp() {
    printf '%s' "$VDEV_JSON_DATA" | jq -r --arg s "$1" '
      .states[$s].stamp // empty'
}

if [ -n "$VDEV_JSON_DATA" ]; then
    _helpers_dir="$(get_json_get_key "helpers-dir")"
    VDEV_HELPERS_DIR="${_helpers_dir:-/lib/vdev}"

    _state_dir="$(get_json_get_key "states-stamp-dir")"
    if [ -z "$_state_dir" ]; then
        _state_dir="$(get_json_get_key "stamp_dir")"
    fi
    VDEV_STATE_DIR="${_state_dir:-/run/vdev}"
    VDEV_STAMP_DIR="${_state_dir:-/run/vdev}"

    _stamp_modprobe="$(get_json_get_stamp "modprobe")"
    VDEV_MODPROBE_STAMP="${VDEV_STAMP_DIR}/${_stamp_modprobe:-vdevd-0-coldplug.ready}"

    _stamp_early="$(get_json_get_stamp "early")"
    VDEV_EARLY_STAMP="${VDEV_STAMP_DIR}/${_stamp_early:-vdevd-1-coldplug.ready}"
fi

VDEV_SETTLE_CTL="${VDEV_STATE_DIR}/vdev-settle.ctl"
VDEV_SETTLE_RSP="${VDEV_STATE_DIR}/vdev-settle.rsp"
VDEV_SETTLE_BIN="${VDEV_HELPERS_DIR}/vdev_settle"
VDEV_SETTLE_RETRIGGERED="${VDEV_STATE_DIR}/vdev-settle.retriggered"
VDEV_SETTLE_TRIGGER_FAILED="${VDEV_STATE_DIR}/vdev-settle.trigger-failed"

# Bounded poll until a stamp path exists.
# $1 = stamp path; $2 = max iterations (default 25); $3 = sleep seconds per step (default 0.2).
# Returns 0 if the stamp appeared, 1 on timeout.
vdev_wait_for_stamp() {
    _vs_path="$1"
    _vs_max="${2:-25}"
    _vs_sleep="${3:-0.2}"
    _vs_i=0
    while [ "$_vs_i" -lt "$_vs_max" ]; do
        [ -e "$_vs_path" ] && return 0
        sleep "$_vs_sleep"
        _vs_i=$((_vs_i + 1))
    done
    return 1
}

# Query vdev_settle FIFO (open rsp before writing ctl).
# $1 = command (status | list | list-pending | reload)
vdev_settle_cmd() {
    _scmd="$1"
    if [ ! -p "$VDEV_SETTLE_CTL" ] || [ ! -p "$VDEV_SETTLE_RSP" ]; then
        return 1
    fi
    (
        exec 3<"$VDEV_SETTLE_RSP"
        printf '%s\n' "$_scmd" >"$VDEV_SETTLE_CTL"
        cat <&3
    )
}

# Stop vdev_settle daemon (SIGTERM) using its pid file.
vdev_settle_stop_daemon() {
    if [ ! -f "${VDEV_STATE_DIR}/vdev-settle.pid" ]; then
        return 0
    fi
    read -r _vs_pid < "${VDEV_STATE_DIR}/vdev-settle.pid" 2>/dev/null || _vs_pid=""
    if [ -n "$_vs_pid" ]; then
        kill -TERM "$_vs_pid" 2>/dev/null || true
    fi
    rm -f "${VDEV_STATE_DIR}/vdev-settle.pid"
}

# Return 0 if event node (e.g. event0) is listed by vdev_settle/libinput.
# $1 = seen list text; $2 = event node name
vdev_settle_event_seen() {
    _slist="$1"
    _sev="$2"
    printf '%s\n' "$_slist" | cut -f1 | grep -Fx "$_sev" >/dev/null 2>&1
}

# Return 0 if we already logged a successful uevent re-add for this event node.
vdev_settle_event_triggered() {
    _tev="$1"
    [ -f "$VDEV_SETTLE_RETRIGGERED" ] || return 1
    grep -Fx "$_tev" "$VDEV_SETTLE_RETRIGGERED" >/dev/null 2>&1
}

# Inject a kernel "add" uevent for one input event node (for vdevd hotplug).
# $1 = event node name (e.g. event3). Returns 0 on success.
vdev_settle_trigger_add() {
    _tev="$1"
    _tuevent="/sys/class/input/${_tev}/uevent"

    if [ ! -e "$_tuevent" ]; then
        return 1
    fi
    if ! printf 'add\n' >"$_tuevent" 2>/dev/null; then
        if command -v udevadm >/dev/null 2>&1 &&
           [ -e "/sys/class/input/${_tev}" ] &&
           udevadm trigger --action=add "/sys/class/input/${_tev}" >/dev/null 2>&1; then
            :
        else
            printf '%s %s\n' "$(date -Iseconds 2>/dev/null || date)" "$_tev" \
                >>"$VDEV_SETTLE_TRIGGER_FAILED"
            return 1
        fi
    fi
    if ! vdev_settle_event_triggered "$_tev"; then
        printf '%s\n' "$_tev" >>"$VDEV_SETTLE_RETRIGGERED"
    fi
    return 0
}

# Enumerate /sys/class/input/event* (avoid shell glob quirks when the set is large).
vdev_settle_foreach_event() {
    if [ ! -d /sys/class/input ]; then
        return 0
    fi
    for _fed in /sys/class/input/event*; do
        [ -e "$_fed" ] || continue
        basename "$_fed"
    done
}

# One-shot: uevent-add every event* so vdevd hotplug can catch coldplug misses.
vdev_settle_trigger_all_events() {
    _fta_ev=""
    for _fta_ev in $(vdev_settle_foreach_event); do
        vdev_settle_trigger_add "$_fta_ev" || true
    done
}

# Poll sysfs input nodes: uevent-add each event* for vdevd, exit when libinput is in sync.
vdev_input_settle_worker() {
    # _vw_i: loop counter (0.5 s per step → up to _vw_max steps).
    _vw_i=0
    # _vw_max: upper bound on poll iterations (~60 s with sleep 0.5).
    _vw_max=60
    # _vw_stable: consecutive polls where every event* is visible to libinput (seat0);
    # libinput visibility must NOT skip uevent add — vdevd.1 can miss nodes X already sees.
    _vw_stable=0
    _vw_stable_rounds=5

    modprobe -q evdev 2>/dev/null || true

    mkdir -p "$VDEV_STATE_DIR"
    : >"$VDEV_SETTLE_RETRIGGERED"
    : >"$VDEV_SETTLE_TRIGGER_FAILED"

    # vdevd.1 coldplug stamp (QUEUE_TRACKING_FLAG=2) before retrigger; worker may run
    # parallel to the init subshell that exec's vdevd -H. Brief pause after the stamp
    # (VDEV_SETTLE_VDEVD_DELAY, default 0.5s) so hotplug-only vdevd is listening.
    vdev_wait_for_stamp "$VDEV_EARLY_STAMP" 250 0.1 || true
    sleep "${VDEV_SETTLE_VDEVD_DELAY:-0.5}"

    # Initial pass once long-running vdevd should be listening on netlink.
    vdev_settle_trigger_all_events

    if [ -x "$VDEV_SETTLE_BIN" ]; then
        "$VDEV_SETTLE_BIN" --state-dir "$VDEV_STATE_DIR" || true
        _vw_i=0
        while [ "$_vw_i" -lt 50 ]; do
            [ -p "$VDEV_SETTLE_CTL" ] && [ -p "$VDEV_SETTLE_RSP" ] && break
            sleep 0.1
            _vw_i=$((_vw_i + 1))
        done
    fi

    _vw_i=0
    while [ "$_vw_i" -lt "$_vw_max" ]; do
        _vw_seen="$(vdev_settle_cmd list 2>/dev/null || true)"
        _vw_pending=0
        _vw_ev=""

        for _vw_ev in $(vdev_settle_foreach_event); do
            # vdevd catch-up: one uevent add per sysfs node (independent of libinput).
            if ! vdev_settle_event_triggered "$_vw_ev"; then
                vdev_settle_trigger_add "$_vw_ev" || true
            fi
            # Settle exit criterion only: libinput has seen this event*.
            if ! vdev_settle_event_seen "$_vw_seen" "$_vw_ev"; then
                _vw_pending=1
            fi
        done

        if [ "$_vw_pending" -eq 0 ]; then
            _vw_stable=$((_vw_stable + 1))
            
            if [ "$_vw_stable" -ge "$_vw_stable_rounds" ]; then
                break
            fi
        else
            _vw_stable=0
        fi

        sleep 0.5
        _vw_i=$((_vw_i + 1))
    done

    # Settle done (or timed out): monitor no longer needed until next boot.
    vdev_settle_stop_daemon
}

case "$1" in
    start)
        log_daemon_msg "Waiting for vdev settle" "vdev-settle"
	
        # vdevd.0: wait for os-queue-tracking stamp (VDEV_OS_QUEUE_TRACKING_FLAG=1).
        vdev_wait_for_stamp "$VDEV_MODPROBE_STAMP" || true

        # vdevd.1: wait for os-queue-tracking stamp (VDEV_OS_QUEUE_TRACKING_FLAG=2).
        vdev_wait_for_stamp "$VDEV_EARLY_STAMP" || true

        # Detached worker: libinput monitor + sysfs uevent re-add; does not block init stop.
        (
            vdev_input_settle_worker
        ) >/dev/null 2>&1 &
        ;;
    stop)
        vdev_settle_stop_daemon
        ;;
    restart|force-reload|status)
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|force-reload|status}" >&2
        exit 1
        ;;
esac

exit 0
