#!/bin/bash

echo2() { echo -e "$@" >&2 ; }

indent2() {
    local line="$1"
    echo "$line" | sed 's|^|    |' >&2
}

Msg() {
    if [ "$quiet" = "no" ] ; then
        local date=$(date "+%Y-%m-%d %H:%M:%S")
        echo2 "$date:" "$@"
    fi
}
MsgForce() {
    local quiet_old="$quiet"
    quiet=no
    Msg "$@"
    quiet="$quiet_old"
}
Info() { Msg Info: "$@" ; }
Note() { eos-color tip 2; MsgForce Note: "$@"; eos-color reset 2; }                 # like Info() but show always
NoteList() {
    local arg
    for arg in "$@" ; do
        MsgForce "     " "$arg"
    done
}
Warn() { eos-color warning 2; MsgForce Warning: "$@"; eos-color reset 2; }
DIE()  { eos-color error 2; MsgForce Error: "$@"; eos-color reset 2; exit 1 ; }

IsInstalled() {
    local pkg="$1"
    echo "$all_installed_packages" | grep "^$pkg$" >/dev/null
}

IsRepoPkg() {
    local pkg="$1"
    echo "$repopkgs" | grep "^$pkg$" >/dev/null
}

PkgInstall() {
    # prepares installing given packages
    install+=("$@")
}

PkgRemove() {
    # prepares removing given packages if they are installed
    local pkg

    for pkg in "$@" ; do
        if IsInstalled "$pkg" ; then
            remove+=("$pkg")
        fi
    done
}

EnsureDkms() {
    if [ $dkms_added = no ] ; then
        if [ ! -x /bin/dkms ] ; then
            adjusted+=(dkms)
            dkms_added=yes
        fi
    fi
    local kernel kernels=(linux linux-lts linux-hardened linux-zen linux-rt linux-rt-lts)
    for kernel in "${kernels[@]}" ; do
        if IsInstalled "$kernel" ; then
            IsInstalled "${kernel}-headers" || adjusted+=("${kernel}-headers")
        fi
    done
}

CheckUnsupportedKernels() {
    local unsupported_kernels="$(expac -Qs %n linux headers | grep -P 'rt-|hardened-|zen-' | sed 's|-headers||')"
    [ "$unsupported_kernels" ] && Warn "unsupported kernels installed: $unsupported_kernels"
}

DIE_AUR() {
    local pkg="$1"
    DIE "$pkg: AUR packages not supported"
}

AdjustPkgsAboutDkms() {
    # check if dkms or non-dkms packages need to be installed/removed
    local pkg ix ic=${#install[@]} rc=${#remove[@]}
    local adjusted=()

    case "$dkms" in
        no)
            # This far all was done in favor of dkms packages. Now change dkms stuff to non-dkms.

            for ((ix=0; ix<ic; ix++)) ; do
                pkg="${install[$ix]}"
                case "$pkg" in
                    nvidia-open-dkms)
                        # Info "Changing $pkg to nvidia-open"
                        IsInstalled linux     && adjusted+=(nvidia-open)
                        IsInstalled linux-lts && adjusted+=(nvidia-open-lts)
                        CheckUnsupportedKernels
                        ;;
                    bbswitch-dkms)
                        # Info "Changing $pkg to bbswitch"
                        adjusted+=(bbswitch)
                        ;;
                    nvidia-beta-dkms)
                        DIE_AUR "$pkg"
                        ;;
                    *)
                        adjusted+=("$pkg")
                        ;;
                esac
            done
            if [ ${#adjusted[@]} -gt 0 ] ; then
                install=("${adjusted[@]}")
            fi

            adjusted=()

            for ((ix=0; ix<rc; ix++)) ; do
                pkg="${remove[$ix]}"
                case "$pkg" in
                    nvidia-open-dkms)
                        # Info "Changing $pkg to nvidia-open"
                        IsInstalled linux     && adjusted+=(nvidia-open)
                        IsInstalled linux-lts && adjusted+=(nvidia-open-lts)
                        ;;
                    bbswitch-dkms)
                        # Info "Changing $pkg to bbswitch"
                        adjusted+=(bbswitch)
                        ;;
                    *)
                        adjusted+=("$pkg")
                        ;;
                esac
            done
            if [ ${#adjusted[@]} -gt 0 ] ; then
                remove=("${adjusted[@]}")
            fi
            ;;
        yes)
            local dkms_added=no
            for ((ix=0; ix<ic; ix++)) ; do
                pkg="${install[$ix]}"
                case "$pkg" in
                    nvidia-open-dkms | bbswitch-dkms)
                        EnsureDkms
                        adjusted+=("$pkg")
                        ;;
                    nvidia-beta-dkms)
                        DIE "$pkg: AUR packages not supported"
                        ;;
                    *)
                        adjusted+=("$pkg")
                        ;;
                esac
            done
            if [ ${#adjusted[@]} -gt 0 ] ; then
                install=("${adjusted[@]}")
            fi
            ;;
    esac
}

KeepsAndRemoves() {
    AdjustPkgsAboutDkms

    # remove pr_nvidia[] except install[]

    local rp kp

    if [ ${#install[@]} -eq 0 ] ; then
        PkgRemove "${pr_nvidia[@]}"
    else
        for rp in "${pr_nvidia[@]}" ; do
            if IsInstalled "$rp" ; then
                for kp in "${install[@]}" ; do
                    [ "$rp" = "$kp" ] && break
                done
                if [ "$rp" != "$kp" ] ; then
                    if [ "$aur_driver" ] && [ "$rp" = "$aur_driver" ] && [ $quiet = no ] ; then
                        [ $remove_nothing = no ] && Warn "removing $rp, but it should work with your GPU."
                    fi
                    PkgRemove "$rp"
                fi
            fi
        done
    fi

    # remove already installed packages from $install
    local tmp=("${install[@]}")
    install=()
    for kp in "${tmp[@]}" ; do
        if ! IsInstalled "$kp" ; then
            install+=("$kp")
        else
            # Special note for certain packages since they are from the AUR. Note: the only AUR support in nvidia-inst currently!
            case "$kp" in
                $aur_driver | ${aur_driver%-dkms}-utils) Note "Package $kp is set for install but it is installed already." ;;
            esac
        fi
    done

    # check if install contains any AUR stuff

    local repopkgs="$(expac -S %n)"

    for kp in "${install[@]}" ; do
        if ! IsRepoPkg "$kp" ; then
            aurs+=("$kp")               # We don't support AUR packages!
        fi
    done
}

AddCmd() {
    [ "$cmd" ] && cmd+="; "
    cmd+="$1"
}

Bumblebee() {
    local user=$(whoami)
    local exec1="Exec=/usr/bin/nvidia-settings"
    local exec2="Exec=optirun -b none /usr/bin/nvidia-settings -c :8"
    local desktop=/usr/share/applications/nvidia-settings.desktop
    local group
    local -r no_such_unit=4   # see the "EXIT STATUS" of command 'man systemctl'

    case "$mode" in
        bumblebee)
            # for old cards ==> don't use nvidia-open
            PkgInstall bumblebee bbswitch-dkms $p_nvidia $p_nvidia32 xf86-video-intel

            if [ "$user" != "root" ] ; then
                for group in bumblebee video ; do
		    if [ -z "$(id $user | grep "($group)")" ] ; then
			Info "Adding user $user to group: $group"
			AddCmd "gpasswd -a $user $group"
		    fi
                done
            fi
            if [ "$(grep "$exec1" $desktop 2>/dev/null)" ] ; then
                Info "Patching $desktop (with optirun)"
                AddCmd "sed -i $desktop -e 's|$exec1|$exec2|'"
            fi
            systemctl status bumblebeed.service >& /dev/null
            case "$?" in
                $no_such_unit)
                    Info "Enabling bumblebeed.service"
                    AddCmd "systemctl enable bumblebeed.service"
                    ;;
            esac
            ;;
        *)
            # remove bumblebee settings
            systemctl status bumblebeed.service >& /dev/null
            case "$?" in
                $no_such_unit)
                    ;;
                *)
                    if [ "$user" != "root" ] ; then
                        for group in bumblebee video ; do
                            if [ "$(id $user | grep "($group)")" ] ; then
                                Info "Removing user $user from group: $group"
                                AddCmd "gpasswd -d $user $group"
                            fi
                        done
                    fi
                    Info "Disabling bumblebeed.service"
                    AddCmd "systemctl disable bumblebeed.service"
                    ;;
            esac
            ;;
    esac
}

Nouveau() {
    PkgInstall vulkan-nouveau     # installing xf86-video-nouveau no more recommended
}

Prime() {
    PkgInstall nvidia-prime $p_nvidia $p_nvidia32 nvidia-hook
}

SwitcherooEnable() {
    [ $quiet = yes ] && return
    local cmds=(
        "sudo pacman -Syu --needed switcheroo-control"
        "sudo systemctl enable --now switcheroo-control.service"
    )
    local cmd has_header=no
    for cmd in "${cmds[@]}" ; do
        if [ $test = no ] ; then
            $cmd
        else
            [ $has_header = no ] && echo2 "$commands_to_run_string:"
            has_header=yes
            echo2 "    $cmd"
        fi
    done
}

OnlyNvidia() {
    DebugBreak
    [ $driver_source_type = unknown ] && return
    if [ $driver_source_type = nouveau ] && [ "$aur_driver" ] && IsInstalled $aur_driver ; then
        PkgInstall $aur_driver ${aur_driver%-dkms}-utils
    elif [ "$series" = "$latest_from_arch" ] ; then
        PkgInstall $p_nvidia $p_nvidia32 nvidia-hook
    else
        PkgInstall $p_nvidia $p_nvidia32 # nvidia-hook
    fi
}

AddConfFileLine() {
    local line="$1"
    local tmp="$(printf "echo %-40s >> $conf_file\n" "'$line'")"
    AddCmd "$tmp"
}

CreateConfFile() {
    if [ "$create_conf" = "yes" ] ; then
        if [ -r $conf_file ] ; then
            Info "File $conf_file already exists, will not overwrite."
            return
        fi
        Info "Creating file $conf_file"
        AddConfFileLine 'Section "Device"'
        AddConfFileLine '    Identifier "Nvidia Card"'
        AddConfFileLine '    Driver "nvidia"'
        AddConfFileLine '    VendorName "NVIDIA Corporation"'
        AddConfFileLine '    Option "NoLogo" "true"'
        AddConfFileLine 'EndSection'
    else
        if [ -r $conf_file ] ; then
            local conf_file_save="$conf_file".save
            Info "Rename existing file $conf_file to $conf_file_save"
            AddCmd "mv $conf_file $conf_file_save"
        fi
    fi
}

IsNvidiaCard() {
    [ "$NvidiaData" ]
}

IsoVersion() {
    local VERSION=""
    local file=/usr/lib/endeavouros-release
    LANG=C source $file || return
    echo "$VERSION"
}

ShowCommandsToRun() {
    echo2 "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    echo2 "$commands_to_run_string:"
    local xx
    if [ "$cmd" ] ; then
        xx=$(echo "$cmd"  | sed -e 's|; |\n|g')
        indent2 "$xx"
    else
        indent2 "==> Looks like all requested packages and settings are already OK!"
    fi
    echo2 "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

    return 0
}

SeparateShortOpts() {
    local opts="$1"
    if [ "$opts" ] ; then
        opts=$(echo "$opts" | sed -e 's|:|=|g' -e 's|\([a-zA-Z]\)| -\1|g')
        opts="${opts:1}"
    fi
    echo "$opts"
}

SeparateLongOpts() {
    local opts="$1"
    if [ "$opts" ] ; then
        opts=$(echo "$opts" | sed -e 's|:||g' -e 's|,| --|g')
        opts="--$opts"
    fi
    echo "$opts"
}

ListOptions() {
    local sopts="$1"
    local lopts="$2"

    sopts=$(SeparateShortOpts "$sopts")
    lopts=$(SeparateLongOpts "$lopts")
    echo "$lopts $sopts"
}

ViewFile() {
    local -r file="$1"
    local app
    local apps=(xdg-open exo-open kde-open)
    for app in "${apps[@]}" ; do
        if [ -x /usr/bin/$app ] ; then
            /usr/bin/$app "$file"
            break
        fi
    done
}

Options() {
    local opts
    local sopts="bfhnopqtv"
    local lopts="32,bumblebee,conf,force,help,ignore,listopts,listseries"   # ,legacyrepo
    lopts+=",no-dkms,nouveau,open,prime,quiet,series:,test,version,id:,switcheroo,show-gpu-info"
    lopts+=",no-settings,recommended-driver,view-pci-ids,no-hwdata-update,remove-nothing"

    opts="$(/usr/bin/getopt -o=$sopts --longoptions $lopts --name "$progname" -- "$@")" || {
        Options -h
        return 1
    }

    eval set -- "$opts"

    while true ; do
        case "$1" in
            --no-dkms)          dkms=no ;;
            --no-settings)      settings_pkg=no ;;        # "internal" option, used when installing EOS

            -b | --bumblebee)   mode=bumblebee ;;
            -n | --nouveau)     mode=nouveau ;;
            -p | --prime)       mode=prime ;;
            -o | --open)        driver_source_type=open ;;
            --switcheroo)       SwitcherooEnable; exit 0 ;;
            -f | --force)       force=yes ;;
            -q | --quiet)       quiet=yes ;;
            -t | --test)        test=yes ;;
            --conf)             create_conf=yes ;;
            --ignore)           ignore_errors=yes ;;
            --32)               bit32=yes ;;
            --series)           series="$2" ; shift ;;
            --listopts)         ListOptions "$sopts" "$lopts" ; exit 0 ;;
            --listseries)
                [ "$series_now" ] || series_now=$(GetSeriesFromPacman)
                echo "$series_now"
                exit 0
                ;;
            --show-gpu-info)    echo "$NvidiaData" ; exit 0 ;;
            --recommended-driver)
                ShowRecommendedDriver
                exit 0
                ;;
            --view-pci-ids)                            # internal option
                ViewFile /usr/share/hwdata/pci.ids
                exit 0
                ;;
            -v | --version)
                [ "$PROGVERSION" ] || PROGVERSION=$(expac -Q %v "$progname")
                echo "$progname $PROGVERSION"
                exit 0
                ;;
            --id)
                nvidia_card_id="$2"
                shift
                ;;
            --remove-nothing)
                remove_nothing=yes
                ;;
            --no-hwdata-update)  # handled in Main
                ;;
            -h | --help)
                cat <<EOF >&2

$progname - help installing drivers for Nvidia GPU

Usage: $progname [options]

Options:
  -h, --help              This help.
  -b, --bumblebee         Install bumblebee and Nvidia drivers for optimus cards (only very old GPUs).
  -f, --force             Force driver installation even if no Nvidia GPU is detected.
  -n, --nouveau           Use kernel's nouveau driver instead of Nvidia's proprietary driver.
  -o, --open              Install the Nvidia driver (default for the 'Turing' family and newer GPUs).
  -p, --prime             Install also prime render offload support.
  -q, --quiet             Suppress log messages.
  -t, --test              Test mode. Nothing in your system will be modified.
  -v, --version           Show version of this software.
  --32                    Add support for 32-bit apps. Repository [multilib] in $pacmanconf must be enabled.
  --switcheroo            Install switcheroo-control and enable its service. Use this option separately.
  --conf                  Create also file $conf_file (might be needed on some older systems).
  --no-dkms               Install only non-dkms packages (e.g. 'nvidia-open' instead of 'nvidia-open-dkms').
  --show-gpu-info         Show information about your GPU. Use this option separately.
  --listseries            List Nvidia driver versions supported by $progname.
  --ignore                Ignore all errors (useful for testing only).
  --series=X              (Advanced) Use the given Nvidia driver version X (for testing).
  --remove-nothing        (Advanced) Do not remove any existing package (for testing).
  --listopts              List options supported by $progname.

Notes:
   * By default, $progname detects the Nvidia GPU family and selects automatically between
     Nvidia's open source driver, or the nouveau kernel driver.
   * NVIDIA no longer actively supports older than Turing family GPUs. Thus using
     the nouveau driver (or a driver from the AUR) with them may provide a solution.
     See also: https://wiki.archlinux.org/title/NVIDIA#Installation
   * By default, dkms packages are preferred and will be selected for install if none of options
          --no-dkms
          --nouveau
          --bumblebee
     is used.
   * If you have more than one GPU in the system, then you may need a tool to choose the GPUs to use.
     There are several popular tools in the AUR, e.g.
          - envycontrol
          - optimus-manager
          - supergfxctl
     to mention a few.
     See also:
          - https://discovery.endeavouros.com/?s=nvidia
          - https://wiki.archlinux.org/title/NVIDIA_Optimus

EOF
                exit 0
                ;;
            
            --) shift ; break ;;
        esac
        shift
    done
}

AllRemovables() {
    # Find all packages that this program may uninstall and add them to array pr_nvidia.

    pr_nvidia=(
        bumblebee
        bbswitch
        bbswitch-dkms
        xf86-video-nouveau
        vulkan-nouveau
        lib32-vulkan-nouveau

        # nvidia packages from repos:
        $(expac -Ss %n nvidia | grep -P '^nvidia|^lib32-nvidia' | grep -Pv 'nvidia-inst|-toolkit|beta')

        # old nvidia packages that no more exist
        nvidia-dkms nvidia nvidia-lts
    )

    readarray -t pr_nvidia < <(printf "%s\n" "${pr_nvidia[@]}" | sort -u)
}

ProperNvidiaPackages() {
    # Set proper values for
    # - p_nvidia
    # - p_nvidia32
    # because older nvidia drivers may be needed.

    [ -z "$series" ] && series="$series_now"   # we support only what Arch repos have

    case "$series" in
        $latest_from_arch)
            case "$driver_source_type" in
                open)           p_nvidia="nvidia-open-dkms $p_nvidia"
                                p_nvidia32="lib32-nvidia-utils"
                                [ $settings_pkg = yes ] && p_nvidia+=" nvidia-settings"
                                ;;
                nouveau)        p_nvidia="vulkan-nouveau"
                                p_nvidia32="lib32-vulkan-nouveau"
                                ;;
                *)              DIE "Nvidia GPU not detected!" ;;
            esac
            ;;
    esac

    if [ "$bit32" = "yes" ] ; then
        [ $has_multilib = yes ] || DIE "To support 32-bit apps, enable the [multilib] repo in $pacmanconf."
    else
        p_nvidia32=""
    fi
}

HasRepo() { grep "^\[$1\]" $pacmanconf >/dev/null; }

YesOrNo() {
    local question="$1"           # the question to ask
    local default="$2"            # the default answer, either N or Y (or lower case the same)
    local options=""              # for showing the accepted answer

    case "$default" in
        [Nn]) options="(N/y)" ;;
        [Yy]) options="(Y/n)" ;;
    esac

    while true ; do
        read -p "$question ${options}? " reply >&2
        [ "$reply" ] || reply="$default"
        case "$reply" in
            [Nn]) exit 0 ;;
            [Yy]) break ;;
        esac
    done
}

SanityChecks() {
    local reply

    if [ $driver_source_type = open ] && [ $driver_source_type_initial = nouveau ] ; then
        Warn "Option --open used, but nvidia-open fails to support this GPU!"
        YesOrNo "Do you really want to proceed" N
    fi
    if [ $driver_source_type = nouveau ] && [ $driver_source_type_initial = open ] ; then
        Warn "Selected to use nouveau, but package nvidia-open supports this GPU!"
        YesOrNo "Do you want to proceed" Y
    fi
}

GetSeriesFromPacman() {
    local pacman_version=$(echo "$latest_arch_version_full" | sed 's|-[0-9.]*$||')   # only pkgver
    local pacman_version_series=${pacman_version%%.*}
    echo "$pacman_version_series"
}

RecommendedDriverSourceType() {
    # Outputs one of: open, nouveau, unknown
    # References:
    # - https://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units
    # - https://us.download.nvidia.com/XFree86/Linux-x86_64/570.133.07/README/supportedchips.html
    # - https://wiki.archlinux.org/title/NVIDIA#Installation
    # - https://nouveau.freedesktop.org/wiki/CodeNames.html
    # - /usr/share/hwdata/pci.ids

    local family=$(lspci -d $NVIDIA::03xx | sed -E 's|.* NVIDIA Corporation ([^ ]+) .*|\1|')

    case "$family" in
        "")
            # Nvidia GPU not detected
            driver_source_type=unknown
            return 1
            ;;
        TU*|GA*|AD*|GB*|GN*)
            # Turing, Ampere, Ada Lovelace, Blackwell.
            # (GN=? is from file /usr/share/hwdata/pci.ids.)
            driver_source_type=open
            ;;
        GM*|GP*|GV*)
            # Maxwell, Pascal and Volta
            driver_source_type=nouveau
            aur_driver=nvidia-580xx-dkms
            IsInstalled $aur_driver || Note "Package $aur_driver from the AUR should support this GPU."
            ;;
        *)
            # Older than Maxwell.
            driver_source_type=nouveau
            ;;
    esac
    return 0
}

ShowRecommendedDriver() {
    # Show the recommended driver (one of: nvidia, nvidia-open, nouveau) and stop.
    # No output means Nvidia GPU was not detected.
    # Note: this function is used by the ISO installer.

    if [ $hwdata_update = yes ] ; then
        sleep 1
        eos-connection-checker && sudo pacman -Sy --needed --noconfirm hwdata &> /dev/null
    fi
    local family=$(lspci -d $NVIDIA::03xx | sed -E 's|.* NVIDIA Corporation ([^ ]+) .*|\1|')
    case "$family" in
        "")                   ;;                    # Nvidia GPU not found.
        TU*|GA*|AD*|GB*|GN*)  echo "nvidia-open" ;; # Nvidia's open source driver.
        GM*|GP*|GV*|*)        echo "nouveau" ;;     # For older Nvidia GPUs.
    esac
}

Main()
{
    # Support only these Nvidia series (must update these values too!).
    # Include:
    # - the latest from Arch
    # - two latest from the kernel-lts repo

    local -r progname=${0##*/}
    local -r NVIDIA=10de
    local quiet=no
    local arg
    local hwdata_update=yes

    # quick check for special options
    for arg in "$@" ; do
        case "$arg" in
            --no-hwdata-update)   hwdata_update=no ;;    # useful with option --recommended-driver
            --recommended-driver) Options "$arg" ;;      # for the ISO installer
            --listopts)           Options "$arg" ;;      # for bash completion
        esac
    done

    local all_installed_packages="$(expac -Q %n)"

    local NvidiaData="$(lspci -vnn | grep NVIDIA | grep -P 'VGA|3D|Display')"

    local pacmanconf=/etc/pacman.conf
    local mode="nvidia"
    local aur_driver=""                                           # might recommend this (if has value)
    local driver_source_type=""                                   # open, nouveau, unknown
    DebugBreak
    RecommendedDriverSourceType
    local driver_source_type_initial="$driver_source_type"
    local force=no
    local test=no
    local create_conf=no
    local bit32=no
    local ignore_errors=no
    local series=""
    local dkms=yes                                                # yes or no
    local settings_pkg=yes
    local conf_file=/etc/X11/xorg.conf.d/20-nvidia.conf
    local PROGVERSION=""
    local pr_nvidia=()
    local p_nvidia=""
    local p_nvidia32=""
    local install=()
    local remove=()
    local remove_nothing=no
    local aurs=()                                                 # collect needed AUR packages in this array
    local cmd=""
    local nvidia_card_id=""                                       # for option --id to test any card id
    local -r commands_to_run_string="COMMANDS TO RUN"

    local series_now=""                                           # get it later because of the speed for bash completion
    local series_prev1=470-not-supported
    local series_prev2=390-not-supported
    local has_multilib=no
    HasRepo multilib && has_multilib=yes

    local -r latest_arch_version_full=$(expac -S %v nvidia-open-dkms)

    DebugBreak

    Options "$@"

    if [ $has_multilib = yes ] && [ $bit32 = no ] && IsInstalled lib32-nvidia-utils ; then
        bit32=yes  # assume user still wants 32-bit support...
    fi

    if [ $test = yes ] && [ $quiet = no ] ; then
        [ "$NvidiaData" ] && Info "$NvidiaData"

        Info "Currently installed packages related to Nvidia:"
        local ipkgs
        readarray -t ipkgs < <(expac -Qs "%n %v" nvidia)
        NoteList "${ipkgs[@]}"
        Info "inxi -Gza"
        inxi -Gza >&2
    fi

    [ "$series_now" ] || series_now=$(GetSeriesFromPacman)

    DebugBreak
    SanityChecks

    local -r latest_from_arch=$(echo "$latest_arch_version_full" | cut -d '.' -f1)
    if [ "$latest_from_arch" != "$series_now" ] ; then
        Warn "Series $series_now is no more the latest in Arch but $latest_from_arch is. Results may not be accurate."
    fi

    DebugBreak
    AllRemovables

    [ "$PROGVERSION" ] || PROGVERSION=$(expac -Q %v "$progname")
    Info "$progname version $PROGVERSION"
    Info "Command line: $progname $*"

    case "$driver_source_type" in
        open)
            case "$mode" in
                nouveau) Info "Selected mode: $mode (freedesktop.org's open source)" ;;
                *)       Info "Selected mode: $mode (Nvidia's open source)" ;;
            esac
            ;;
        nouveau)
            case "$mode" in
                nouveau) Info "Selected mode: $mode"
                         ;;
                *)       if [ "$aur_driver" ] && IsInstalled "$aur_driver" ; then
                             # keep the existing AUR driver (updates should be handled by yay etc.)
                             Info "Selected mode: $mode"
                         else
                             # we don't actively support AUR drivers
                             local -r wanted_mode=$mode
                             mode=nouveau
                             Info "Mode changed from '$wanted_mode' to '$mode'"
                         fi
                         ;;
            esac
            ;;
        *)
            Warn "could not find Nvidia GPU."
            if [ "$force" = "no" ] ; then
                if ! IsNvidiaCard ; then
                    [ "$ignore_errors" = "no" ] && exit 0
                fi
            fi
            ;;
    esac

    DebugBreak
    ProperNvidiaPackages

    # Now all packages in various situations are known.

    Bumblebee  # add or remove bumblebee stuff !!
    case "$mode" in
        bumblebee) ;;
        nouveau)   Nouveau ;;
        prime)     Prime   ;;
        nvidia)    OnlyNvidia ;;
    esac

    DebugBreak
    KeepsAndRemoves
    CreateConfFile

    if [ ${#install[@]} -gt 0 ] ; then
        Info "Installing packages: ${install[*]}"
        if [ "${#aurs[@]}" -gt 0 ] ; then
            cmd="yay -Syu ${install[*]}; $cmd"
        else
            cmd="pacman -Syuq --noconfirm --noprogressbar --needed ${install[*]}; $cmd"
        fi
    fi
    [ $remove_nothing = yes ] && remove=()
    if [ ${#remove[@]} -gt 0 ] ; then
        Info "Removing packages: ${remove[*]}"
        cmd="pacman -Rs --noconfirm --noprogressbar --nodeps ${remove[*]}; $cmd"
    fi

    ShowCommandsToRun

    local txt=""

    if [ "$(printf "%s\n" "${install[@]}" | grep -P "nvidia-390|nvidia-470")" ] ; then
        if [ "$(grep "^\[kernel-lts\]" $pacmanconf )" ] ; then
            txt+="Sorry, the [kernel-lts] repo is no more supported.\n"
        else
            txt+="Sorry, $progname does not support installing packages from AUR.\n"
        fi
    fi
    if [ "${#aurs[@]}" -gt 0 ] ; then
        txt+="    To continue, manually run all commands from $commands_to_run_string above.\n"
        DIE "$txt"
    fi
    [ "$test" = "yes" ] && return 0   # dryrun

    # The commands are actually executed here.
    # If AUR packages are involved, the sudo/root password needs to be given twice!

    if [ "$cmd" ] ; then
        DebugBreak
        printf "\n==> NOTE: running the commands may take several minutes...\n\n"
        sudo bash -c "$cmd" && Note "To have the changes in effect, you must reboot the computer."
    fi
}

DebugBreak() { : ; }

Main "$@"
