#!/bin/bash

#
# Simple ranking of mirrors for the EndeavourOS mirrorlist.
#
# TODO:
#   - option to test timeouts from small to larger

source /etc/eos-color.conf

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

notif_echo2()   { printf2 "==> " ; echo2   "$@" ; }
notif_printf2() { printf2 "==> " ; printf2 "$@" ; }

UseColor() { [ $use_colors = yes ] && eos-color "$@" 2 ; }

InfoColor()    { UseColor info    ; echo2 "$@"; UseColor reset ; }
ErrorColor()   { UseColor error   ; echo2 "$@"; UseColor reset ; }
WarningColor() { UseColor warning ; echo2 "$@"; UseColor reset ; }

WARN() {
    local msg="$1"
    WarningColor "==> $progname: warning: $msg"
}
INFO() {
    local msg="$1"
    local prefix="$2"
    case "$prefix" in
        "") InfoColor "==> $progname: info: $msg" ;;
        *)  InfoColor "$prefix$msg" ;;
    esac
}
ERROR() {
    local metadata="$1"
    local realdata="$2"
    ErrorColor "==> $progname: error: $metadata"
    [ "$realdata" ] && ErrorColor "    $realdata"
}

DIE() {
    local msg="$1"
    local level="$2" ; [ "$level" ] || level=0
    ERROR "error detected at function ${FUNCNAME[$((level+1))]}, line ${BASH_LINENO[$level]}:" "$msg"
    DeleteTmpFiles
    Usage
    exit 1
}

verbose() {
    if [ "$verbose" = "yes" ] ; then
        echo2 "  $1"
    fi
}

DeleteTmpFiles() {
    if [ ${#delete_at_end[@]} -gt 0 ] ; then
        echo2 ""
        verbose "Deleting temporary files: ${delete_at_end[*]}"
        rm -f "${delete_at_end[@]}"
    fi
}

RemoveFromDelList() {
    if [ ${#delete_at_end[@]} -gt 0 ] ; then
        local -r remove_me="$1"
        local oldlist=("${delete_at_end[@]}")
        local xx
        delete_at_end=()
        for xx in "${oldlist[@]}" ; do
            [ "$xx" != "$remove_me" ] && delete_at_end+=("$xx")
        done
    fi
}

ShowArray() {
    local item
    for item in "$@" ; do
        echo $item
    done
}

SortMirrors() {
    local latest_ml="$1"
    local sortopts="$2"
    local marr mirror m m2
    local rank_errs=0
    local lastm=""
    local lastix ix

    readarray -t marr < <(grep "^Server = " $latest_ml)

    [ "$added_mirror" ] && marr+=("Server = $added_mirror")        # for test-ranking a mirror

    lastix=${#marr[@]}
    while [ $lastix -gt 0 ] ; do
        ((lastix--))
        lastm="${marr[$lastix]}"
        [ "$lastm" ] && break
    done
    [ "$lastm" ] || return 1   # no mirrors!

    if [ ${#marr[@]} -gt 0 ] ; then
        for mirror in "${marr[@]}" ; do
            m=$(echo "$mirror" | awk '{print $3}')                    # mirror value (with variables)
            verbose "$m"
            m2=$(echo "$m" | sed 's|/$repo/$arch$||')                 # mirror url (without variables)
            if ! GetUpdateNumberAndTime "$m2/state" "$m"              # update number from "state" and its fetch time
            then
                ((rank_errs++))
            fi
            if [ "$mirror" = "$lastm" ] ; then
                if [ $rank_errs -ne 0 ] ; then
                    local secs=5
                    # notif_printf2 "Sleeping $secs seconds...\n"
                    sleep $secs                                       # some mirrors failed, sleep a few seconds to show it
                fi
            fi
        done | LC_ALL=C sort -g $sortopts
    fi
}

RankPerAge() {
    # latest_ml : in     # servers not commented!
    # result    : out
    local mirror m m2
    local marr
    local sortopts=""
    local sort_rate="-k3,3 -k2r,2"
    local sort_age="-k2r,2 -k3,3"

    case "$sort" in
        rate | fastest | speed)
            sortopts="$sort_rate" ;;
        age | latest)
            sortopts="$sort_age" ;;
        *)
            WARN "value '$sort' of option --sort is unsupported, using 'age'."
            sortopts="$sort_age"
            ;;
    esac

    readarray -t result < <(SortMirrors "$latest_ml" "$sortopts")

    if [ ${#result[@]} -le 1 ] ; then
        local msg="no mirrors found!"
        [ $timeout -lt 10 ] && msg+=" Too short timeout ($timeout)?"
        DIE "$msg"
    fi
}

WarningAbout() {
    local id="$1"
    shift
    UseColor warning
    printf2 "==> Warning about %s:\n" "$id"
    [ "$1" ] && printf2 "    %s\n" "$@" #"Continuing."
    UseColor reset
}

GetUpdateNumberAndTime() {
    local url="$1"
    local mirror="$2"
    local tmpfile="$HOME/.eos-rank-state.123456"
    local result=0
    local retcode=0
    local time
    local nr
    local low_limit=999999           # still acceptable value (use a very high initial number!)
    local too_low_limit=999999       # fails when below this

    if [ "$reference_level" ] ; then
        low_limit=$((reference_level - 2))
        too_low_limit=$((reference_level - 10))
    fi

    time=$(curl --fail -Lsm $timeout -w "%{time_total}" "$url" -o"$tmpfile")
    retcode=$?
    case "$retcode" in
        0)
            nr="$(head -n1 "$tmpfile")"
            rm -f "$tmpfile"

	    if [ "${nr//[0-9]/}" ] ; then   # check that $nr is a number!
		WarningAbout "$mirror" "Unrecognized data returned by this mirror."
		return
	    fi

            if [ "$reference_level" ] ; then
                # ad hoc tests for the update level value in the state file
                if [ $nr -gt $reference_level ] ; then
                    if [ "$mirror" != 'https://mirror.alpix.eu/endeavouros/repo/$repo/$arch' ] || [ $nr -gt $((reference_level + 1)) ] ; then
                        WarningAbout "$mirror" "Update level $nr is too high (> $reference_level), ignoring this mirror."
                    fi
                    return
                elif [ $nr -lt $too_low_limit ] ; then
                    WarningAbout "$mirror" "Update level $nr/$reference_level is very low, ignoring this mirror."
                    return
                elif [ $nr -lt $low_limit ] ; then
                    WarningAbout "$mirror" "update level $nr/$reference_level is lower than expected, will lower the ranking of this mirror."
                fi
            fi
            result="$nr $time"
            echo "$mirror $result"      # "mirror nr time"
            ;;
        *)
            local curl_msg=""
            local msg=()
            local add_code=no
            case "$mirror_verbosity" in
                all | code | show)
                    case "$retcode" in
                        28) msg+=("Connection to this mirror failed after $timeout seconds.") ;;
                      # 7)  msg+=("Connection to this mirror failed. The mirror may be temporarily or permanently offline.") ;;
                        *)  msg+=("Connection to this mirror failed (curl exit code $retcode).") ;;
                    esac
                    case "$mirror_verbosity" in
                        all)
                            curl_msg="$(curl-exit-code-to-string $retcode)"
                            if [ "$curl_msg" ] ; then
                                msg+=("--> (curl exit code $retcode: $curl_msg)")
                            else
                                add_code=yes
                            fi
                            ;;
                        code)
                            add_code=yes
                            ;;
                    esac
                    [ "$add_code" = "yes" ] && msg+=("--> (curl exit code $retcode.)")

                    WarningAbout "$mirror" "${msg[@]}"
                    ;;
                none)
                    ;;
            esac
            rm -f "$tmpfile"
            return $retcode
            ;;
    esac
}

GetReferenceUpdateLevel() {
    local refroot="https://mirror.alpix.eu/endeavouros/repo"
    local result=$(GetUpdateNumberAndTime "$refroot/state" "$refroot"/'$repo/$arch')

    [ "$result" ] && reference_level=$(echo "$result" | awk '{print $2}')
}

CheckMirrorlist() {
    if [ "$internal_testing" = "yes" ] ; then
        local file="$1"
        if grep "^#[ ]*Server = " "$file" ; then
            RemoveFromDelList "$latest_ml"
            DIE "endeavouros-mirrorlist (see file $file) has one or more mirrors commented out!" 1
                 # 1 = level of BASH_LINENO array (which starts from 0)
        fi
    fi
}
ExtractListFromPackage() {
    local -r pkg="$1"
    [ "$pkg" ] || return 1
    INFO "extracting package $pkgname $latest_remote ..."
    tar --extract -O -f "$pkg" etc/pacman.d/endeavouros-mirrorlist > $latest_ml
}

GetLatestRemoteVersion() {
    local pkgname="$1"
    local -n _latest="$2"

    local lat=""
    local data
    local url
    local urls=(
        https://mirror.alpix.eu/endeavouros/repo/endeavouros/${arch}
        https://mirror.moson.org/endeavouros/repo/endeavouros/${arch}
        $(grep ^Server /etc/pacman.d/endeavouros-mirrorlist | awk '{print $3}' | sed -e "s|\$repo|$repo|" -e "s|\$arch|$arch|")
    )
    for url in "${urls[@]}" ; do
        data=$(curl -Lsm 10 "$url" | grep "$pkgname" | grep -v zst\.sig | sed -E -e "s|.*>(${pkgname}-.*\.zst)<.*|\1|")
        case "$data" in
            ${pkgname}-*-any.pkg.tar.zst)
                lat=$(pkg-name-components --real VR "$data")
                _latest="$lat"
                return
                ;;
        esac
    done
    _latest=$(expac -S %v "$pkgname")         # this might not work if pacman -Sy has not been run
}

FetchLatestMirrorlist() {
    # Grab endeavouros-mirrorlist from a mirror.

    # fix $arch for the ARM repo

    local latest_remote=""
    GetLatestRemoteVersion "$pkgname" latest_remote
    local pkg
    local code=0
    local url
    local urls2="$(grep ^Server /etc/pacman.d/endeavouros-mirrorlist | awk '{print $3}')"
    urls2=$(LANG=C echo "$urls2" | sed -e "s|\$repo|$repo|" -e "s|\$arch|$arch|" -e "s|$|/endeavouros-mirrorlist-${latest_remote}-any.pkg.tar.zst|")
    local urls=(
        "https://mirror.alpix.eu/endeavouros/repo/endeavouros/${arch}/endeavouros-mirrorlist-${latest_remote}-any.pkg.tar.zst"
        "https://mirror.moson.org/endeavouros/repo/endeavouros/${arch}/endeavouros-mirrorlist-${latest_remote}-any.pkg.tar.zst"
        $urls2
    )

    DEBUG "'$arch' '${urls[0]}'" 

    pkg=$(mktemp /tmp/XXXXX.tar.zst)
    delete_at_end+=("$pkg")

    for url in "${urls[@]}" ; do
        curl --fail -Lsm $timeout -o $pkg "$url"
        code=$?
        [ $code -eq 0 ] && break
    done
    [ $code -eq 0 ] || DIE "cannot fetch EndeavourOS mirrorlist package (curl error code $code [$url])."
    ExtractListFromPackage "$pkg" && CheckMirrorlist "$latest_ml"
}

SimpleRank() {
    # Rank mirrors from the latest available mirrorlist file.
    #
    # The resulting mirrorlist will contain:
    # - the latest available endeavouros-mirrorlist (mirrors commented out)
    # - the results of ranking
    # - the ranked mirrorlist

    local result IGNORE=""
    local xx

    latest_ml=$(mktemp /tmp/tmp.XXXXX) ; delete_at_end+=("$latest_ml")

    FetchLatestMirrorlist

    if [ ${#EOS_IGNORED_MIRRORS[@]} -gt 0 ] ; then
        IGNORE=${EOS_IGNORED_MIRRORS[*]}
        IGNORE=${IGNORE// /\|}
        [ "$ignored_mirrors" ] && ignored_mirrors+="|"
        ignored_mirrors+="$IGNORE"
    fi

    local pref="$preferred_mirrors $ALWAYS_FIRST_EOS_MIRRORS"
    if [ "$pref" != " " ] ; then
        pref=${pref//[,|]/ }
        INFO "preferring mirrors: $pref"
    fi

    if [ "$ignored_mirrors" != "" ] ; then
        ignored_mirrors=${ignored_mirrors//[,|]/ }
        INFO "ignoring mirrors: ${ignored_mirrors}"
        # grep -Pv "$ignored_mirrors" $latest_ml > $latest_ml.tmp
        # rm -f $latest_ml
        # mv $latest_ml.tmp $latest_ml
        for xx in ${ignored_mirrors} ; do
            sed -i $latest_ml -E -e "s|^(Server[ ]*=[ ]*.*$xx.*)|#\1|"
        done
    fi

    rm -f $latest_ml.orig
    mv $latest_ml $latest_ml.orig
    delete_at_end+=("$latest_ml.orig")
    grep "^Server[ ]*=" $latest_ml.orig | sort | uniq > $latest_ml

    # $result will contain an array of lines like: "mirror update-ordinal fetch-time"
    INFO "ranking EndeavourOS mirrors, please wait ..."
    RankPerAge

    local result_orig=("${result[@]}")      # copy the info if the displaying order below changes...

    ShowActualRankingResult                 # changes "${result[@]}"

    if [ "$mirrorlist_only" = "no" ] ; then
        # Show optional info that is not disabled.
        [ $show_ranking_info  = yes ] && ShowRankingInfo "${result_orig[@]}"
        [ $show_original_list = yes ] && ShowOriginalMirrorlist
    fi

    SeparatorLine
}

ShowItems() {
    local prompt="$1"
    local items="$2"
    local item
    local maxlen=70
    local line="$prompt"

    for item in $items ; do
        if [ $(( ${#line} + ${#item})) -gt $maxlen ] ; then
            echo "$line"
            line="$prompt $item"
        else
            line+=" $item"
        fi
    done
    [ "$line" != "$prompt" ] && echo "$line"
}

ShowActualRankingResult() {
    SeparatorLine
    echo "# EndeavourOS mirrorlist, $rank_signature at $(date +%x\ %X)."
    if [ "$preferred_mirrors" ] || [ "$ALWAYS_FIRST_EOS_MIRRORS" ] ; then
        ShowItems "# Preferred mirrors:" "${preferred_mirrors} ${ALWAYS_FIRST_EOS_MIRRORS}"
    fi
    [ "$ignored_mirrors" ] && ShowItems "# Ignored mirrors:" "${ignored_mirrors}"
    SeparatorLine

    # Option --prefer takes precedence over variable ALWAYS_FIRST_EOS_MIRRORS when both are used.

    RemoveUserAddedFromList "$pref"

    if [ "$preferred_mirrors" ] ; then
        UserAddedMirrors "$preferred_mirrors"
    fi

    if [ "$ALWAYS_FIRST_EOS_MIRRORS" ] ; then
        UserAddedMirrors "$ALWAYS_FIRST_EOS_MIRRORS"  # Example: ALWAYS_FIRST_EOS_MIRRORS='tuna|moson|umu'
    fi

    INFO "Results of ranking EndeavourOS mirrors:\n" "==> Info: "

    ShowArray "${result[@]}" | /usr/bin/awk '{print $1}' | /usr/bin/sed 's|^|Server = |'
}

ShowRankingInfo() {
    # show the result of ranking (in comments)
    [ $save = no ] && UseColor info
    echo ""
    SeparatorLine
    printf "# Mirror ranking info at (UTC) %s:\n" "$(date -u "+%x %X")"
    printf "# The following fields are shown for each mirror:\n"
    printf "#   mirror:          The mirror address\n"
    printf "#   update-level:    Ordinal number of the latest update (larger is newer)\n"
    printf "#   fetch-time:      Measures the speed of the mirror (smaller is faster)\n"
    printf "#\n"
    while true ; do
        echo "mirror update-level fetch-time"
        echo "~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~"
        ShowArray "$@"
        break
    done | /usr/bin/column -t | /usr/bin/sed 's|^|# |'
    [ $save = no ] && UseColor reset
}

SeparatorLine() {
    echo "#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
}

ShowOriginalMirrorlist() {
    echo ""
    SeparatorLine
    echo -e "\n### Original mirrorlist before ranking:\n"
    sed 's|^Server = |#Server = |' $latest_ml.orig         # comment mirrors out
}

RemoveUserAddedFromList() {
    local -r prefs="$1"
    local pref
    local item
    local tmp=()
    local found=no

    if [ "$prefs" ] ; then
        for item in "${result[@]}" ; do
            for pref in $prefs ; do
                case "$item" in
                    *"$pref"*) found=yes
                               [ "$internal_testing" = yes ] && INFO "Dropping duplicate:  $(echo "$item" | awk '{print $1}')"
                               continue 2
                               ;;
                esac
            done
            tmp+=("$item")
        done
    fi
    [ "$found" = "yes" ] && result=("${tmp[@]}")
}

UserAddedMirrors() {
    local mirrors="$1"    # a list of strings separated by '|' (pipe) chars
    local mirror m

    echo "# User added mirrors >>>"

    for m in ${mirrors//[,|]/ } ; do
        case "$m" in
            Server=*)
                # Server=<full-mirror-name> is another supported syntax for user added preferred mirrors.
                # Note1: white spaces are not allowed.
                # Note2: these mirrors will not be ranked.
                INFO "User added: ${m//=/ = }"
                echo "${m//=/ = }"
                ;;
            *)
                mirror="$(grep "^[ ]*Server[ ]*=[ ]*" "$latest_ml.orig" | grep "$m")"
                if [ "$mirror" ] ; then
                    INFO "User added: $mirror"
                    echo "$mirror"
                fi
                ;;
        esac
    done
    echo2 ""
    echo "# User added mirrors <<<"
}

WriteSystemMirrorlist() {
    local eos="$1"
    local new="$2"
    local bak="$eos.bak"
    local eosranked="${eos}.pacnew"
    local cmd=""

    if ! diff "$latest_ml.orig" "$new" >/dev/null ; then
        echo2 ""
        if [ "$EOS_AUTO_MIRROR_RANKING" = "yes" ] || [ "$hook_rank" = "no" ] ; then
            # saving new list to /etc/pacman.d/endeavour-mirrorlist
            notif_printf2 "Moving old EndeavourOS mirrorlist to %s.\n" "$bak"
            notif_printf2 "Writing new ranked EndeavourOS mirrorlist to %s.\n" "$eos"
            $EOS_ROOTER "mv $eos $bak ; mv $new $eos ; chown root:root $eos ; chmod 0644 $eos"
            [ -r "$new" ] && notif_printf2 "Failed.\n"    # probably password failure
        else
            # saving new list to /etc/pacman.d/endeavour-mirrorlist.pacnew
            notif_printf2 "Writing new ranked EndeavourOS mirrorlist to %s.\n" "$eosranked"
            $EOS_ROOTER "mv $new $eosranked ; chown root:root $eosranked ; chmod 0644 $eosranked"
            [ -r "$new" ] && notif_printf2 "Failed.\n"    # probably password failure
        fi
    else
        INFO "The new EndeavourOS mirrorlist file ($new) was not ranked, not saving it."
        return
    fi
}

DumpOptions() {
    local user_shorts=${SHORT_OPTIONS//:/}                 # remove all :
    user_shorts=${user_shorts//?/ -&}                      # add " -" to each letter

    local user_longs=${LONG_OPTIONS//:/}                   # remove all :
    user_longs="--${user_longs//,/ --}"                    # commas to spaces and add -- to each name

    case "$1" in
        --all)
            local internal_options=${LONG_OPTIONS_INTERNAL//:/}    # remove all :
            internal_options="--${internal_options//,/ --}"        # commas to spaces and add -- to each name
            echo $user_longs $user_shorts $internal_options
            ;;
        *)
            echo $user_longs $user_shorts
            ;;
    esac
    exit 0
}

Usage() {
    cat <<EOF
Purpose: $progname is meant for creating a new file that can be used as file
             $emlpath
         after ranking the mirrors.

Note 1:  The Arch mirrorlist file /etc/pacman.d/mirrorlist will not be updated with this app.
Note 2:  This app potentially creates file $emlpath.pacnew which needs to merged.
Note 3:  File $conf can contain many more settings and options, explained in the file.

Usage: $progname [options]

Options:
  --help, -h
             This help.

  --ignore <wordlist>
             Mirrors to be ignored from the generated mirrorlist.
             This is useful for e.g. ignoring non-functional mirrors.
             The <wordlist> is a list of words separated by a comma or
             a pipe '|' character.
             Each word must be a unique part of a mirror address.
             Note that the list should be enclosed in single quotes if
             it includes any special character like '$' or '|'.
             This option can be used more than once.
             Examples:
                 eos-rankmirrors --ignore='funami|pizza'
                 eos-rankmirrors --ignore=belnet,gigenet

  --mirror-verbosity <value>
             How much information will be shown when a mirror fails.
             Supported values: see file $conf, setting
             EOS_RANKMIRRORS_EXIT_CODE_VERBOSITY.

  --no-save, -n
             Don't save the generated mirrorlist.

  --prefer <wordlist>
             List of preferred mirrors, in the given order.
             This is useful if you want to automatically add
             certain mirrors (obviously reliable, fast and well updated)
             as first in the resulting mirrorlist.
             The <wordlist> is like in option --ignore.
             Examples:
                 eos-rankmirrors --prefer=funami,accum,moson
                 eos-rankmirrors --prefer='https://endeavour.remi.lu/repo/\$repo/\$arch'

  --show-orig-list <value>
             Add the original mirror list into $emlpath.
             Supported values: "yes" or "no".
             Default: $show_original_list_default.

  -b         Same as '--show-orig-list no'.

  --show-rank-info
             Show the ranking data in $emlpath.
             Supported values: "yes" or "no".
             Default: $show_ranking_info_default.

  --sort <value>
             Primary key for sorting the mirrors.
             Supported key values:
                 age     (latest first)
                 rate    (fastest first)
             Default: $sort_default.

  --timeout, -t <value>
             Set the timeout value (in seconds) for checking a mirror.
             Default: $timeout_default.

  --use-local-mirrorlist
             For testing purposes.
             Uses information in local $mlfolder/$pkgname as the base for ranking.

  --verbose
             Show more detailed output.

Advanced options:
  --list-only
             Save only the mirrorlist without the ranking statistics.

  --mirror-add
             Temporarily add a mirror URL for ranking (for testing purposes only).
EOF
}

Options() {
    local opts

    opts=$(getopt -o=$SHORT_OPTIONS --longoptions $LONG_OPTIONS,$LONG_OPTIONS_INTERNAL --name "$progname" -- "$@") \
        || DIE "getopt detected unsupported option, will not continue."

    eval set -- "$opts"

    while true ; do
        case "$1" in
            # internal tool options:
            --internal-testing)         internal_testing=yes ;;
            --hook-rank)                hook_rank=yes ;;
            --dump-options)             DumpOptions ;;
            --dump-options=all)         DumpOptions --all ;;
            --mirror-add)               added_mirror="$2" ;;
            --debug)                    debugging=yes ;;

            # all user options
            --sort)                     sort="$2" ; shift ;;
            --ignore)                   [ "$ignored_mirrors" ]   && ignored_mirrors+=" ${2//[,|]/ }"   || ignored_mirrors="${2//[,|]/ }" ; shift ;;
            --prefer)                   [ "$preferred_mirrors" ] && preferred_mirrors+=" ${2//[,|]/ }" || preferred_mirrors="${2//[,|]/ }" ; shift ;;
            --use-local-mirrorlist)     use_local_mirrorlist=yes ;;
            --timeout | -t)             timeout="$2" ; shift ;;
            --verbose)                  verbose=yes ;;
            --mirror-verbosity)         mirror_verbosity="$2" ; shift ; Check-mirror_verbosity option ;;
            --no-save | -n)             save=no ;;
            --list-only)                mirrorlist_only=yes ;;  # and no other output
            --show-rank-info)           show_ranking_info="$2"; shift ;;
            --show-orig-list)           show_original_list="$2"; shift ;;
            -b)                         show_original_list=no ;;
            --test-all | -a)            save=no
                                        test_all=yes
                                        EOS_IGNORED_MIRRORS=()
                                        ignored_mirrors=""
                                        [ $timeout -lt $timeout_default ] && timeout=$timeout_default
                                        ;;
            --colors)                   use_colors="$2"
                                        shift
                                        case "$use_colors" in
                                            yes | no) ;;
                                            *) use_colors=yes ;;
                                        esac
                                        ;;
            --)                         shift; break ;;
        esac
        shift
    done
}

Check-mirror_verbosity() {
    local where="$1"   # option or configfile
    case "$mirror_verbosity" in
        all | code | show | none) ;;

        *)  printf2 "==> warning: "
            case "$where" in
                configfile) printf2 "file '$conf': EOS_RANKMIRRORS_EXIT_CODE_VERBOSITY" ;;
                option)     printf2 "option --mirror-verbosity" ;;
            esac
            echo2 " has unsupported value '$mirror_verbosity', will use '$mirror_verbosity_def'."
            mirror_verbosity="$mirror_verbosity_def"
            ;;
    esac
}

Main()
{
    local -r SHORT_OPTIONS="abhnt:"
    local -r LONG_OPTIONS_INTERNAL="dump-options::,hook-rank,list-only,mirror-add:,internal-testing,debug"
    local    LONG_OPTIONS="verbose,help,no-save,test-all,timeout:,sort:,ignore:,prefer:,mirror-verbosity:"
    LONG_OPTIONS+=",use-local-mirrorlist,show-rank-info:,show-orig-list:,colors:"

    local -r progname=${0##*/}         # eos-rankmirrors

    source /usr/share/endeavouros/scripts/eos-script-lib-yad --limit-icons || return 1
    export -f eos_GetArch

    local debugging=no
    local use_colors=yes

    local -r sort_default=age                     # age or rate
    local -r timeout_default=30                   # in seconds
    local -r show_original_list_default=no        # yes or no
    local -r show_ranking_info_default=yes        # yes or no

    local -r pkgname=endeavouros-mirrorlist
    local -r mlfolder=/etc/pacman.d
    local eos=$mlfolder/$pkgname
    local -r conf=/etc/$progname.conf
    local -r emlpath=/etc/pacman.d/endeavouros-mirrorlist
    local -r repo=endeavouros
    local arch=$(eos_GetArch)
    [ "$arch" != x86_64 ] && arch=aarch64

    # Don't do anything time consuming before dumping options.
    local arg
    for arg in "$@" ; do
        case "$arg" in
            --help | -h)        Usage; exit 0 ;;
            --dump-options)     DumpOptions ;;
            --dump-options=all) DumpOptions --all ;;
        esac
    done

    local -r rank_signature="ranked by $progname"   # enables checking $rank_signature

    local CMD_OPTIONS=()

    local new=""
    local latest_ml=""
    local delete_at_end=()
    local save=yes
    local hook_rank=no
    local ignored_mirrors=""
    local preferred_mirrors=""
    local reference_level=""
    local -r mirror_verbosity_def="show"
    local mirror_verbosity=""
    local use_local_mirrorlist=no
    local mirrorlist_only=no                                    # 'no' provides rather verbose output, 'yes' provides only the essential list
    local show_ranking_info="$show_ranking_info_default"        # subject to $mirrorlist_only
    local show_original_list="$show_original_list_default"      # subject to $mirrorlist_only
    local added_mirror=""
    local internal_testing=no
    local test_all=no

    export -f DeleteTmpFiles
    trap "sleep 0.1 ; DeleteTmpFiles" EXIT    # do this *after* DumpOptions to make bash completion faster

    if [ ! -r "$eos" ] ; then                 # this test is not really needed...
        DIE "no local $eos found, please install package '$pkgname'."
    fi

    source $conf || DIE "Config file $conf does not exist!"
    ALWAYS_FIRST_EOS_MIRRORS=${ALWAYS_FIRST_EOS_MIRRORS//[,|]/ }
    [ "$EOS_SHOW_ORIGINAL_MIRRORLIST" ] && show_original_list="$EOS_SHOW_ORIGINAL_MIRRORLIST"

    mirror_verbosity="$EOS_RANKMIRRORS_EXIT_CODE_VERBOSITY"

    Check-mirror_verbosity configfile

    # option-related variables
    local verbose=no
    local sort=$sort_default
    local timeout=$timeout_default
    if [ "$EOS_AUTORANK_TIMEOUT" ] && [ "$EOS_AUTORANK_TIMEOUT" != "$timeout_default" ] ; then
        timeout="$EOS_AUTORANK_TIMEOUT"
    fi

    eos-connection-checker || DIE "internet connection not available!"

    GetReferenceUpdateLevel

    Options "${CMD_OPTIONS[@]}" "$@"

    [ "$test_all" = "no" ] && source /etc/$progname.disabled

    new=$(/usr/bin/mktemp) ; delete_at_end+=("$new")

    SimpleRank > $new || DIE "ranking failed."
    [ "$hook_rank" = "no" ] && cat $new

    [ "$(grep "^Server = " $new)" ] || DIE "ranking failed, no mirrors found!"

    [ "$save" = "yes" ] && WriteSystemMirrorlist "$eos" "$new"

    DeleteTmpFiles
}

DEBUG() {
    if [ "$debugging" = yes ] ; then
        echo "DEBUG/$progname/line=${BASH_LINENO[0]}:" "$@"
    fi
}

Main0() {
    LANG=C Main "$@"
}

Main0 "$@"
