#!/bin/bash
set -e

# If found, and --no-beta wasn't explicitly passed, use beta mode.  Set by e.g. foxnet.
BIOS_BETA_FILE=/run/jupiter-bios-use-beta

# BIOS versions that can be updated and to what
# 'default'      - specifies what BIOS this version should be updated to by default
# 'samsung'      - specifies what BIOS samsung-memory units should update to
# 'beta'         - if set, the version to update to with --beta or BIOS_BETA_FILE set
# 'samsung_beta' - if set, the version to update to with --beta or BIOS_BETA_FILE set for samsung units
# 'args'         - additional args this update should pass to the updater (not used currently)
# 'auto'         - whether or not to update with --auto
#
# - BIOS 19+ are EV2 bios, and can carry forward to the newest.
# - EV1 units no longer supported, BIOS before F7A0006 requires special steps
BIOS_VERSION_MAP='{
                    "F7A0019": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0020": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0021": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0022": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0023": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0024": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0025": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0026": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0027": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0028": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0029": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0030": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0031": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0032": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0100": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" },
                    "F7A0101": { "default": "F7A0102", "samsung": "F7A0102", "beta": null, "samsung_beta": null, "args": "", "auto": "false" }
                  }'

# Bios to attempt to install
BIOS_FLASHER="/usr/share/jupiter_bios_updater/h2offt"
# Where the bioses (bi-ii?) live
BIOS_DIR="/usr/share/jupiter_bios"
# What to append to the bios name to get the file name
BIOS_SUFFIX="_sign.fd"
# Inhibit file. Do nothing if this exists.
BIOS_INHIBIT_FILE="/foxnet/bios/INHIBIT"

# Util
info() { echo >&2 "$*"; }
err()  { info "!! $*"; }
die() {
  local errstr
  if [[ $# -gt 0 ]]; then
    err "$*"
    errstr="Error: \"$*\""
  fi
  finish 1 false "${errstr:-unknown failure}"
}

finish() {
  local exitcode="$1"
  local changed="$2"
  local comment="${*:3}"
  [[ -z $comment ]] || info "$comment"
  if [[ -n $SALT_STATE ]]; then
    echo
    echo "changed=$changed comment='${comment//\'/}'"
  fi
  exit "$exitcode"
}

##
## Args
##

checkmode=
beta=
automode=
# If --no-beta was passed, we don't want to auto-detect beta from /run/jupiter-bios-use-beta
explicit_no_beta=

usage() {
  echo >&2 "!! Usage: $0 [--beta] [check] [--auto]";
  exit 1
}

while [[ ${#@} -gt 0 ]]; do
  arg="$1"
  if [[ $arg = "check" && -z $checkmode ]]; then
    checkmode=1
  elif [[ $arg = "--no-beta" ]]; then
    beta=
    explicit_no_beta=1
  elif [[ $arg = "--beta" ]]; then
    beta=1
    explicit_no_beta=
  elif [[ $arg = "--auto" ]]; then
    automode=1
  else
    usage
  fi
  shift
done

# Check mode and for BIOS_BETA_FILE
actionstring="BIOS updates"
if [[ -z $explicit_no_beta && -z $beta && -e $BIOS_BETA_FILE ]]; then
  beta=1
  actionstring="BETA BIOS updates due to $BIOS_BETA_FILE"
elif [[ -n $beta ]]; then
  actionstring="BETA BIOS updates"
fi

##
## Main
##

# Die early if inhibited
[[ ! -f $BIOS_INHIBIT_FILE ]] || die "Bios updates inhibited, no action ($BIOS_INHIBIT_FILE)"

# Print action
modestring="Performing"
[[ -z $checkmode ]] || modestring="Checking for"
info "$modestring $actionstring"

# Check for samsung memory
memory_sns=($(dmidecode | awk '/^[^\t]/ {i=0}; /^Memory Device/ {i=1}; /\tPart Number:/ && i { print $3 }' | uniq))
[[ ${#memory_sns[@]} -eq 1 ]] || die "Couldn't identify this unit from DMI, unexpected memory serials: ${memory_sns[*]}"

# Samsung memory modules have S/Ns beginning with K3L
samsung=""
[[ ${memory_sns[0]:0:3} != K3L ]] || samsung=1

# Read & check curent version
currentversion="$(cat /sys/devices/virtual/dmi/id/bios_version)"
echo >&2 "BIOS version: $currentversion"
[[ -n $currentversion ]] || die "Failed to parse bios version from dmidecode"

# Helper to query the bios version map for currentversion+field (with needlessly wordy variable escaping since current
# version is an arbitrary string that has contained bad characters historically)
configfield() {
  local currentversion=$1
  local field=$2
  jq -r --arg currentversion "$currentversion" --arg field "$field" \
     '.[$currentversion] | .[$field] | select(type == "string")' <<< "$BIOS_VERSION_MAP"
}

# Check config table for desired version
if [[ -n $samsung ]]; then
  desiredversion=$(configfield "$currentversion" "samsung")
  desiredversion_beta=$(configfield "$currentversion" "samsung_beta")
else
  desiredversion=$(configfield "$currentversion" "default")
  desiredversion_beta=$(configfield "$currentversion" "beta")
fi

# If beta is set and the beta value for this bios is not null, use it.
[[ -z $beta || -z $desiredversion_beta ]] || desiredversion=$desiredversion_beta

# Args aren't differentiated by samsung/beta/etc currently
desiredargs=$(configfield "$currentversion" "args")

echo >&2 "Desired version: ${desiredversion:-<none>}"

# No updates configured for the current version
if [[ -z $desiredversion ]]; then
  finish 0 no 'No updates configured for this bios'
fi

# Skip if --auto was specified but "auto": "false" for the current version
if [[ -n $automode && $(configfield "$currentversion" "auto") != "true" ]]; then
  finish 0 no "Auto-updates for $currentversion are disabled"
fi

# Make sure we have the files to install this version
biosfile="$BIOS_DIR/$desiredversion$BIOS_SUFFIX"
[[ -x $BIOS_FLASHER ]] || die "Could not find or execute flasher at \"$BIOS_FLASHER\""
[[ -f "$biosfile" ]] || die "Could not find expected bios at: $biosfile"

#
# Update needed
#
[[ -z $checkmode ]] || finish 7 yes "Updates available, not applying in check mode"

# Abort if --auto and battery < 20%
# (for non-auto updates we leave this up to the UI)
if [[ -n $automode && $(( "$(cat /sys/class/power_supply/BAT1/capacity)" )) -lt 20 ]]; then
  finish 2 no "Cannot attempt bios update on <= 20% battery"
fi

# Don't retry auto-updates more than once per 24 hours
if [[ -n $automode ]]; then
  last_auto_attempt_path="/var/lib/jupiter-biosupdate/last_auto_attempt_$desiredversion"
  last_auto_attempt=$(cat "$last_auto_attempt_path" 2> /dev/null || echo "")
  now=$(date +%s)

  if [[ "$now" -lt $(( "$last_auto_attempt" + 60 * 60 * 24 )) ]]; then
    finish 0 no "Auto-update to $desiredversion was attempted within the last 24 hours, skipping for now"
  fi

  mkdir -p "$(dirname "$last_auto_attempt_path")"
  echo "$now" > "$last_auto_attempt_path"
fi

# Try to flash
#   Flasher is cwd-sensitive
cd "$(dirname "$BIOS_FLASHER")"
# shellcheck disable=SC2206 # desiredargs expanded on purpose
cmd=("$BIOS_FLASHER" "$biosfile" -all -AC $desiredargs)
info "Running: " "${cmd[@]@Q}"
if "${cmd[@]}"; then
  finish 0 yes "Applied bios update to version $desiredversion"
fi

die "Attempted to flash bios but failed"
