#!/usr/bin/env bash
# rice-display - monitor state for the eww panel, and the actions it runs.
#
#   rice-display info             JSON: output, modes, refresh rates, DDC brightness
#   rice-display brightness <0-100>
#   rice-display mode <id>        switch resolution (reverts in 15s unless you keep it)
#   rice-display rate <id>        switch refresh rate at the current resolution
set -uo pipefail

run="${XDG_RUNTIME_DIR:-/tmp}/rice"
modes_tsv="$run/modes.tsv"
rates_tsv="$run/rates.tsv"
bright_cache="$run/brightness"
mkdir -p "$run"

say() { rice-notify send "$1" "${2:-}" -i video-display; }

primary() {
  xrandr --query | awk '/ connected primary/ { print $1; exit } / connected/ && !first { first = $1 } END { if (first) print first }' | head -n1
}

# "WxH<TAB>rate<TAB>current(1/0)" for every mode of the output, xrandr order.
mode_lines() {
  xrandr --query | awk -v out="$1" '
    $1 == out { inblock = 1; next }
    inblock && /^[^ ]/ { exit }
    inblock {
      mode = $1
      for (i = 2; i <= NF; i++) {
        r = $i; cur = (r ~ /\*/) ? 1 : 0; gsub(/[*+]/, "", r)
        if (r != "") printf "%s\t%s\t%d\n", mode, r, cur
      }
    }'
}

# DDC is slow (up to a second per read), so reads are cached for a minute.
brightness() {
  if [[ -f $bright_cache ]] && (( $(date +%s) - $(stat -c %Y "$bright_cache") < 60 )); then
    cat "$bright_cache"; return
  fi
  local v
  v=$(ddcutil getvcp 10 --brief 2>/dev/null | awk '$1 == "VCP" { print $4; exit }')
  [[ -n $v ]] && echo "$v" | tee "$bright_cache"
}

info() {
  local out cur_mode cur_rate
  out=$(primary)
  mode_lines "$out" > "$run/all-modes.tsv"
  IFS=$'\t' read -r cur_mode cur_rate _ < <(awk -F'\t' '$3 == 1' "$run/all-modes.tsv")

  awk -F'\t' '!seen[$1]++ { print $1 }' "$run/all-modes.tsv" | head -n 6 |
    awk -v cur="$cur_mode" '{ printf "%d\t%s\t%d\n", NR - 1, $1, ($1 == cur) }' > "$modes_tsv"
  # One entry per rounded rate (monitors list 120.00, 119.98 and 119.88), keeping the active one.
  awk -F'\t' -v cur="$cur_mode" '$1 == cur { printf "%d\t%s\t%d\n", n++, $2, $3 }' "$run/all-modes.tsv" |
    sort -t$'\t' -k3,3nr -k2,2nr | awk -F'\t' '!seen[int($2 + 0.5)]++' |
    sort -t$'\t' -k2,2nr | head -n 5 > "$rates_tsv"

  local b ddc=false
  if command -v ddcutil >/dev/null; then
    b=$(brightness)
    [[ -n $b ]] && ddc=true
  fi

  jq -cn --arg output "$out" --arg mode "$cur_mode" --arg rate "$cur_rate" \
    --argjson ddc "$ddc" --argjson brightness "${b:-0}" \
    --argjson bar "$(pgrep -x polybar >/dev/null && echo true || echo false)" \
    --argjson modes "$(jq -R -s -c 'split("\n") | map(select(length > 0) | split("\t") | {id: (.[0] | tonumber), mode: .[1], current: (.[2] == "1")})' "$modes_tsv")" \
    --argjson rates "$(jq -R -s -c 'split("\n") | map(select(length > 0) | split("\t") | {id: (.[0] | tonumber), label: ((.[1] | tonumber | round | tostring) + "Hz"), current: (.[2] == "1")})' "$rates_tsv")" \
    '{output: $output, mode: $mode, rate: $rate, ddc: $ddc, brightness: $brightness, bar: $bar, modes: $modes, rates: $rates,
      ddcHint: "Needs ddcutil and the i2c-dev module"}'
}

# Apply an xrandr change, then revert unless it is confirmed within 15 seconds.
apply_with_revert() {
  local out="$1" old_mode="$2" old_rate="$3" new_mode="$4" new_rate="$5"
  xrandr --output "$out" --mode "$new_mode" --rate "$new_rate" || { say "Display" "That mode did not apply"; return 1; }
  local keep
  keep=$(printf 'Keep %s @ %sHz\nRevert\n' "$new_mode" "$new_rate" |
    timeout 15 rofi -dmenu -theme "$HOME/.config/rofi/menu.rasi" -p "Display" -mesg "Reverting in 15 seconds") || keep=""
  if [[ $keep != Keep* ]]; then
    xrandr --output "$out" --mode "$old_mode" --rate "$old_rate"
    say "Display" "Reverted to $old_mode @ ${old_rate}Hz"
  fi
}

case "${1:-}" in
  info) info ;;
  brightness)
    [[ ${2:-} =~ ^[0-9]+$ ]] && (( $2 <= 100 )) || { echo "rice-display: brightness 0-100" >&2; exit 2; }
    echo "$2" > "$bright_cache"
    # Sliders fire many values; only the newest one talks to the monitor.
    pkill -u "$UID" -f 'ddcutil setvcp 10' 2>/dev/null
    setsid -f ddcutil setvcp 10 "$2" --noverify >/dev/null 2>&1
    ;;
  mode|rate)
    [[ ${2:-} =~ ^[0-9]+$ ]] || { echo "rice-display: $1 takes an id" >&2; exit 2; }
    out=$(primary)
    IFS=$'\t' read -r old_mode old_rate _ < <(mode_lines "$out" | awk -F'\t' '$3 == 1')
    if [[ $1 == mode ]]; then
      new_mode=$(awk -F'\t' -v id="$2" '$1 == id { print $2 }' "$modes_tsv")
      new_rate=$(mode_lines "$out" | awk -F'\t' -v m="$new_mode" '$1 == m { print $2 }' | sort -nr | head -n1)
    else
      new_mode="$old_mode"
      new_rate=$(awk -F'\t' -v id="$2" '$1 == id { print $2 }' "$rates_tsv")
    fi
    [[ -n $new_mode && -n $new_rate ]] || exit 1
    rice-panel close
    apply_with_revert "$out" "$old_mode" "$old_rate" "$new_mode" "$new_rate"
    ;;
  *) sed -n '2,8p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;;
esac
