#!/bin/bash

################################################################################
#
# opensuse-personalizer.sh
# Interactive post-install setup script for OpenSUSE installations
#
################################################################################

################################################################################
#
# Constants
#
################################################################################

########################################
# OpenSUSE patterns

PATTERNS=(
    # (Base system)
    patterns-base-apparmor
    patterns-base-base
    patterns-base-enhanced_base

    # (Base fonts)
    patterns-fonts-fonts

    # (Desktop environment)
    patterns-gnome-gnome_basic
)

########################################
# Zypper packages

# To remove
RM=(
    yast2-* nautilus-share
)

# To remove (and prevent reinstallation of)
AL=(
    gnome-clocks gnome-packagekit gnome-software tigervnc xterm yast2
)

# To install
IN=(
    # (Necessary packages)
    git-core flatpak opi

    # (Theming)
    papirus-icon-theme fira-code-fonts gdouros-symbola-fonts

    # (Shell utilities)
    fish fzf ispell zenity fortune toilet cowsay wmctrl libnotify-tools

    # (Programs)
    emacs inkscape gimp shotcut audacity geary nextcloud-desktop steam blender

    # (GNOME utilities)
    gnome-session-wayland gnome-characters gnome-disk-utility gnome-font-viewer gnome-dictionary gnome-logs gnome-screenshot gnome-weather eog dconf-editor baobab totem menulibre Fragments

    # (Misc.)
    distribution-logos-openSUSE distribution-logos-openSUSE-icons distribution-logos-openSUSE-Tumbleweed
)

########################################
# Flatpak packages

FLATPAK_PACKAGES=(
    com.discordapp.Discord
    com.spotify.Client
    com.github.tchx84.Flatseal
    org.nickvision.tagger
)

########################################
# GNOME extensions

GNOME_EXTENSIONS=(
    AlphabeticalAppGrid@stuarthayhurst
    color-picker@tuberry
    places-menu@gnome-shell-extensions.gcampax.github.com
)

########################################
# Junk files

JUNK_FILES=(
    .inputrc
    .i18n
    .bashrc
    .xim-template
    bin/
)

########################################
# URLs

PACKMAN_REPO_URL="https://ftp.gwdg.de/pub/linux/misc/packman/suse/openSUSE_Tumbleweed/"

FLATHUB_REMOTE_URL="https://flathub.org/repo/flathub.flatpakrepo"

PERSONALIZER_REPO_URL="https://gitlab.com/jessieh/opensuse-personalizer.git"

########################################
# Directories and files

PERSONALIZER_REPO_DIR=/tmp/opensuse-personalizer

################################################################################
#
# Pre-setup prompts
#
################################################################################

########################################
# Pre-setup

# Ensure we're not running as root
if [ "${EUID}" == 0 ]; then
    echo "DO NOT RUN THIS SCRIPT AS ROOT!"
    echo "Please run this script as a regular user."
    exit 1
fi

# Ensure PackageKit is not running so that it doesn't interrupt us
sudo systemctl stop packagekit

# Ensure "dialog" is installed
echo "Installing dialog utility..."
sudo zypper --non-interactive in dialog

########################################
# Prompt for system chassis

system_chassis=$(hostnamectl chassis)

function string_eq ()
{
    [[ "${1}" == "${2}" ]] && echo ON || echo OFF
}

function prompt_chassis_type ()
{


    entry=$(dialog --title "Chassis Type" --stdout --no-cancel --erase-on-exit \
                   --radiolist "Confirm the chassis type for this system:" 0 0 0 \
                   "Desktop" "Stationary form factor" $(string_eq "${system_chassis}" "desktop") \
                   "Laptop" "Clamshell form factor" $(string_eq "${system_chassis}" "laptop") \
                   "Tablet" "Touchscreen form factor" $(string_eq "${system_chassis}" "tablet") \
                   "Convertible" "Hybrid form factor" $(string_eq "${system_chassis}" "convertible"))

    # Ensure entry is lowercase
    entry=${entry,,}


    case "${entry}" in
        "desktop"|"laptop"|"convertible"|"tablet")
            if
                dialog --title "Chassis Type" --defaultno --yesno "Use ${entry} as chassis type?" 0 0
            then
                system_chassis="${entry}"
                return 0
            else
                return 1
            fi
            ;;
        *)
            dialog --title "Chassis Type" --msgbox "Please choose a chassis type for this system." 0 0
            return 1
            ;;
    esac

}

# Repeatedly show the prompt until we receive valid input
until prompt_chassis_type; do : ; done

# Set chassis type
echo "Setting chassis type to ${system_chassis}"
sudo hostnamectl chassis "${system_chassis}"

########################################
# Prompt for system name

system_name=$(hostnamectl hostname)

function prompt_system_name ()
{

    entry=$(dialog --title "System Name" --stdout --no-cancel --erase-on-exit \
                   --inputbox "All lowercase letters, no spaces or symbols:" 0 0 \
                   "${system_name}")

    # Filter all non-letter characters out of the user's entry
    entry=${entry//[^[:alpha:]]/}

    # Ensure entry is lowercase
    entry=${entry,,}

    case "${entry}" in
        "")
            dialog --title "Chassis Type" --msgbox "Please enter a name for this system." 0 0
            return 1
            ;;
        *)
            if
                dialog --title "Chassis Type" --defaultno --yesno "Use ${entry} as system name?" 0 0
            then
                system_name="${entry}"
                return 0
            else
                return 1
            fi
            ;;
    esac

}

# Repeatedly show the prompt until we receive valid input
until prompt_system_name; do : ; done

# Set hostname and pretty hostname
echo "Setting hostname to ${system_name}..."
sudo hostnamectl hostname "${system_name}"
echo "Setting pretty hostname to ${system_name^}"
sudo hostnamectl hostname --pretty "${system_name^}"

########################################
# Prompt for optional additions

optional_additions=""

function prompt_optional_additions ()
{

    ENTRY=$(dialog --title "Optional Additions" --stdout --no-cancel --erase-on-exit --single-quoted --output-separator '|' \
                   --checklist "Select any additional configuration changes that you would like applied to the system:" 0 0 0 \
                   "Xournal++" "Install Xournal++ for taking handwritten notes" OFF \
                   "EasyEffects" "Install EasyEffects for applying effects (e.g. equalizers) to system audio devices" OFF \
                   "Media Control Key Shortcuts" "Set up media control keyboard shortcuts (for systems without dedicated media control keys)" OFF \
                   "Power Button Screen Lock Override" "Override the power button's default behavior with a screen lock shortcut (for tablets and some convertibles)" OFF )

    case "${ENTRY}" in
        "")
            if
                dialog --title "Chassis Type" --defaultno --yesno "Apply only baseline system configuration?" 0 0
            then
                return 0
            else
                return 1
            fi
            ;;
        *)
            if
                ADDITIONS_LIST=$(echo "${ENTRY}" | sed "s/|/\\\\n   *  /g" | sed "s/'//g")
                dialog --title "Chassis Type" --defaultno --yesno "Confirm your selection of the following additions:\n${ADDITIONS_LIST}" 0 0
            then
                optional_additions="${ENTRY}"
                return 0
            else
                return 1
            fi
            ;;
    esac
}

# Repeatedly show the prompt until we receive valid input
until prompt_optional_additions; do : ; done

################################################################################
#
# Feature detection
#
################################################################################

detected_features=()

# Mobile system detection
case "${system_chassis}" in
    "latop"|"convertible"|"tablet")
        detected_features+=('Mobile')
        ;;
esac

# x86-64-v3 CPU support detection
CPU_FLAGS=$(cat /proc/cpuinfo | grep flags | head -n 1 | cut -d: -f2)
if
    echo ${CPU_FLAGS} | awk '/avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/ {found=1} END {exit !found}'
then
    detected_features+=('x86-64-v3')
fi

# Touchpad detection
if
    cat /proc/bus/input/devices | grep -i Touchpad
then
    detected_features+=('Touchpad')
fi

# Windows multi-boot detection
if
    sudo grep menuentry /boot/grub2/grub.cfg  | grep -Po ".*Windows.*"
then
    detected_features+=('Windows')
fi

################################################################################
#
# Package setup
#
################################################################################

########################################
# Package operations

echo "Setting up packages..."

sudo zypper --non-interactive install ${PATTERNS[@]}
if [[ "${detected_features[@]}" == *"Mobile"* ]]; then
    sudo zypper --non-interactive install patterns-desktop-mobile
fi
if [[ "${detected_features[@]}" == *"x86-64-v3"* ]]; then
    sudo zypper --non-interactive install patterns-glibc-hwcaps-x86_64_v3
fi

sudo zypper --non-interactive remove ${RM[@]} ${AL[@]}
sudo zypper --non-interactive addlock ${AL[@]}
sudo zypper --non-interactive install ${IN[@]}

# Install any codecs not hosted by OpenSUSE
sudo zypper --non-interactive --gpg-auto-import-keys addrepo -cfp 90 "${PACKMAN_REPO_URL}" packman
sudo zypper --non-interactive --gpg-auto-import-keys install --from packman ffmpeg gstreamer-plugins-{good,bad,ugly,libav} libavcodec-full

########################################
# Flatpak operations

echo "Setting up Flatpak remotes..."

sudo flatpak remote-add --if-not-exists flathub "${FLATHUB_REMOTE}"

echo "Setting up Flatpak packages..."
sudo flatpak install --noninteractive ${FLATPAK_PACKAGES[@]}

########################################
# (OPTIONAL) Extra packages

if [[ "${optional_additions}" == *"Xournal++"* ]]; then
    echo "Installing Xournal++..."
    sudo zypper --non-interactive addlock texlive
    sudo zypper --non-interactive install xournalpp
fi

if [[ "${optional_additions}" == *"EasyEffects"* ]]; then
    echo "Installing EasyEffects..."
    sudo zypper --non-interactive install easyeffects
fi

################################################################################
#
# System configuration
#
################################################################################

########################################
# GRUB configuration

# Silence GRUB
echo "Configuring GRUB..."
echo "# Automatically added by opensuse-personalizer.sh
GRUB_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT=0
GRUB_QUIET=true
GRUB_HIDDEN_TIMEOUT_QUIET=true" | sudo tee --append /etc/default/grub
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

########################################
# PolicyKit configuration

# Create a local policy that unlocks basic networking settings for users
echo "Configuring PolicyKit..."
sudo mkdir -p /etc/polkit/localauthority/50-local.d/
echo "[Allow users to modify system network settings]
Identity=unix-group:users
Action=org.freedesktop.NetworkManager.settings.modify.system
ResultAny=auth_admin_keep
ResultInactive=auth_admin_keep
ResultActive=yes" | sudo tee /etc/polkit/localauthority/50-local.d/10-NetworkManager.pkla

################################################################################
#
# GNOME keyboard shortcuts
#
################################################################################

echo "Configuring GNOME keyboard shortcuts..."

########################################
# Navigation

gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-left "['<Primary><Super>Left']"
gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-left "['<Primary><Super>Right']"
gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-left "['<Primary><Shift><Super>Left']"
gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-left "['<Primary><Shift><Super>Right']"

# Swap "switch applications" (grouped alt+tab) for "switch windows" (non-grouped alt+tab) shortcuts
gsettings set org.gnome.shell.window-switcher current-workspace-only false
gsettings set org.gnome.desktop.wm.keybindings switch-windows "$(gsettings get org.gnome.desktop.wm.keybindings switch-applications)"
gsettings set org.gnome.desktop.wm.keybindings switch-windows-backward "$(gsettings get org.gnome.desktop.wm.keybindings switch-applications-backward)"
gsettings set org.gnome.desktop.wm.keybindings switch-applications "[]"
gsettings set org.gnome.desktop.wm.keybindings switch-applications-backward "[]"

########################################
# (OPTIONAL) Media

if [[ "${optional_additions}" == *"Media Control Key Shortcuts"* ]]; then
    gsettings set org.gnome.settings-daemon.plugins.media-keys mic-mute "['<Super>Home']"
    gsettings set org.gnome.settings-daemon.plugins.media-keys next "['<Control><Super>Page_Down']"
    gsettings set org.gnome.settings-daemon.plugins.media-keys previous "['<Control><Super>Page_Up']"
    gsettings set org.gnome.settings-daemon.plugins.media-keys volume-down "['<Super>Page_Down']"
    gsettings set org.gnome.settings-daemon.plugins.media-keys volume-mute "['<Super>End']"
    gsettings set org.gnome.settings-daemon.plugins.media-keys volume-up "['<Super>Page_Up']"
fi

########################################
# Windows

gsettings set org.gnome.desktop.wm.keybindings close "['<Super>w']"
gsettings set org.gnome.desktop.wm.keybindings toggle-fullscreen "['<Super>f']"

########################################
# Custom shortcuts

# Helper function (because gsettings doesn't make this simple)
function add_custom_gnome_keybinding ()
{
    EXISTING_KEYBINDINGS=$(gsettings get org.gnome.settings-daemon.plugins.media-keys custom-keybindings)
    KEYBINDING_INDEX=$(expr $(echo $EXISTING_KEYBINDINGS | grep -oE '[0-9]+' | sort -n | tail -n 1) + 1)
    KEYBINDING_PATH=/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom${KEYBINDING_INDEX}/
    if [[ "$EXISTING_KEYBINDINGS" == "@as []" ]]; then
        gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "['${KEYBINDING_PATH}']"
    else
        gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "$(echo ${EXISTING_KEYBINDINGS} | sed s/.$//), '${KEYBINDING_PATH}']"
    fi
    gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:${KEYBINDING_PATH} name "${1}"
    gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:${KEYBINDING_PATH} command "${2}"
    gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:${KEYBINDING_PATH} binding "${3}"
}

# Add custom shortcuts
add_custom_gnome_keybinding "Launch Terminal" "gnome-terminal" "<Super>Return"
add_custom_gnome_keybinding "Toggle Night Light" "gnome-toggle-night-light" "<Alt><Super>n"

# (OPTIONAL) Add custom shortcuts
if [[ "${detected_features[@]}" == *"Touchpad"* ]]; then
    add_custom_gnome_keybinding "Toggle Touchpad Lock" "gnome-toggle-touchpad-lock" "<Alt><Super>t"
fi
if [[ "${optional_additions}" == *"Power Button Screen Lock Override"* ]]; then
    add_custom_gnome_keybinding "Lock Screen" "gdm-lock-screen" "PowerOff"
fi

################################################################################
#
# GNOME appearance configuration
#
################################################################################

echo "Configuring GNOME appearance settings..."

gsettings set org.gnome.desktop.interface icon-theme "Papirus"

gsettings set org.gnome.desktop.interface clock-format '24h'
gsettings set org.gnome.desktop.interface clock-show-date true
gsettings set org.gnome.desktop.interface clock-show-weekday true

gsettings set org.gnome.desktop.interface font-antialiasing "rgba"
gsettings set org.gnome.desktop.interface font-name "Cantarell 10"
gsettings set org.gnome.desktop.interface document-font-name "Cantarell 10"
gsettings set org.gnome.desktop.interface monospace-font-name "Fira Code 10"

gsettings set org.gnome.settings-daemon.plugins.color night-light-enabled true
gsettings set org.gnome.settings-daemon.plugins.color night-light-temperature 3700
gsettings set org.gnome.settings-daemon.plugins.color night-light-schedule-automatic false
gsettings set org.gnome.settings-daemon.plugins.color night-light-schedule-from 22
gsettings set org.gnome.settings-daemon.plugins.color night-light-schedule-to 6

################################################################################
#
# GNOME app folder configuration
#
################################################################################

echo "Configuring GNOME app folders..."

########################################
# Favorites

gsettings set org.gnome.shell favorite-apps "['org.gnome.Terminal.desktop', 'firefox.desktop', 'emacsclient.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Nautilus.desktop']"

########################################
# Utilities folder

UTILITIES_FOLDER_PATH=/org/gnome/desktop/app-folders/folders/Utilities/
gsettings set org.gnome.desktop.app-folders folder-children "['Utilities']"
gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} name "X-GNOME-Utilities.directory"
gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} translate true
gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} categories "[]"
gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} apps "['org.gnome.FileRoller.desktop', 'org.gnome.Characters.desktop', 'ca.desrt.dconf-editor.desktop', 'org.gnome.Dictionary.desktop', 'org.gnome.baobab.desktop', 'org.gnome.DiskUtility.desktop', 'org.gnome.Evince.desktop', 'com.github.wwmm.easyeffects.desktop', 'org.gnome.Extensions.desktop', 'com.github.tchx84.Flatseal.desktop', 'org.gnome.font-viewer.desktop', 'de.haeckerfelix.Fragments.desktop', 'org.gnome.eog.desktop', 'org.gnome.Logs.desktop', 'menulibre.desktop', 'org.gnome.Screenshot.desktop', 'gnome-system-monitor.desktop', 'org.nickvision.tagger.desktop', 'org.gnome.tweaks.desktop', 'uefi-firmware-settings.desktop', 'org.gnome.Totem.desktop']"

################################################################################
#
# GNOME extension setup
#
################################################################################

echo "Setting up GNOME extensions..."

for EXTENSION in "${GNOME_EXTENSIONS[@]}"; do
    busctl --user call org.gnome.Shell.Extensions /org/gnome/Shell/Extensions org.gnome.Shell.Extensions InstallRemoteExtension s ${EXTENSION}
done

################################################################################
#
# Junk file removal
#
################################################################################

echo "Removing junk files..."

for JUNK_FILE in "${JUNK_FILES[@]}"; do
    rm -r "${HOME}/${JUNK_FILE}"
done

################################################################################
#
# Personal configuration files and scripts
#
################################################################################

########################################
# Emacs

echo "Setting up Emacs..."

# Remove any vendor-provided Emacs configuration files
rm ${HOME}/.emacs ${HOME}/.emacs.d/init.el

# Download Emacs configuration
EMACS_CONFIG_DIR=${HOME}/.config/emacs
mkdir -p ${EMACS_CONFIG_DIR}
wget https://jessieh.net/emacs -O ${EMACS_CONFIG_DIR}/init.el

# Enable and start Emacs daemon
systemctl --user enable emacs.service
systemctl --user start emacs.service

########################################
# Fish

echo "Setting up Fish..."

# Download Fish configuration
FISH_CONFIG_DIR=${HOME}/.config/fish
mkdir -p ${FISH_CONFIG_DIR}
wget https://jessieh.net/fish -O ${FISH_CONFIG_DIR}/config.fish

# Set Fish as default shell
sudo chsh ${USER} -s $(which fish)

########################################
# Utility scripts

echo "Installing scripts..."

# Clone personalizer repo containing scripts, desktop entries, etc. to temporary folder
git clone "${PERSONALIZER_REPO_URL}" "${PERSONALIZER_REPO_DIR}"

if [[ "${detected_features[@]}" != *'Touchpad'* ]]; then
    rm ${PERSONALIZER_REPO_DIR}/scripts/gnome-toggle-touchpad-lock
fi

if [[ "${detected_features[@]}" != *'Windows'* ]]; then
    rm ${PERSONALIZER_REPO_DIR}/scripts/reboot-windows
fi

chmod +x ${PERSONALIZER_REPO_DIR}/scripts/*
sudo cp ${PERSONALIZER_REPO_DIR}/scripts/* /usr/bin

########################################
# Desktop entries

echo "Installing desktop entries..."

if [[ "${detected_features[@]}" != *'Windows'* ]]; then
    rm ${PERSONALIZER_REPO_DIR}/desktop_entries/windows.desktop
fi

mkdir -p ${HOME}/.local/share/applications/
cp ${PERSONALIZER_REPO_DIR}/desktop-entries/* ${HOME}/.local/share/applications/

################################################################################
#
# Post-setup suggestions
#
################################################################################

echo "
Personalization complete!

Recommended next steps:

 • Log in to Firefox Sync

 • Configure Nextcloud client
$(if [[ "${optional_additions}" == *"EasyEffects"* ]]; then echo " • Install an audio device profile from"; echo "      https://autoeq.app"; echo; fi)
 • Install a Wireguard VPN profile from
      https://mullvad.net/en/account/#/wireguard-config/?platform=linux

 • Configure display settings and run 'gdm-update-display-settings'

 • Run 'themer' to install/update unofficial libadwaita themes

A copy of these recommendations has been saved to ${HOME}/next-steps" | tee ${HOME}/next-steps