#!/usr/bin/env bash
# Copyright (c) 2021 The Toltec Contributors
# SPDX-License-Identifier: MIT

set -euo pipefail

# Path where Toltec resides (will be mounted to $toltec_dest)
toltec_src=/home/root/.entware

# Path where Toltec is mounted
toltec_dest=/opt

# Path where toltec caches compatibility information
toltec_share=/home/root/.local/share/toltec
# Path to static opkg build
opkg_path=/home/root/.local/bin/opkg
# Path to opkg install status file
opkg_status="/opt/lib/opkg/status"

# Path to Opkg configuration
opkg_conf="$toltec_src"/etc/opkg.conf
opkg_conf_dir="$toltec_src"/etc/opkg.conf.d

# Path to the Xochitl configuration
xochitl_conf=/home/root/.config/remarkable/xochitl.conf
ssh_key_file=/home/root/.ssh/authorized_keys

# Root of the remote Toltec server
toltec_srv_root=https://toltec-dev.org

# Start and end markers of Toltec-managed section in bashrc
bashrc_path=/home/root/.bashrc
bashrc_start_marker="# Added by Toltec bootstrap (do not modify!)"
bashrc_old_start_marker="# Path added by Toltec bootstrap"
bashrc_end_marker="# End of Toltec bootstrap additions"

# Print a log message
#
# Arguments:
#
# [$1] - Log level: INFO, WARN or ERROR (default: INFO)
# $2... - Messages to print, each argument goes to a separate line
log() {
    # Output stream where the messages will be sent
    local log_type="INFO"
    local fd=1
    local colored_prefix

    if [[ $# -ge 2 ]]; then
        case "$1" in
            INFO | WARN | ERROR)
                log_type="$1"
                shift
                ;;
        esac
    fi

    case "$log_type" in
        INFO) colored_prefix='\e[32mINFO:\e[0m  ' ;;
        WARN)
            colored_prefix='\e[33mWARN:\e[0m  '
            fd=2
            ;;
        ERROR)
            colored_prefix='\e[31mERROR:\e[0m '
            fd=2
            ;;
    esac

    echo -e "${colored_prefix}$1" >&$fd

    # Extra lines to print indented
    shift
    local line

    for line in "$@"; do
        echo -e "       $line" >&$fd
    done
}

# Identify which reMarkable model we’re currently running on
#
# Output: One of the following strings
#
# rm1 - for reMarkable 1
# rm2 - for reMarkable 2
# unknown
identify-model() {
    local device_id
    device_id="$(cat /sys/devices/soc0/machine)"

    case $device_id in
        "reMarkable 1.0" | "reMarkable Prototype 1")
            echo "rm1"
            ;;
        "reMarkable 2.0")
            echo "rm2"
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

# Find the current OS release version
#
# Output: Current version in the X.Y.Z.P format
get-release-version() {
    awk -F= '/RELEASE_VERSION/{print $2}' /usr/share/remarkable/update.conf
}

# Check to see if the version is supported and error if it isn't supported
#
# Arguments:
#
# $1 - Branch for which to check support
#
# Exit code:
#
# 0 - if the current version is supported
# 1 - if unsupported
# 2 - if version compatibility information could not be retrieved
# 3 - unable to install standalone wget
check-version() {
    local wget
    if [[ "$(install-state)" != "yes" ]] || [[ "$(command -v wget)" != "/opt/bin/wget" ]]; then
        if ! install-standalone-wget; then
            return 2
        fi
        wget="${toltec_share}/wget"
    else
        wget="wget"
    fi
    local check_branch
    local current_model
    local current_version
    local cwd
    check_branch="$1"
    current_model="$(identify-model)"
    current_version="$(get-release-version)"

    cwd="$(pwd)"
    mkdir -p "$toltec_share"
    cd "$toltec_share"
    if ! "$wget" "$toltec_srv_root"/"$check_branch"/Compatibility \
        --timestamping \
        --no-verbose \
        --ignore-length 2> /dev/null; then
        log ERROR "Unable to fetch updated version compatibility information, make sure you have a stable Wi-Fi connection"
        if ! [ -f Compatibility ]; then
            cd "$cwd"
            return 2
        fi
        log WARN "Using outdated cached version compatibility information"
    fi
    cd "$cwd"

    if ! grep -q "${current_model}=${current_version}" "${toltec_share}/Compatibility"; then
        LOG ERROR "You’re running an unsupported OS version: $current_version"
        LOG ERROR "Please monitor Toltec releases for upcoming support"
        return 1
    fi

    return 0
}

# Get the password used to login to the SSH prompt
#
# Output: SSH password
get-ssh-password() {
    awk -F= '/DeveloperPassword=/{ printf "%s" $2 }' "$xochitl_conf"
}

# Get the path for a systemd bind mount unit
#
# Arguments:
#
# $1 - Mount point
#
# Output:
#
# Path at which the definition of a bind mount on $1 should reside
get-bind-mount-path() {
    echo "/lib/systemd/system/$(systemd-escape --path "$1").mount"
}

# Create or update a bind mount systemd unit and enable it
#
# Arguments:
#
# $1 - Source directory
# $2 - Mount point
add-bind-mount() {
    local unit_path
    local unit_name
    unit_path="$(get-bind-mount-path "$2")"
    unit_name="$(basename "$unit_path")"

    if [[ -e $unit_path ]]; then
        echo "Bind mount configuration for '$2' already exists, updating"
    else
        echo "Mounting '$1' over '$2'"
    fi

    cat > "$unit_path" << UNIT
[Unit]
Description=Bind mount $1 over $2
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target
[Mount]
What=$1
Where=$2
Type=none
Options=bind
[Install]
WantedBy=local-fs.target
UNIT

    systemctl daemon-reload
    systemctl enable "$unit_name"
    systemctl start "$unit_name"
}

# Disable and remove a bind mount systemd unit
#
# Arguments:
#
# $1 - Mount point
remove-bind-mount() {
    local unit_path
    local unit_name
    unit_path="$(get-bind-mount-path "$1")"
    unit_name="$(basename "$unit_path")"

    if [[ ! -e $unit_path ]]; then
        echo "No existing bind mount for '$1'"
        return
    fi

    echo "Removing mount over '$1'"
    systemctl disable "$unit_name"
    systemctl stop "$unit_name"
    if mountpoint -q "$1"; then
        umount -l "$1"
    fi
    rm "$unit_path"
    systemctl daemon-reload
}

# Escape a string for use in a sed search
#
# Arguments
#
# $1 - String to escape
#
# Output:
#
# Escaped string
escape-for-sed() {
    # shellcheck disable=SC1003
    sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<< "$1" | tr -d '\n'
}

# Escape a string for use in a sed replacement
#
# Arguments
#
# $1 - String to escape
#
# Output:
#
# Escaped string
escape-for-sed-replace() {
    IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<< "$1")
    printf %s "${REPLY%$'\n'}"
}

# Replace multiline string content in stdin
#
# Arguments:
#
# $1 - String to replace
# $2 - string to replace with
# stdin - Text to perform replacement on
#
# Output:
#
# Contents of stdin with the string replaced
replace-string() {
    sed \
        -e ':a' \
        -e '$!{N;ba' -e '}' \
        -e "s/$(escape-for-sed "$1")/$(escape-for-sed-replace "$2")/"
}

# Get the opkg status for a package
#
# Arguments:
#
# $1 - Package name
#
# Output: Opkg status information for the package
#
# Exit Code:
#
# 0 - Status found
# 1 - Status not found
get-package-status() {
    local status
    status="$(opkg status "$1" | grep -v "has no valid architecture, ignoring" || true)"
    if [ -n "$status" ]; then
        echo "$status"
        return 0
    fi
    local index
    local index2
    local count
    # Don't use --line-number so this works on the built in grep
    index="$(grep -n "Package: ${1}" "$opkg_status" | cut -d':' -f1 || echo "-1")"
    if [ "$index" -eq -1 ]; then
        return 1
    fi
    # Don't use --line-number so this works on the built in grep
    count="$(tail -n +$((index + 1)) "$opkg_status" | grep -n "Package: " | head -n1 | cut -d':' -f1 || echo "-1")"
    if [ "$count" -eq -1 ]; then
        return 1
    fi
    index2=$((index + count - 1))
    status="$(sed -n "${index},${index2}p;$((index2 + 1))q" "$opkg_status")"
    if [ -z "$status" ]; then
        return 1
    fi
    echo "$status"
    return 0
}

# Check if a string appears in a set of strings
#
# Arguments:
#
# $1 - String to look for
# $2... - Elements to compare to $1
#
# Example:
#
# > has-element "needle" "${haystack[@]}"
# Will return 0 if "needle" is found in the "haystack" array.
#
# Exit code: 0 if the element is found, 1 otherwise.
has-element() {
    local needle="$1"
    shift

    for element; do
        if [[ $needle == "$element" ]]; then
            return 0
        fi
    done

    return 1
}

# Update the opkg status for a package with the correct architecture for the device
#
# Arguments:
#
# $1 - Package name
#
# Exit Codes:
#
# 0 - Package status was updated
# 1 - Package status was not modified
# 2 - Package status was not found
# 3 - Package status has no architecture
update-package-arch() {
    if ! grep -q "Package: ${1}" "$opkg_status"; then
        return 1
    fi
    local arch
    local model
    local new_status
    local version
    local status
    local rc=0
    status="$(get-package-status "$1" || true)"
    if [ -z "$status" ]; then
        log WARN "Unable to get status for package: ${1}"
        return 2
    fi
    arch="$(echo "$status" | grep "Architecture:" | awk '{print $2}')"
    if [ -z "$arch" ]; then
        return 3
    fi
    case "$arch" in
        rmallos2 | rmallos3)
            version="$(identify-support-version)"
            new_status="$(echo "$status" | replace-string "Architecture: ${arch}" "Architecture: rmall${version}")"
            ;;
        rm1os2 | rm1os3 | rm2os2 | rm2os3)
            model="$(identify-model)"
            version="$(identify-support-version)"
            new_status="$(echo "$status" | replace-string "Architecture: ${arch}" "Architecture: ${model}${version}")"
            ;;
        *)
            new_status="$status"
            ;;
    esac
    if [[ "$status" == "$new_status" ]]; then
        return 1
    fi
    echo "Updating package architecture for package: ${1}"
    sed -i \
        -e ':a' \
        -e '$!{N;ba' -e '}' \
        -e "s/$(escape-for-sed "$status")/$(escape-for-sed-replace "$new_status")/" \
        "$opkg_status"
    return 0
}

# Reinstall toltec-base and toltec-deletions
reinstall-base() {
    local packages=()
    while read -r pkg; do
        if update-package-arch "$pkg"; then
            packages+=("$pkg")
        fi
    done < <(opkg list-installed | grep -v "has no valid architecture, ignoring" | cut -d' ' -f1 || true)
    while read -r pkg; do
        if update-package-arch "$pkg"; then
            packages+=("$pkg")
        fi
    done < <(opkg list-installed | grep "has no valid architecture, ignoring" | cut -d' ' -f2 || true)
    if ! has-element "toltec-base" "${packages[@]}"; then
        packages+=(toltec-base)
    fi
    if ! has-element "toltec-deletions" "${packages[@]}"; then
        packages+=(toltec-deletions)
    fi
    opkg install --force-reinstall "${packages[@]}" || true
}

# Reinstall all Toltec packages that had files installed outside of
# $toltec_src, e.g. systemd configuration files
reinstall-root() {
    # Get the list of installed packages with files on root
    local pkgname
    declare -A on_root_packages
    while read -r inst_line; do
        pkgname="$(echo "$inst_line" | awk '{ print $1 }')"
        if opkg files "$pkgname" | grep -v -e "/home/root" -e "$toltec_dest" \
            -e "is installed on root" -q; then
            on_root_packages[$pkgname]=1
        fi
    done < <(opkg list-installed)

    # Filter the list to keep only packages that can be installed
    declare -A reinstall_packages
    while read -r pkgname; do
        if [[ -v "on_root_packages[$pkgname]" ]]; then
            reinstall_packages[$pkgname]=1
        fi
    done < <(gunzip -c /opt/var/opkg-lists/* | grep "^Package:" | awk '{print $2}')

    # Workaround: Checking the size of an empty array when the nounset option
    # is active may throw an error on some Bash versions, so we disable it
    # temporarily
    set +u
    if [[ ${#reinstall_packages[@]} -ne 0 ]]; then
        opkg install --force-reinstall --force-remove "${!reinstall_packages[@]}" || true
    else
        echo "No package needs to be reinstalled"
    fi
    set -u
}

# Remove all PATH definitions for /opt in bashrc
clean-path() {
    if [[ -f $bashrc_path ]]; then
        sed -i "/^$bashrc_start_marker\$/,/^$bashrc_end_marker\$/d" "$bashrc_path"
        sed -i "/^$bashrc_old_start_marker\$/!b;n;d" "$bashrc_path"
        sed -i "/^$bashrc_old_start_marker\$/d" "$bashrc_path"
        sed -i '/^\(export \)\?PATH="\?\.*\/opt\/bin:\/opt\/sbin.*"\?$/d' "$bashrc_path"
    fi
}

# Add/update PATH declarations in bashrc to include Toltec binaries
set-path() {
    local old_path
    old_path="$(bash -l -c "echo \$PATH")"

    clean-path
    cat >> "$bashrc_path" << SHELL
$bashrc_start_marker
PATH="/opt/bin:/opt/sbin:/home/root/.local/bin:\$PATH"
$bashrc_end_marker
SHELL

    # Warn user if PATH contents have changed
    local new_path
    new_path="$(bash -l -c "echo \$PATH")"

    if [[ $old_path != "$new_path" ]]; then
        log WARN "Please restart your SSH session or run 'exec bash --login' to use Toltec"
    fi
}

# Print a file with its comments and blank lines removed
#
# Arguments:
#
# $1 - Path to the file
#
# Output: Cleaned file contents
clean-file-comments() {
    sed -e '/^#/d' -e '/^$/d' "$1"
}

# Rebuild the Opkg configuration file from split config files
#
# Exit code:
#
# 0 - If the configuration file has changed (in which case
#     `opkg update` needs to be run)
# 1 - If there were no changes
generate-opkg-conf() {
    local old_opkg_conf
    old_opkg_conf="$(mktemp)"

    if [[ -f $opkg_conf ]]; then
        mv "$opkg_conf" "$old_opkg_conf"
    else
        touch "$old_opkg_conf"
    fi

    cat > "$opkg_conf" << CONF
# Opkg configuration
# Generated by toltecctl (do not modify!)

# Define custom configuration in files under the '$opkg_conf_dir' directory
# then run \`toltecctl generate-opkg-conf\` to regenerate this file

dest root /
dest ram /opt/tmp
lists_dir ext /opt/var/opkg-lists
option tmp_dir /opt/tmp

CONF

    for conf in "$opkg_conf_dir"/*; do
        echo "# $conf"
        clean-file-comments "$conf"
        echo
    done >> "$opkg_conf"

    ! diff <(clean-file-comments "$old_opkg_conf" | sort) \
        <(clean-file-comments "$opkg_conf" | sort) > /dev/null
}

# Create the Opkg configuration file for fetching Entware packages
create-entware-conf() {
    mkdir -p "$opkg_conf_dir"
    cat > "$opkg_conf_dir"/10-entware.conf << CONF
# Entware repository configuration
# Managed by Toltec (do not modify!)

# Define custom configuration in other files in this directory
# then run \`toltecctl generate-opkg-conf\` to regenerate '$opkg_conf'

arch all 100
arch armv7-3.2 160
src/gz entware https://bin.entware.net/armv7sf-k3.2
CONF
}

# Get the current Toltec branch of this installation
#
# Output: Current branch name
get-branch() {
    if ! [ -f "$opkg_conf_dir/15-toltec.conf" ]; then
        return
    fi
    local srv_prefix_len=${#toltec_srv_root}
    local srv_prefix="${toltec_srv_root//\//\\/}"
    local awk_get_branch="
        match(\$0, /$srv_prefix\/(.+)\/rmall/) {
            print substr( \
                \$0, \
                RSTART+$srv_prefix_len+1, \
                RLENGTH-$srv_prefix_len-1-6 \
            )
        }
    "
    awk "$awk_get_branch" "$opkg_conf_dir/15-toltec.conf" | head -n1 || true
}

# Compare two version numbers
#
# Arguments:
#
# $1 - Version number to compare
# $2 - Version number to compare
#
# Output: True if the first number is larger than the second
compare-versions() {
    [ "$(echo -e "${1}\n${2}" | sort | head -n1)" != "$1" ]
}

# Identify which toltec version this should be limited to
#
# Output: One of the following strings
#
# os2 - 2.x OS version
# os3 - 3.x OS version
# unknown
identify-support-version() {
    local current_version
    current_version="$(get-release-version)"

    if compare-versions "2.0" "$current_version"; then
        echo "unknown"
    elif compare-versions "3.0" "$current_version"; then
        echo "os2"
    elif compare-versions "4.0" "$current_version"; then
        echo "os3"
    else
        echo "unknown"
    fi
}

# Set the Toltec branch for this installation
# (generate-opkg-conf must be run afterwards to rebuild the main config file)
#
# Arguments:
#
# $1 - Name of the branch to use (either stable or testing)
switch-branch() {
    local branch="${1:-stable}"
    local model
    local version
    model="$(identify-model)"
    version="$(identify-support-version)"

    if [[ $model = "unknown" ]]; then
        log ERROR "You’re running an unsupported or unrecognised device"
        exit 1
    fi
    if [[ $version = "unknown" ]]; then
        log ERROR "You're running an unsupported or unrecognised OS version"
        exit 1
    fi

    mkdir -p "$opkg_conf_dir"
    cat >| "$opkg_conf_dir"/15-toltec.conf << CONF
# Toltec repository configuration
# Generated by toltecctl (do not modify!)

# Use \`toltecctl switch-branch [stable|testing]\` to
# switch to a different Toltec branch

arch rmall 200
src/gz toltec-rmall ${toltec_srv_root}/${branch}/rmall
arch ${model} 250
src/gz toltec-${model} ${toltec_srv_root}/${branch}/${model}
arch rmall${version} 260
src/gz toltec-rmall$version ${toltec_srv_root}/${branch}/rmall${version}
arch ${model}os2 270
arch ${model}os3 270
src/gz toltec-${model}${version} ${toltec_srv_root}/${branch}/${model}${version}
CONF
}

# Re-enable Toltec install after system update
reenable() {
    log INFO "Mounting /opt"
    add-bind-mount "$toltec_src" "$toltec_dest"
    switch-branch "$(get-branch)"
    log INFO "Generating /opt/etc/opkg.conf"
    generate-opkg-conf || true
    log INFO "Opkg update"
    opkg update
    log INFO "Reinsalling base packages"
    reinstall-base
    log INFO "Reinstalling packages with files on the root partition"
    reinstall-root
    if [ -d /opt/share/toltec/reenable.d ]; then
        find /opt/share/toltec/reenable.d -maxdepth 1 -mindepth 1 -print0 \
            | xargs -0rn1 basename \
            | while read -r pkg; do
                local script
                script="/opt/lib/opkg/info/${pkg}.postinst"
                if [ -f "$script" ]; then
                    log INFO "Reconfiguring ${pkg}"
                    "$script" configure || true
                fi
            done
    fi
    log INFO "Configuring bashrc"
    set-path
}

# List all installed packages such that any package comes before
# its dependencies. Dependency cycles are broken arbitrarily
list-installed-ordered() {
    # shellcheck disable=SC2016
    local awk_list_to_graph='
        /^.* depends on:$/{
            from=$1;
            print from " " from;
        }

        /^\t/{
            print from " " $1;
        }
    '
    opkg depends '*' | awk "$awk_list_to_graph" | tsort 2> /dev/null || true
    # tsort reports errors if there are dependency cycles, we ignore them
}

# Install standalone opkg binary
install-standalone-opkg() {
    local wget
    if [[ "$(install-state)" != "yes" ]] || [[ "$(command -v wget)" != "/opt/bin/wget" ]]; then
        if ! install-standalone-wget; then
            return 2
        fi
        wget="${toltec_share}/wget"
    else
        wget="wget"
    fi
    local opkg_remote=https://bin.entware.net/armv7sf-k3.2/installer/opkg

    if ! "$wget" --no-verbose "$opkg_remote" --output-document "$opkg_path"; then
        log ERROR "Unable to fetch standalone opkg, make sure you have a stable Wi-Fi connection"
        return 1
    fi

    chmod u+x "$opkg_path"
    if [[ $(command -v opkg) != "$opkg_path" ]]; then
        export PATH
        PATH="$(dirname "$opkg_path"):$PATH)"
    fi
}

# Install a local wget binary which supports TLS (the original one
# installed on the reMarkable does not) in the PATH
install-standalone-wget() {
    local wget_path="${toltec_share}/wget"
    local wget_remote=http://toltec-dev.org/thirdparty/bin/wget-v1.21.1
    local wget_checksum=8798fcdabbe560722a02f95b30385926e4452e2c98c15c2c217583eaa0db30fc

    if [ -f "$wget_path" ] && ! [[ -e $wget_path ]] || ! sha256sum -c <(echo "$wget_checksum  $wget_path") > /dev/null 2>&1; then
        rm "$wget_path"
    fi

    if ! [ -f "$wget_path" ]; then
        log INFO "Fetching secure wget"

        # Download and compare to hash
        mkdir -p "$(dirname "$wget_path")"

        if ! wget -q "$wget_remote" --output-document "$wget_path"; then
            log ERROR "Could not fetch wget, make sure you have a stable Wi-Fi connection"
            exit 1
        fi

        if ! sha256sum -c <(echo "$wget_checksum  $wget_path") > /dev/null 2>&1; then
            log ERROR "Invalid checksum for the local wget binary"
            exit 1
        fi

        chmod 755 "$wget_path"
    fi
}

# Remove Toltec completely
uninstall() {
    # Fetch standalone opkg used to uninstall packages
    if ! install-standalone-opkg; then
        return 1
    fi

    # Remove installed packages in reverse dependency order
    list-installed-ordered | while read -r pkgname; do
        "$opkg_path" remove --force-removal-of-essential-packages --force-depends --force-remove "$pkgname"
    done

    systemctl daemon-reload
    rm -f "$opkg_path"

    # Remove mount point
    remove-bind-mount "$toltec_dest"
    rmdir "$toltec_dest"

    # Unset PATH
    clean-path

    # Remove Toltec data
    rm -r "$toltec_src"

    # Re-enable xochitl if needed
    systemctl enable xochitl
}

# The current toltec install state
#
# Output:
#
# yes - opt.mount is enabled and active
# partial - opt.mount is active but not enabled
# no - opt.mount is not enabled, or does not exist
install-state() {
    local unit_name
    local enabled
    unit_name="$(systemd-escape --path "$toltec_dest").mount"
    if ! systemctl --quiet is-enabled "$unit_name" 2> /dev/null; then
        echo "no"
    elif systemctl --quiet is-active "$unit_name"2 > /dev/null; then
        echo "partial"
    else
        echo "yes"
    fi
}

# Output toltec installation status
status() {
    echo -ne "Enabled: \033[1m"
    local enabled=false
    case "$(install-state)" in
        yes)
            enabled=true
            echo -ne "\e[32mYes"
            ;;
        no)
            echo -ne "\e[31mNo"
            ;;
        partial)
            echo -ne "\e[33mPartial"
            ;;
        *)
            echo -ne "\e[31mUnknown"
            ;;
    esac
    echo -e "\e[0m"
    echo -ne "Supported: \033[1m"
    local rc
    rc=0
    check-version "$(get-branch)" &> /dev/null || rc=$?
    if [ $rc -eq 0 ]; then
        echo -ne "\e[32mYes"
    elif [ $rc -eq 2 ] || [ $rc -eq 3 ]; then
        echo -ne "\e[33mUnknown"
    else
        echo -ne "\e[31mNo"
    fi
    echo -e "\e[0m"
    echo -n "Branch: "
    if [ -f "$opkg_conf" ]; then
        # shellcheck disable=SC2005
        echo "$(get-branch)"
    else
        echo -e "\033[1m\e[33mUnknown\e[0m"
    fi
    echo -n "Model: "
    identify-model
    echo -n "OS: "
    get-release-version
}

help() {
    echo "Usage: $(basename "$0") COMMAND
Manage your Toltec install. Available commands:

    help                    Show this help message.
    generate-opkg-conf      Rebuild the Opkg configuration file.
    switch-branch [BRANCH]  Change the current branch to BRANCH.
    reenable                Re-enable Toltec after a system update.
    uninstall               Permanently remove Toltec."
}

if [[ $0 = "${BASH_SOURCE[0]}" ]]; then
    if [[ $# -eq 0 ]]; then
        help
        exit 1
    fi

    action="$1"
    shift

    case $action in
        generate-opkg-conf)
            no_update=$({ (($# > 0)) && [[ $1 = "--no-update" || $1 = "-n" ]] && echo 1; } || true)

            if generate-opkg-conf && [[ -z $no_update ]]; then
                read -p "Your Opkg configuration changed. Reload it? [Y/n] " -n 1 -r
                echo

                if [[ $REPLY =~ ^[Nn]$ ]]; then
                    log WARN "Not reloading. Use 'opkg update' to do it manually."
                else
                    opkg update
                fi
            fi
            ;;

        switch-branch)
            target_branch=stable
            force=

            while (($#)); do
                if [[ $1 = "--force" || $1 = "-f" ]]; then
                    force=1
                else
                    target_branch="$1"
                fi
                shift
            done

            if [[ -z $force ]] && ! wget --quiet --spider "$toltec_srv_root/$target_branch"; then
                cat << MSG
The '$target_branch' branch does not seem to exist. This may happen if you made
a typo in the branch name or if you have no Internet access.
MSG
                read -p "Continue anyway? [y/N] " -n 1 -r
                echo

                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    log WARN "Canceled"
                    exit 1
                fi
            fi

            if ! check-version "$target_branch"; then
                if [[ -z $force ]]; then
                    exit 1
                fi
            fi

            if [[ -z $force ]] && [[ $target_branch = "testing" ]]; then
                cat << MSG
Using packages from the testing branch may cause breakage or data loss.
Please make sure you read the following before switching to testing:

    <https://github.com/toltec-dev/toltec/blob/stable/docs/branches.md>

MSG
                read -p "Continue? [y/N] " -n 1 -r
                echo

                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    log WARN "Canceled"
                    exit 1
                fi
            fi

            switch-branch "$target_branch"

            if generate-opkg-conf; then
                if ((force == 0)); then
                    read -p "Your Opkg configuration changed. Reload it? [Y/n] " -n 1 -r
                    echo

                    if [[ $REPLY =~ ^[Nn]$ ]]; then
                        log WARN "Not reloading. Use 'opkg update' to do it manually."
                        exit
                    fi
                fi

                opkg update
            fi
            ;;

        reenable)
            force=$({ (($# > 0)) && [[ $1 = "--force" || $1 = "-f" ]] && echo 1; } || true)

            if ! check-version "$(get-branch)"; then
                if [[ -z $force ]]; then
                    exit 1
                fi
            fi

            reenable
            ;;

        uninstall)
            force=$({ (($# > 0)) && [[ $1 = "--force" || $1 = "-f" ]] && echo 1; } || true)

            if [[ -z $force ]]; then
                read -p "This will wipe out all files in '$toltec_dest'. Continue? [y/N] " -n 1 -r
                echo

                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    log WARN "Canceled"
                    exit 1
                fi
            fi

            uninstall

            if [[ -z $force ]]; then
                read -p "To complete the uninstall process, the device needs to be rebooted. Reboot now? [Y/n] " -n 1 -r
                echo

                if [[ $REPLY =~ ^[Nn]$ ]]; then
                    log WARN "Please reboot your device manually to complete the uninstall process"
                    exit
                fi
            fi

            if [[ ! -f $ssh_key_file ]]; then
                log WARN "In case something goes wrong, your SSH password is: $(get-ssh-password)"
            fi

            log INFO "Rebooting"
            systemctl reboot
            ;;

        status)
            status
            ;;

        help | -h | --help)
            help
            ;;

        *)
            log ERROR "Invalid command '$action'\n"
            help
            exit 1
            ;;
    esac
fi
