#!/bin/sh

set -eu

DOTS_REPO="https://git.snaile.de/luca/dotfiles"
DOTS_BRANCH="main"
STOW_DIR=".local/share/stow"
DOTS_PACKAGE="dots"
CONFIG_FILE="./config.yml"
LOG_FILE="bootstrap.log"

USER_GROUPS="wheel,floppy,audio,video,cdrom,optical,kvm,xbuilder,users" # Comma separated list
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

error() {
    printf "%b\n" "${RED}${BOLD}${1}${RESET}" >&2
    echo "$1" >>"$LOG_FILE"
    exit 1
}

prompt() {
    message=$1
    printf "%b" "${BLUE}${message}${RESET}" >"$(tty)"
    # shellcheck disable=SC3037,SC2046
    read -r x
    echo "$x"
    unset x
}

emphasize() {
    message=$1
    printf "%-$(tput cols)b\n" "${GREEN}${BOLD}${message}${RESET}"
    echo "$1" >>"$LOG_FILE"
}

info() {
    message=$1
    printf "%-$(tput cols)b\n" "${message}"
    echo "$1" >>"$LOG_FILE"
}

warn() {
    message=$1
    printf "%-$(tput cols)b\n" "${YELLOW}${BOLD}${message}${RESET}"
    echo "$1" >>"$LOG_FILE"
}

print_buffer() {
    buffer_size=$1
    trim=${2:-y} # 'y' | 'n'
    line_length=$(tput cols)

    for i in $(seq 1 "$((buffer_size))"); do
        if [ "$trim" = "y" ]; then
            template="%-${line_length}s\n"
        else
            template="%-${line_length}.${line_length}s\n"
        fi
        eval '[ -z "${buffer'"$i"':-}" ] || {
            printf "'"$template"'" "${buffer'"$i"'}"
        }'
    done
}

push_buffer() {
    line=$1
    buffer_size=$2
    for i in $(seq 1 $((buffer_size - 1))); do
        eval 'buffer'"$i"'="${buffer'$((i + 1))':-}"'
    done

    eval 'buffer'"$buffer_size"'=''''"$line"'''''
}

scroll() {
    buffer_size=$1 # int
    trim=${2:-y}   # 'y' | 'n'

    tput civis
    while read -r line; do
        push_buffer "$line" "$buffer_size"
        echo "$line" >>"$LOG_FILE"

        tput sc
        print_buffer "$buffer_size" "$trim"
        tput rc
    done
    tput cnorm

    tput sc
    for i in $(seq 1 $((buffer_size))); do
        printf "%-$(tput cols)s" ""
        eval "unset buffer$i"
    done
    tput rc
    print_buffer "$buffer_size" "$trim"
}

check_root() {
    [ "$(id -u)" = "0" ] || error "This script needs root!"
}

setup() {
    echo "Synchronizing XBPS index..." | tee -pa "$LOG_FILE"
    xbps-install -Sy xbps void-repo-nonfree >/dev/null 2>&1 || echo "Failed to synchronize XBPS index! (Try manually running xbps-install -S)" | tee -pa "$LOG_FILE"

    if ! xbps-query ntp >/dev/null 2>&1; then
        echo "Installing script dependencies..." | tee -pa "$LOG_FILE"
        progs="ntp jq yq git stow fzf ncurses"
        # shellcheck disable=SC2086
        xbps-install -y $progs >/dev/null 2>&1

        echo "Synchronizing time..." | tee -pa "$LOG_FILE"
        ntpdate "pool.ntp.org" >/dev/null 2>&1 || warn "Failed to synchronize time!"
    fi

    echo "Done!" | tee -pa "$LOG_FILE"
}

install_packages() {
    IFS='
'
    set +e
    for p in $(yq -c '.packages.[]' <config.yml); do
        command="$(echo "$p" | yq -r '.command[0]')"
        pre_command="$(echo "$p" | yq -r '.pre[]' 2>/dev/null)"
        flags=$(echo "$p" | yq -r '.command[1:].[]')
        packages=$(echo "$p" | yq -r '.list[]')
        [ -n "${packages}" ] && {
            $pre_command
            case $(echo "$p" | yq -r '.local') in
                true)
                    echo "$flags" "$packages" | sudo -i -u "$username" xargs "$(echo "$p" | yq -r '.command[0]')" ||
                        touch /tmp/bootstrapper-failed
                    ;;
                false)
                    echo "$flags" "$packages" | xargs "$command" ||
                        touch /tmp/bootstrapper-failed
                    ;;
            esac
        }
    done 2>&1 | scroll 7
    set -e

    if [ -f /tmp/bootstrapper-failed ]; then
        warn "Failures during package installation, check logs."
        rm /tmp/bootstrapper-failed
    else
        info "Done!"
    fi
}

install_files() {
    (
        cd "${SCRIPT_DIR}/files" || exit 1
        find . -type d -exec mkdir -p "/{}" \;
        find . -type f -exec install -o root -g root "{}" "/{}" \;
        find . -type l -exec cp -d "{}" "/{}" \;
    )
    info "Done!"
}

create_user() {
    failed=false
    while ! echo "$username" | grep "^[a-z_][a-z0-9_-]*$" | grep -qv "root"; do
        $failed && warn "Invalid username, try again!"
        username=$(prompt "Input Username: ")
        failed=true
    done

    if id -u "$username" >/dev/null 2>&1; then
        warn "User \"$username\" already exists, Skipping user creation!"
        usermod -G "$USER_GROUPS" "$username"
    else
        info "Creating user \"$username\" with the following groups: \"$USER_GROUPS\"..."
        useradd -m -G "$USER_GROUPS" "$username"
        failed=false
        while [ -z "${pass1:-}" ] || [ "${pass1:-}" != "${pass2:-}" ]; do
            $failed && warn "Passwords do not match or are empty, try again!"
            pass1=$(prompt "Input Password: ")
            pass2=$(prompt "Repeat Password: ")
            failed=true
        done
        echo "$username:$pass1" | chpasswd
    fi

    user_home=$(getent passwd "$username" | cut -d ':' -f 6)
    [ -z "$username" ] &&
        error "\$username variable is empty, this script is bugged!"
    [ -z "$user_home" ] &&
        error "\$user_home variable is empty, this script is bugged!"
    sudo -u "$username" [ -w "$user_home" ] || error "$username can't write to '$user_home'!"

    info "Done!"
}

create_directories() {
    #shellcheck disable=SC2016
    directories=$(yq -c '.directories.[]' <${CONFIG_FILE})
    counter=1
    num_dirs=$(echo "$directories" | wc -l)

    for entry in $directories; do
        path=$(echo "$entry" | jq -r '.path')
        mode=$(echo "$entry" | jq -r '.mode')
        [ "$mode" = "null" ] && mode="0755"

        info "Creating directory ${counter} of ${num_dirs}: ~/${path}"
        [ -d "${user_home}/${path}" ] || mkdir -m "${mode}" -p "${user_home}/${path}"

        counter=$((counter + 1))
    done 2>&1 | scroll 7

    info "Done!"
}

install_dotfiles() {
    info "Cloning dotfiles..."
    mkdir -p "${user_home}/${STOW_DIR}"
    if [ ! -d "${user_home}/${STOW_DIR}/${DOTS_PACKAGE}/.git" ]; then
        if ! git -C "${user_home}/${STOW_DIR}" clone -q --recurse-submodules -b "$DOTS_BRANCH" "$DOTS_REPO" "$DOTS_PACKAGE"; then
            warn "Failed to clone dotfiles"
            return 1
        fi
    fi
    info "Symlinking dotfiles..."
    if ! stow -d "$user_home/$STOW_DIR" -t "$user_home" "$DOTS_PACKAGE" 1>/dev/null 2>&1; then
        warn "Failed to symlink dotfiles"
        return 2
    fi
    info "Done!"
}

select_keymap() {
    [ -L "${user_home}/.local/share/xkb/compiled/keymap" ] && return
    map="$(find "${user_home}/.local/share/xkb/compiled" -type f -printf "%f\n" | fzf --header="Select a keymap keymap:")"
    ln -s "$map" "${user_home}/.local/share/xkb/compiled/keymap"
}

# FIX: This has to work in a chroot
enable_services() {
    services=$(yq -r '.services[]' <config.yml)
    counter=1
    num_services=$(echo "$services" | wc -l)

    info "Removing old services..."
    for sv in /var/service/*; do
        echo "$services" | grep -qx "$(basename "$sv")" || rm "$sv"
    done

    info "Enabling services..."
    for service in $services; do
        info "Enabling service ${counter} of ${num_services}: ${service}"
        [ ! -L "/var/service/${service}" ] && ln -s "/etc/sv/${service}" "/var/service/"

        counter=$((counter + 1))
    done 2>&1 | scroll 7

    info "Done!"
}

finalize() {
    gid=$(getent passwd "$username" | cut -d ':' -f 4)
    groupname=$(getent group "$gid" | cut -d ':' -f 1)
    info "Setting ownership of home directories..."
    chown "$username:$groupname" -R "$user_home"
    info "Done!"
}

### CONTROL FLOW BEGINS HERE ###

trap 'tput cnorm; tput sgr0; exit' INT TERM EXIT

echo >"$LOG_FILE"

check_root
setup

BOLD="$(tput bold)"
RED="$(tput setaf 1)"
GREEN="$(tput setaf 2)"
YELLOW="$(tput setaf 3)"
BLUE="$(tput setaf 4)"
RESET="$(tput sgr0)"

emphasize "-- Copying Files --"
install_files

# emphasize "-- Creating Symlinks --"
# create_symlinks

username="${SUDO_USER:-}"
if [ -z "$username" ]; then
    emphasize "-- Creating User Account --"
    create_user
else
    user_home=$(getent passwd "$username" | cut -d ':' -f 6)
fi

emphasize "-- Installing Dotfiles --"
install_dotfiles
select_keymap

emphasize "-- Creating Standard Home Directories --"
create_directories

emphasize "-- Installing Packages --"
install_packages

emphasize "-- Enabling Services --"
enable_services

emphasize "-- Finalizing Installation --"
finalize

emphasize "-- Installation Complete --"