#!/bin/bash
# dsys - system settings on Das OS: host name, time zone, language and
# console keyboard, in place of hostnamectl, timedatectl and localectl.
set -euo pipefail

# shellcheck source=helpers.sh
. "${DAS_HELPERS:-/usr/lib/das/helpers.sh}"

zoneinfo=/usr/share/zoneinfo
libc_locales=/etc/default/libc-locales
keymaps=/usr/share/kbd/keymaps

usage() {
  if [[ -n $uk ]]; then
    cat <<'EOF'
Використання: dsys [КОМАНДА] [ЗНАЧЕННЯ]

  info                    огляд системи (типова команда)
  hostname [ІМ'Я]         показати або змінити ім'я хоста
  timezone [ЗОНА]         показати або змінити часовий пояс (Europe/Kyiv,
                          достатньо й частини назви: Kyiv)
  timezone list [ФІЛЬТР]  усі часові пояси
  locale [МОВА]           показати або змінити мову системи (uk_UA, en_US)
  locale list [ФІЛЬТР]    доступні мови; * — уже зібрані
  keymap [РОЗКЛАДКА]      показати або змінити розкладку консолі (us, ua-utf)
  keymap list [ФІЛЬТР]    доступні розкладки консолі

Мову й розкладку графічного сеансу кожен користувач задає в налаштуваннях
Xfce і в das-welcome; dsys змінює типові для системи й консолі.
EOF
  else
    cat <<'EOF'
Usage: dsys [COMMAND] [VALUE]

  info                    system overview (the default)
  hostname [NAME]         show or change the host name
  timezone [ZONE]         show or change the time zone (Europe/Kyiv,
                          part of the name will do: Kyiv)
  timezone list [FILTER]  all time zones
  locale [LANGUAGE]       show or change the system language (uk_UA, en_US)
  locale list [FILTER]    available languages; * marks the built ones
  keymap [MAP]            show or change the console keyboard map (us, ua-utf)
  keymap list [FILTER]    available console keyboard maps

In the graphical session every user picks the language and keyboard in the
Xfce settings and in das-welcome; dsys changes the system and console defaults.
EOF
  fi
}

current_hostname() {
  cat /etc/hostname 2>/dev/null || hostname
}

current_timezone() {
  local tz
  tz=$(readlink /etc/localtime 2>/dev/null || true)
  echo "${tz#*zoneinfo/}"
}

current_locale() {
  sed -n 's/^LANG=//p' /etc/locale.conf 2>/dev/null | tr -d '"'
}

rc_value() {
  sed -n "s/^$1=//p" /etc/rc.conf 2>/dev/null | tr -d '"' | tail -n 1
}

# A graphical session keeps its X authority under the old host name
session_warning() {
  if pgrep -x lightdm >/dev/null || [[ -n ${DISPLAY:-} ]]; then
    say "Перезавантажте комп'ютер: графічний сеанс не відкриватиме нових вікон зі старим іменем." \
      "Reboot: the graphical session will not open new windows under the old name."
  fi
}

# 3 д 4 год 5 хв / 3 d 4 h 5 min
uptime_text() {
  local s d h m
  read -r s _ </proc/uptime
  s=${s%.*}
  d=$((s / 86400))
  h=$((s % 86400 / 3600))
  m=$((s % 3600 / 60))
  if ((d > 0)); then
    say "$d д $h год $m хв" "$d d $h h $m min"
  elif ((h > 0)); then
    say "$h год $m хв" "$h h $m min"
  else
    say "$m хв" "$m min"
  fi
}

cmd_info() {
  local ntp line
  pad "$(say "Ім'я хоста:" "Host name:")" 16; current_hostname
  pad "$(say "Система:" "System:")" 16; echo "$(uname -sr), $(uname -m)"
  pad "$(say "Працює:" "Up:")" 16; uptime_text
  pad "$(say "Час:" "Time:")" 16; date '+%Y-%m-%d %H:%M:%S %Z'
  pad "$(say "Часовий пояс:" "Time zone:")" 16; current_timezone
  if ntp=$(chronyc -n tracking 2>/dev/null); then
    if grep -q '^Leap status *: Normal' <<<"$ntp"; then
      ntp=$(say "синхронізовано (chrony)" "synchronized (chrony)")
    else
      ntp=$(say "не синхронізовано (chrony)" "not synchronized (chrony)")
    fi
  else
    ntp=$(say "не працює (dsv enable chronyd)" "not running (dsv enable chronyd)")
  fi
  pad "$(say "Синхронізація:" "Time sync:")" 16; echo "$ntp"
  pad "$(say "Мова:" "Language:")" 16; current_locale
  pad "$(say "Консоль:" "Console:")" 16
  echo "$(say розкладка keymap) $(rc_value KEYMAP || true), $(say шрифт font) $(rc_value FONT || true)"
  say "Мережа:" "Network:"
  ip -brief address 2>/dev/null | while read -r line; do
    [[ $line == lo\ * ]] || echo "  $line"
  done
}

cmd_hostname() {
  local new=${1:-} old
  if [[ -z $new ]]; then
    current_hostname
    return
  fi
  [[ $new =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$ ]] ||
    die "'$new': малі латинські літери, цифри й дефіс (не на початку й не в кінці)" \
      "'$new': lowercase Latin letters, digits and hyphens (not first or last)"
  need_root hostname "$new"
  old=$(current_hostname)
  echo "$new" >/etc/hostname
  # The installers add "127.0.1.1 <name>"; keep the line in step
  if grep -qw -- "$old" /etc/hosts; then
    sed -i "/^[^#]/s/\([[:space:]]\)${old//./\\.}\([[:space:]]\|$\)/\1$new\2/g" /etc/hosts
  fi
  grep -qw -- "$new" /etc/hosts || printf '127.0.1.1\t%s\n' "$new" >>/etc/hosts
  hostname "$new"
  say "Ім'я хоста: $new" "Host name: $new"
  session_warning
}

# All time zones, from tzdata's table plus UTC
timezones() {
  { awk '!/^#/ { print $3 }' "$zoneinfo/zone1970.tab"; echo UTC; } | sort -u
}

cmd_timezone() {
  local zone=${1:-} matches line
  if [[ -z $zone ]]; then
    current_timezone
    return
  fi
  if [[ $zone == list ]]; then
    timezones | grep -i -- "${2:-}" || true
    return
  fi
  if ! timezones | grep -qx -- "$zone"; then
    # Kyiv -> Europe/Kyiv, when only one zone matches
    matches=$(timezones | grep -i -- "$zone" || true)
    if [[ $(grep -c . <<<"$matches") -eq 1 ]]; then
      zone=$matches
    elif [[ -n $matches ]]; then
      say "Кілька часових поясів підходять, уточніть:" "Several time zones match, be more specific:" >&2
      head -n 20 <<<"$matches" | while read -r line; do echo "  $line"; done >&2
      (($(grep -c . <<<"$matches") <= 20)) || echo "  ..." >&2
      exit 1
    else
      die "невідомий часовий пояс '$zone' (dsys timezone list)" "unknown time zone '$zone' (dsys timezone list)"
    fi
  fi
  need_root timezone "$zone"
  ln -sfn "$zoneinfo/$zone" /etc/localtime
  say "Часовий пояс: $zone, зараз $(date '+%H:%M %Z')" "Time zone: $zone, now $(date '+%H:%M %Z')"
}

# Every UTF-8 locale glibc can build, with * for the enabled ones
locales() {
  sed -n 's/^\(#\?\)\([^ ]*\.UTF-8\) UTF-8[[:space:]]*$/\2 \1/p' "$libc_locales" |
    awk '{ print ($2 == "#" ? "  " : "* ") $1 }'
}

cmd_locale() {
  local lang=${1:-} re
  if [[ -z $lang ]]; then
    current_locale
    return
  fi
  if [[ $lang == list ]]; then
    locales | grep -i -- "${2:-}" || true
    return
  fi
  [[ $lang == *.* ]] || lang+=.UTF-8
  re=${lang//./\\.}
  locales | grep -q "^. $re\$" ||
    die "невідома мова '$lang' (dsys locale list)" "unknown language '$lang' (dsys locale list)"
  need_root locale "$lang"
  if grep -q "^#$re " "$libc_locales"; then
    sed -i "s/^#\($re \)/\1/" "$libc_locales"
    say "Збираю $lang..." "Building $lang..."
    xbps-reconfigure -f glibc-locales >/dev/null
  fi
  # Keep the other settings, Void's default file also has LC_COLLATE=C
  if grep -q '^LANG=' /etc/locale.conf 2>/dev/null; then
    sed -i "s/^LANG=.*/LANG=$lang/" /etc/locale.conf
  else
    echo "LANG=$lang" >>/etc/locale.conf
  fi
  say "Мова системи: $lang (діє з наступного входу)" "System language: $lang (from the next login)"
}

cmd_keymap() {
  local map=${1:-}
  if [[ -z $map ]]; then
    rc_value KEYMAP
    return
  fi
  if [[ $map == list ]]; then
    find "$keymaps" -name '*.map.gz' -printf '%f\n' | sed 's/\.map\.gz$//' | sort -u | grep -i -- "${2:-}" || true
    return
  fi
  [[ -n $(find "$keymaps" -name "$map.map.gz" -print -quit) ]] ||
    die "невідома розкладка '$map' (dsys keymap list)" "unknown keyboard map '$map' (dsys keymap list)"
  need_root keymap "$map"
  set_rc KEYMAP "$map"
  # The default console font has no Cyrillic letters
  if [[ $map == @(ua|ru|by|bg|mk|sr)* && -z $(rc_value FONT) ]]; then
    set_rc FONT cyr-sun16
  fi
  loadkeys "$map" >/dev/null 2>&1 || true
  say "Розкладка консолі: $map" "Console keyboard map: $map"
}

cmd=${1:-info}
[[ $# -gt 0 ]] && shift

case $cmd in
  info | status) cmd_info ;;
  hostname | host) cmd_hostname "$@" ;;
  timezone | tz) cmd_timezone "$@" ;;
  locale | lang) cmd_locale "$@" ;;
  keymap | keyboard) cmd_keymap "$@" ;;
  -h | --help | help) usage ;;
  *)
    usage >&2
    exit 1
    ;;
esac
