#!/usr/bin/env bash
#
# tower-config - the folder that holds what belongs to ONE tower: its config.ini,
# its scan grid and its camera profile XMLs. Run it ON the tower, as the user the
# program runs as (ggs).
#
#   scripts/tower-config dir               print the folder
#                                          (~/.config/fire_gimbal_control)
#   scripts/tower-config where             print the config.ini the program will
#                                          load; exit 1 if there is none
#   scripts/tower-config seed [--no-config]
#                                          a new tower: create the folder with a
#                                          config.ini from the example, the example
#                                          scan grid and an empty camera_profiles/rgb.
#                                          Never overwrites; --no-config leaves the
#                                          config.ini out (install-tower writes its own)
#   scripts/tower-config move [--dry-run]  one time, on a tower set up before this
#                                          folder existed: move config.ini, the scan
#                                          grid and the camera profiles out of the
#                                          project folder and into it
#
# Why a folder of its own: the project folder is what a deploy (rsync --delete)
# and an update replace. Anything the tower changes - its calibration in
# config.ini, the scan grid chosen for its view, the profiles exported from its
# camera - must live where neither can reach it. hardware.ini, which describes
# the assembly and is never changed, stays in /etc/fire_gimbal_control/.
#
# `move` copies config.ini into the folder and rewrites the paths in it that
# pointed into the project folder (grid_file, each camera's profile_dir), with
# fgc-config, so the result is checked to load before it is written. The old
# config.ini is then renamed config.ini.moved-<timestamp>, so nobody edits a copy
# the program no longer reads. The old scan grid and profiles stay where they
# were. It refuses while the program is running: the program writes config.ini
# itself, and would write to the old copy.
#
set -euo pipefail

here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$here/.." && pwd)"
DIR="${XDG_CONFIG_HOME:-$HOME/.config}/fire_gimbal_control"
OLD="$REPO/config.ini"
HW=/etc/fire_gimbal_control/hardware.ini
FGC_CONFIG_BIN="$REPO/build/fgc-config"

say() { printf '>> %s\n' "$*"; }
die() { printf 'tower-config: %s\n' "$*" >&2; exit 1; }
usage() { awk 'NR>2 && /^#/ {sub(/^# ?/, ""); print; next} NR>2 {exit}' "${BASH_SOURCE[0]}"; }

# The same order the program searches, minus --config (src/core/Paths.cpp).
cmd_where() {
    local c
    for c in "${FGC_CONFIG:-}" "$DIR/config.ini" "$OLD"; do
        [[ -n $c && -f $c ]] && { echo "$c"; return 0; }
    done
    return 1
}

cmd_seed() {
    local with_config=1
    [[ ${1:-} == --no-config ]] && with_config=0
    if [[ $with_config -eq 1 ]]; then
        local have
        # An existing tower, wherever its config.ini is, is left alone: one not
        # moved yet must not get a second scan grid that `move` would trip over.
        if have="$(cmd_where)"; then
            say "keeping the existing $have"
            return 0
        fi
        mkdir -p "$DIR"
        cp "$REPO/config/config.example.ini" "$DIR/config.ini"
        say "created $DIR/config.ini from the example - edit it for this tower"
    fi
    mkdir -p "$DIR/camera_profiles/rgb"
    if [[ -e $DIR/scan.csv ]]; then
        say "keeping the existing $DIR/scan.csv"
    else
        cp "$REPO/config/scan.csv" "$DIR/scan.csv"
        say "created $DIR/scan.csv from the example grid - replace it with this tower's"
    fi
}

# A path from config.ini that pointed into the project folder, as it reads from
# the tower folder: the same path without its leading "config/".
moved_path() { local p="$1"; echo "${p#config/}"; }
is_relative() { [[ -n $1 && $1 != /* && $1 != \~* && $1 != \$* ]]; }

cmd_move() {
    local dry=0
    [[ ${1:-} == --dry-run ]] && dry=1

    if [[ -f $DIR/config.ini ]]; then
        if [[ -f $OLD ]]; then
            die "$DIR/config.ini already exists, and so does $OLD.
The program reads $DIR/config.ini; the copy in the project folder is unused.
Compare the two, keep what is right in $DIR/config.ini, then rename the other."
        fi
        say "already moved: the program reads $DIR/config.ini"
        return 0
    fi
    [[ -f $OLD ]] || die "there is no $OLD to move (a new tower: scripts/tower-config seed)"
    [[ -x $FGC_CONFIG_BIN ]] || die "no $FGC_CONFIG_BIN - build first (./deploy.sh does)"
    if [[ -f $REPO/hardware.ini && ! -f $HW ]]; then
        die "this tower's hardware.ini is in the project folder ($REPO/hardware.ini).
Put it in /etc first, where it belongs:
    sudo install -D -m 0644 $REPO/hardware.ini $HW"
    fi
    if [[ $dry -eq 0 ]] && "$here/fgc" status >/dev/null 2>&1; then
        die "the program is running and writes config.ini itself. Stop it first:
    scripts/fgc stop"
    fi

    # What has to come along, and what config.ini will say afterwards.
    local -a sets=() copies=()
    local grid; grid="$("$FGC_CONFIG_BIN" get "$OLD" Scan.grid_file 2>/dev/null || true)"
    if is_relative "$grid"; then
        [[ -f $REPO/$grid ]] || die "[Scan] grid_file = $grid, but $REPO/$grid does not exist. Fix config.ini first."
        copies+=("$REPO/$grid|$DIR/$(moved_path "$grid")")
        [[ $(moved_path "$grid") != "$grid" ]] && sets+=("Scan.grid_file=$(moved_path "$grid")")
    fi
    local cam
    while read -r cam; do
        [[ -n $cam ]] || continue
        local pd; pd="$("$FGC_CONFIG_BIN" get "$OLD" "Camera.$cam.profile_dir" 2>/dev/null || true)"
        is_relative "$pd" || continue
        local f
        for f in "$REPO/$pd"/*.xml "$REPO/$pd"/*.txt; do
            [[ -f $f ]] && copies+=("$f|$DIR/$(moved_path "$pd")/$(basename "$f")")
        done
        [[ $(moved_path "$pd") != "$pd" ]] && sets+=("Camera.$cam.profile_dir=$(moved_path "$pd")")
    done < <(sed -nE 's/^[[:space:]]*\[Camera\.([^]]+)\][[:space:]]*$/\1/p' "$OLD")

    say "moving this tower's files to $DIR"
    echo "   $OLD -> $DIR/config.ini"
    local c
    for c in ${copies[@]+"${copies[@]}"}; do echo "   ${c%%|*} -> ${c#*|}"; done
    for c in ${sets[@]+"${sets[@]}"}; do echo "   config.ini: set ${c%%=*} = ${c#*=}"; done
    echo "   $OLD renamed to config.ini.moved-<timestamp>"
    [[ $dry -eq 1 ]] && { say "dry run: nothing changed"; return 0; }

    for c in ${copies[@]+"${copies[@]}"}; do
        local src="${c%%|*}" dst="${c#*|}"
        if [[ -e $dst ]] && ! cmp -s "$src" "$dst"; then
            die "$dst already exists and differs from $src. Nothing was moved; sort that out first."
        fi
    done
    mkdir -p "$DIR"
    for c in ${copies[@]+"${copies[@]}"}; do
        mkdir -p "$(dirname "${c#*|}")"
        cp -p "${c%%|*}" "${c#*|}"
    done
    cp -p "$OLD" "$DIR/config.ini"
    if (( ${#sets[@]} )); then
        local -a args=() hw_args=()
        for c in "${sets[@]}"; do args+=(--set "$c"); done
        [[ -f $HW ]] && hw_args=(--hardware "$HW")
        if ! "$FGC_CONFIG_BIN" set "$DIR/config.ini" --config "$DIR/config.ini" ${hw_args[@]+"${hw_args[@]}"} \
                "${args[@]}" --provenance "$(date -u +%Y-%m-%dT%H:%MZ), tower-config move"; then
            rm -f "$DIR/config.ini"
            die "the moved config.ini would not load; nothing was changed in $REPO"
        fi
    fi
    local moved; moved="config.ini.moved-$(date +%Y%m%d-%H%M%S)"
    mv "$OLD" "$REPO/$moved"
    say "done. The program now reads $DIR/config.ini"
    echo "   the old copy is $REPO/$moved; the old scan grid and profiles are left where they were"
}

sub="${1:-}"; shift || true
case "$sub" in
    dir)   echo "$DIR" ;;
    where) cmd_where ;;
    seed)  cmd_seed "$@" ;;
    move)  cmd_move "$@" ;;
    ""|-h|--help|help) usage; [[ -z $sub ]] && exit 1 || exit 0 ;;
    *) die "unknown command: $sub (try 'tower-config --help')" ;;
esac
