#!/usr/bin/env bash

set -o errexit
set -o pipefail

CONFIG="$(mktemp)"
declare -rgA default_values=(
    ["wifi_if"]="wlan0"
    ["install_dev"]="/dev/mmcblk2"
    ["repo"]="https://alpha.de.repo.voidlinux.org/current/aarch64"
    ["arch"]="aarch64"
    ["timezone"]="Europe/Zurich"
    ["keymap"]="uk"
    ["locale"]="en_US.UTF-8 UTF-8"
)

get_value() {
    local white=$'\e[97m'
    local nocolour=$'\e[0m'

    local var_name="$1"
    local prompt="$2"
    local secure=""

    if [ -n "$3" ]; then
        secure="s"
    fi

    # shellcheck source=/dev/null
    . "$CONFIG"

    if [ -z "${!var_name}" ]; then
        local def_val="${default_values[$var_name]}"
        read -${secure}rp "$white$prompt [$def_val]:$nocolour " value
        
        echo "export $var_name=\"${value:-$def_val}\"" >> "$CONFIG"
        
        # shellcheck source=/dev/null
        . "$CONFIG"
    fi

    echo "${!var_name}"
    
}

network() {
    local wifi_if
    wifi_if="$(get_value "wifi_if" "wifi interface name")"    

    ip link set "$wifi_if" up

    nmcli device wifi list

    local wifi_ssid
    wifi_ssid="$(get_value "wifi_ssid" "wifi ssid")"    
    local wifi_pw
    wifi_pw="$(get_value "wifi_pw" "wifi password" 1)"    

    nmcli con delete "$wifi_ssid" || true
    nmcli device wifi connect "$wifi_ssid" password "$wifi_pw"
}

datetime() {
    local datetime
    datetime="$(get_value "datetime" "date and time now (like 2020-01-01 12:00)")"

    date "+%Y--%m-%d %T" --set="$datetime"
    hwclock --systohc
}

partition() {
    local install_dev
    install_dev="$(get_value "install_dev" "installation device")"

    if [ ! -e "$install_dev" ]; then
        echo "device node $install_dev does not exist"
        exit 1
    fi

    sfdisk --force "$install_dev" << EOF
label: gpt

start=16MiB size=512MiB, type=bc13c2ff-59e6-4262-a352-b275fd6f7172
type=b921b045-1df0-41c3-af44-4c6f280d3fae
EOF

    mkfs.vfat -F32 "$install_dev"p1
   
}

create_luks() {
    local install_dev
    install_dev="$(get_value "install_dev" "installation device")"

    cryptsetup luksFormat "$install_dev"p2
}

open_luks() {
    local install_dev
    install_dev="$(get_value "install_dev" "installation device")"

    cryptsetup open "$install_dev"p2 void
}

create_lvm() {
    pvcreate /dev/mapper/void
    vgcreate void /dev/mapper/void

    lvcreate -L 48G void -n swap
    lvcreate -l 100%FREE void -n root
}

open_lvm() {
    vgchange --activate y
}

make_filesystems() {
    mkswap -L SWAP /dev/void/swap
    mkfs.xfs -f -L ROOT /dev/void/root
}

ensure_mount() {
    local src="$1"
    local dest="$2"
    local with_rbind="$3"

    local rbind=""

    if [ ! -z "$with_rbind" ]; then
        rbind="--rbind"
    fi

    mount $rbind "$src" "$dest"

    if [ ! -z "$with_rbind" ]; then
        mount --make-rslave "$dest"
    fi
}

mount_filesystems() {
    local install_dev
    install_dev="$(get_value "install_dev" "installation device")"

    ensure_mount "/dev/void/root" "/mnt"

    mkdir -p /mnt/boot
    ensure_mount "$install_dev"p1 "/mnt/boot" 

    swapon /dev/void/swap
}

install_basesystem() {
    local repo
    repo="$(get_value "repo" "package repository")"
    local arch
    arch="$(get_value "arch" "installation architecture")"

    XBPS_ARCH=$arch xbps-install -S -y -r /mnt -R "$repo" base-system pinebookpro-base dracut lvm2 cryptsetup sudo stow << 'EOF'
y
EOF
    cp /etc/resolv.conf /mnt/etc/
}

mount_special_filesystems() {
    ensure_mount "/sys" "/mnt/sys" 1
    ensure_mount "/dev" "/mnt/dev" 1
    ensure_mount "/proc" "/mnt/proc" 1
    ensure_mount "/tmp" "/mnt/tmp" 1
}

set_fstab() {
    local install_dev
    install_dev="$(get_value "install_dev" "installation device")"

    local boot_uuid
    boot_uuid="$(blkid | grep "${install_dev}p1" | awk '{print $2}')"

    cat >/mnt/etc/fstab <<EOF
tmpfs          /tmp  tmpfs defaults,nosuid,nodev 0    0
$boot_uuid     /boot vfat  defaults              0    0    
/dev/void/root /	   xfs	 defaults	             0	  0
/dev/void/swap swap	 swap	 defaults	             0	  0
EOF
}

set_boot_scr() {
    local install_dev
    install_dev="$(get_value "install_dev" "installation device")"

    local boot_txt="/mnt/boot/boot.txt"
    local boot_scr="/mnt/boot/boot.scr"
    local boot_txt_mine="/mnt/boot/boot.txt.mine"

    local mac
    mac="$(echo -n "06"; dd bs=1 count=5 if=/dev/random 2>/dev/null | hexdump -v -e '/1 " %02X"' |  tr '[:upper:]' '[:lower:]')"

    local root_uuid
    root_uuid="$(blkid | grep "${install_dev}p2" | awk '{print $2}' | sed 's/"//g')"

    sed -i "s/setenv macaddr.*/setenv macadr $mac/g" "$boot_txt"

    sed -i "s|root=PARTUUID=|root=/dev/void/root rd.auto=1 cryptdevice=${root_uuid}:lvm|g" "$boot_txt"
    cp "$boot_txt" "$boot_txt_mine"

    mkimage -A arm -O linux -T script -C none -n "boot script" -d "$boot_txt" "$boot_scr"
}

set_sudoers() {
    sed -i 's/# %wheel ALL=(ALL) ALL/%wheel ALL=(ALL) ALL/g' /mnt/etc/sudoers
}

set_user() {
    local user_name
    user_name="$(get_value "user_name" "username")"
    
    local user_pw
    user_pw="$(get_value "user_pw" "user password" 1)"

    chroot /mnt /bin/env -i \
           user_name="$user_name" \
           user_pw="$user_pw" \
           /bin/bash <<"EOT"
useradd --groups wheel --create-home --shell /bin/bash --user-group $user_name
echo -e "$user_pw\n$user_pw" | passwd "$user_name"
EOT
}

set_base_conf() {
    local timezone
    timezone="$(get_value "timezone" "timezone")"

    local keymap
    keymap="$(get_value "keymap" "keymap")"

    local hostname
    hostname="$(get_value "hostname" "hostname")"

    local locale
    locale="$(get_value "locale" "locale")"

    echo "$locale" >> /mnt/etc/default/libc-locales

    chroot /mnt /bin/env -i \
           timezone="$timezone" \
           /bin/bash <<"EOT"
xbps-reconfigure -f glibc-locales
ln -sf /usr/share/zoneinfo/$timezone /etc/localtime
EOT

    echo "$hostname" > /mnt/etc/hostname
    
    cat >/mnt/etc/hosts <<EOF
#
# /etc/hosts: static lookup table for host names
#

#<ip-address>		<hostname.domain.org>	<hostname>
127.0.0.1		localhost.localdomain	localhost $hostname 
::1			localhost.localdomain	localhost ip6-localhost $hostname

# End of file
EOF

    cat >/mnt/etc/rc.conf <<EOF
# /etc/rc.conf - system configuration for void

# Set the host name.
#
# NOTE: it's preferred to declare the hostname in /etc/hostname instead:
# 	- echo myhost > /etc/hostname
#
#HOSTNAME="void-live"

# Set RTC to UTC or localtime.
#HARDWARECLOCK="UTC"

# Set timezone, availables timezones at /usr/share/zoneinfo.
TIMEZONE="$timezone"

# Keymap to load, see loadkeys(8).
KEYMAP="$keymap"

# Console font to load, see setfont(8).
#FONT="lat9w-16"

# Console map to load, see setfont(8).
#FONT_MAP=

# Font unimap to load, see setfont(8).
#FONT_UNIMAP=

# Amount of ttys which should be setup.
#TTYS=
EOF
}

tidy_up() {
    sync

    if grep -qe "/mnt " /proc/mounts; then
        umount -R /mnt
    fi

    swapoff /dev/void/swap > /dev/null 2>&1 || true

    vgchange --activate n

    cryptsetup close void > /dev/null 2>&1 || true
}

full_install() {
    datetime
    network
    partition
    create_luks
    open_luks
    create_lvm
    open_lvm
    make_filesystems
    mount_filesystems
    install_basesystem
    mount_special_filesystems
    set_fstab
    set_boot_scr
    set_sudoers
    set_user
    set_base_conf    
    tidy_up
}

repair() {
    open_luks
    open_lvm
    mount_filesystems
    mount_special_filesystems

    chroot /mnt bash
    tidy_up
}

help() {
    cat << EOF
the following commands are available:

    datetime
    network
    partition
    create_luks
    open_luks
    create_lvm
    open_lvm
    make_filesystems
    mount_filesystems
    install_basesystem
    mount_special_filesystems
    set_fstab
    set_boot_scr
    set_sudoers
    set_user
    set_base_conf 
    full_install
    repair
    tidy_up
    help

ensure to understand what they are doing before using them
EOF
}

commands=(
    "datetime"
    "network"
    "partition"
    "create_luks"
    "open_luks"
    "open_lvm"
    "make_filesystems"
    "mount_filesystems"
    "set_fstab"
    "set_boot_scr"
    "set_sudoers"
    "set_user"
    "set_base_conf"
    "full_install"
    "repair"
    "tidy_up"
    "help"
)

cmd_loop() {
    local cmd

    while [[ "$cmd" != "exit" ]]; do
        read -rp "${white}>$nocolour " cmd

        if [[ "${commands[*]}" == *"${cmd}"* ]]; then
           $cmd 
        else
            if [ "$cmd" != "exit" ]; then  
                echo "no such command"
            fi
        fi
    done
}

trap tidy_up 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

if [ "$1" == "full" ]; then
    full_install
else
    cmd_loop
fi