#!/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
}

RankMirrors() {
    local latest_ml="$1"
    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
        local mirrors_to_rank=()
        for mirror in "${marr[@]}" ; do
            m=$(echo "$mirror" | awk '{print $3}')                            # mirror value (with variables)
            m2=${m/\/\$repo\/\$arch/}
            verbose "$m2"
            mirrors_to_rank+=("$m2")
        done

        RankMirrors_$rankingmode
    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 < <(RankMirrors "$latest_ml" | LC_ALL=C sort -g $sortopts)

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

RankMirrors_parallel() {
    local parallel_opts=""   #"--bar"
    printf "%s\n" "${mirrors_to_rank[@]}" | parallel $parallel_opts "eos-rankmirrors-helper {} $timeout $mirror_verbosity $reference_level"
}

RankMirrors_sequential() {
    for m2 in "${mirrors_to_rank[@]}" ; do
        verbose "$m2"
        if ! eos-rankmirrors-helper "$m2" "$timeout" "$mirror_verbosity" "$reference_level"   # update number from the "state" file 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
}

GetReferenceUpdateLevel() {
    local refroot="https://mirror.alpix.eu/endeavouros/repo"
    local result=$(eos-rankmirrors-helper "$refroot" $timeout "$mirror_verbosity" "")

    [ "$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//[,|]/ }
        [ "${pref: -1}" = " " ] && pref="${pref:: -1}"
        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

    # show the results of ranking EndeavourOS mirrors

    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=""
    local -r eos_rooter="sudo bash -c"

    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
}

NanoViewer() {
    local file="$1"
    local cmd="$nano_pager"
    if [ "$file" ] ; then
        if [ -r "$file" ] ; then
            cat "$file" | $cmd
        else
            echo "==> file $file not found" >&2
        fi
    else
        $cmd
    fi
}

LocalGetPager() {
    if [ $use_pager = no ] ; then
        echo "cat"
    elif [ "$EOS_RANKMIRRORS_PAGER" ] ; then
        echo "$EOS_RANKMIRRORS_PAGER"
    else
        GetPager         # from /usr/share/endeavouros/scripts/eos-script-lib-yad
    fi
}

EditConfig() {
    local editor opts=""
    for editor in "${EOS_RANKMIRRORS_EDITOR[@]}" "${EOS_SUDO_EDITORS[@]}" ; do
        if which "${editor%% *}" &>/dev/null ; then
            case "${editor%% *}" in
                nano | rnano) opts="--modernbindings"
            esac
            break
        fi
    done
    sudo $editor $opts "$conf"
}

Usage() {
    cat <<EOF | $pager
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.

  --edit-config
             Edit $conf.

  --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'

  --parallel, -p
             Rank mirrors in parallel (default).
             This should be much faster than using --sequential
             especially if option --ignore is not used.

  --sequential, -s
             Rank mirrors in a sequential manner.
             This usually is much slower than using --parallel.

  --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.

  --pager
             Use a pager app for showing the ranked mirrorlist.
             The app will be selected from a list of popular pagers,
             or via variables EOS_RANKMIRRORS_PAGER (see $conf) or PAGER.
             This works when not saving the mirrorlist.

  --pager-less
             Same as --pager but uses "less" as the pager.

  --pager-nano
             Same as --pager but uses "nano" as the pager.

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
            --edit-config)              EditConfig; exit 0 ;;
            --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
            --pager)                    use_pager=yes: pager=$(LocalGetPager) ;;
            --pager-nano)               use_pager=yes; pager=nano-pager ;;
            --pager-less)               use_pager=yes; pager="$less_pager" ;;
            --parallel | -p)            rankingmode=parallel ;;
            --sequential | -s)          rankingmode=sequential ;;
            --show-rank-info)           show_ranking_info="$2"; shift ;;
            --show-orig-list)           show_original_list="$2"; shift ;;
            -b)                         show_original_list=no ;;
            --test-all | -a)            # Internal testing option to test all mirrors.
                                        # save=no
                                        # [ $timeout -lt $timeout_default ] && timeout=$timeout_default
                                        test_all=yes
                                        EOS_IGNORED_MIRRORS=()
                                        ignored_mirrors=""
                                        show_original_list=no
                                        ;;
            --colors)                   use_colors="$2"
                                        shift
                                        case "$use_colors" in
                                            yes | no) ;;
                                            *) use_colors=yes ;;
                                        esac
                                        ;;
            --)                         shift; break ;;
        esac
        shift
    done
    [ "$test_all" = "no" ] && source /etc/$progname.disabled
}

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="abhnpst:"
    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:,parallel,sequential"
    LONG_OPTIONS+=",pager,pager-nano,pager-less,edit-config"

    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 rankingmode=parallel                    # parallel or sequential
    local -r less_pager="less -RF"
    local -r nano_pager="nano +1 --view -"
    local use_pager=no
    local pager="$(LocalGetPager)"

    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
            --pager)                    use_pager=yes: pager=$(LocalGetPager) ;;
            --pager-nano)               use_pager=yes; pager=nano-pager ;;
            --pager-less)               use_pager=yes; pager="$less_pager" ;;
        esac
    done
    for arg in "$@" ; do
        case "$arg" in
            --help | -h)        Usage; exit 0 ;;
            --dump-options|--dump-options=all|--pager|--pager-nano|--pager-less) Options "$arg" ;;
        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

    [ -e "$eos" ] || sudo touch "$eos"

    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[@]}" "$@"

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

    SimpleRank > $new || DIE "ranking failed."

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

    if [ "$hook_rank" = "no" ] ; then
        # show the ranked list
        if [ $save = yes ] || [ $use_pager = no ] ; then
            cat $new
        else
            $pager $new
        fi
    fi
    [ "$save" = "yes" ] && WriteSystemMirrorlist "$eos" "$new"                    # save ranked list to endeavouros-mirrorlist

    DeleteTmpFiles
}

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

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

Main0 "$@"
