#!/bin/bash

#source /etc/eos-color.conf

echo2()   { echo -e "$@" >&2 ; }
printf2() {
    # shellcheck disable=2059
    printf  "$@" >&2
}

errorline() {
    eos-color error 2
    echo2 "$@"
    eos-color reset 2
}
DIE() {
    errorline "==> Error [$progname]:\n    $1"
    [ "$die2" ] && EditConfigFile "$configfile"
    [ "$2" ] && exit "$2" || exit 1
}
DIE2() {
    die2=foo
    DIE "$@"
}
INFO() {
    eos-color info 2
    echo2 "==> Info [$progname]:\n    $1"
    eos-color reset 2
}

GetInfoPage() {
    # Make sure the download data is stored in a local file.
    if [ ! -e "$datafile" ] ; then
        case "$getter" in
            curl) /bin/curl --fail -Lsm 30 -o"$datafile" -u "$HELLO" "$URLBASE"                 || DIE "failed fetching the info page '$URLBASE'." ;;
            wget) /bin/wget -qO "$datafile" --user "$ACCOUNT" --password "$PASSWORD" "$URLBASE" || DIE "failed fetching the info page '$URLBASE'." ;;
        esac
        [ "$1" != "-q" ] && echo2 "==> Info about available downloads refreshed."
    fi
}

RefreshInfo() {
    rm -f "$datafile"
    GetInfoPage "$@"
}

ShowFiles() {
    # Show downloadable files from the fetched download page.
    RefreshInfo "$@"
    grep "EndeavourOS[-_]" "$datafile" | sed -E 's|.*">(En.*)</a.*|\1|'             # all downloads

    #echo2 "==> function $FUNCNAME()"
}

Usage() {
    cat <<EOF

Usage: $progname [options] [filename]

Options:
    --help, -h           This help.
    --edit-config        Edit the config file of $progname.
    --curl               Use curl as the helper app.
    --wget               Use wget as the helper app.

Tip1: Write a config file at $configfile2. It must contain variables
      URLBASE, ACCOUNT, PASSWORD, and optionally TARGETDIR with their proper values.
      Alternatively, variable HELLO can replace values "\$ACCOUNT:\$PASSWORD".
      Note that the config file will be created if it does not exist,
      and the values will be asked.
Tip2: Without parameters a selection of downloadable files is presented (using fzf).
Tip3: Bash command completion is supported, just press the TAB key after $progname and see.

EOF
}

AskValue() {
    local -r prompt="$1"
    local -r varname="$2"
    local -r defaultval="$3"
    local value=""

    # shellcheck disable=2162
    read -p "$prompt : " value >&2
    case "$value" in
        "") value="$defaultval" ;;
        [qQ]) exit 0 ;;
    esac
    [ "$value" ] || DIE "$varname needs a value!"
    [ "${value/$HOME/}" != "$value" ] && value=${value/$HOME/\$HOME}
    values+=("$varname=\"$value\"")
}

AskConfigValues() {
    echo2 "Config file $configfile2 not found, creating it by asking the required values."
    local values=()
    local dldir="$HOME/Downloads" dl=""
    [ -d "$dldir" ] && dl=" [${dldir/$HOME/\~}]"

    AskValue "Remote base dir URL"            URLBASE   ""
    AskValue "Remote account"                 ACCOUNT   ""
    AskValue "Remote password"                PASSWORD  ""
    AskValue "Local download folder$dl"       TARGETDIR "$dldir"
    AskValue "Downloader app (wget)"          APP       "wget"

    printf "%s\n" "${values[@]}" > "$configfile"
}

EditFile() {
    local app
    local apps=()
    case "$XDG_CURRENT_DESKTOP" in
        KDE) apps=(kde-open xdg-open exo-open) ;;
        *)   apps=(xdg-open exo-open kde-open) ;;
    esac
    apps+=(kate gedit gnome-text-editor pluma xed emacs)
    for app in "${apps[@]}" ; do
        [ -x /bin/"${app%% *}" ] && { $app "$@" 2>/dev/null ; return ; }
    done
    DIE "no app for editing a file!"
}
EditConfigFile() {
    INFO "Editing file $configfile."
    EditFile "$configfile"
}

DumpOptions() {
    if [ "$OPTS" ] ; then
        local o=${OPTS//:/}                # remove every ':'
        [ "${o::1}" = "/" ] || o="--$o"    # add leading '--' if first option is long
        o=${o//,/ --}                      # manage long options
        o=${o//\// -}                      # manage short options
        echo "$o"
    fi
}

Options() {
    # "Bind" long option to its short option. This is used in DumpOptions().
    # Short option starts with '/'.
    local OPTS="help/h,dump-options,show-files,edit-config,curl/c,wget/w"                  # syntax: "aaa/a,bbb:/b:,ccc:,ddd"

    # shellcheck disable=2155
    local sopts="$(echo "$OPTS" | sed -E 's|[^/]*/(.[:]*)[^/]*|\1|g')"
    # shellcheck disable=2155
    local lopts="$(echo "$OPTS" | sed -E 's|(/.[:]*)||g')"
    local opts

    opts="$(/bin/getopt -o="$sopts" --longoptions "$lopts" --name "$progname" -- "$@")" || exit 1
    eval set -- "$opts"

    while true ; do
        case "$1" in
            --)               shift;                                        break  ;;
            --dump-options)   DumpOptions;                                  exit 0 ;;
            --show-files)     ReadConfig; printf "%s\n" "$(ShowFiles -q)";  exit 0 ;;
            --edit-config)    EditConfigFile;                               exit 0 ;;
            --curl | -c)      getter=curl                                          ;;
            --wget | -w)      getter=wget                                          ;;
            --help | -h)      Usage;                                        exit 0 ;;
        esac
        shift
    done

    case "$1" in
        "") ;;
        *.iso.tar.gz | *.iso | *.iso.${sumtype}*)
            echo2 "Download folder: $TARGETDIR"
            FetchIsoFile "$1"
            exit
            ;;
        *)
            DIE "sorry, only *.iso.tar.gz files are supported." ;;
    esac
}

ReadConfig() {
    [ "$URLBASE" ] && return

    # config file sets important values
    [ -e "$configfile" ] || AskConfigValues
    # shellcheck disable=1090
    source "$configfile" || DIE "file $configfile2 has a problem"
    [ "$URLBASE" ]       || DIE2 "URLBASE has no value in $configfile2"
    if [ -z "$HELLO" ] ; then
        [ "$ACCOUNT" ]   || DIE2 "ACCOUNT has no value in $configfile2"
        [ "$PASSWORD" ]  || DIE2 "PASSWORD has no value in $configfile2"
    fi
    if [ "$HELLO" ] ; then
        ACCOUNT="${HELLO%:*}"
        PASSWORD="${HELLO#*:}"
    elif [ "$ACCOUNT$PASSWORD" ] ; then
        HELLO="$ACCOUNT:$PASSWORD"
    else
        DIE2 "HELLO or ACCOUNT/PASSWORD have no proper value in $configfile2"
    fi
    case "$APP" in
        curl | wget) getter="$APP" ;;
    esac
    case "$TARGETDIR" in
        "") DIE2 "TARGETDIR has no value in $configfile2" ;;
        "/") ;;
        *"/") TARGETDIR="${TARGETDIR:: -1}" ;;
    esac
    TARGETDIR="${TARGETDIR/\~/$HOME}"
    [ -d "$TARGETDIR" ] || DIE2 "'$TARGETDIR' does not exist or is not a folder"
    [ -w "$TARGETDIR" ] || DIE "cannot write to TARGETDIR ($TARGETDIR)"

    case "$getter" in
        curl | wget)
            [ -x /bin/$getter ] || DIE "$getter not installed" ;;
        *)
            DIE2 "value '$getter' for APP in $configfile2 is not supported. Use either 'wget' or 'curl'." ;;
    esac
}

Cleanup() {
    [ "$cleanup_files" ] && rm -f "${cleanup_files[@]}"
}

Main() {
    local -r progname=${0##*/}
    trap Cleanup SIGINT
    local die2=""
    local -r configfile="$HOME/.config/$progname.conf"
    local -r configfile2="${configfile/$HOME/\~}"

    if [ -z "$XDG_DOWNLOAD_DIR" ] && [ -e "$HOME/.config/user-dirs.dirs" ] ; then
        # shellcheck disable=1091
        source "$HOME/.config/user-dirs.dirs"
    fi
    local URLBASE=""
    local HELLO=""
    local ACCOUNT="" PASSWORD=""
    local APP=""
    local exitcode=0
    local file
    local datafile=/tmp/$progname.tmp   # remove this if new files should be available
    local getter=wget                   # curl or wget
    local urls=()
    local targets=()
    local cleanup_files=()
    # local sumfile=""
    local TARGETDIR="${XDG_DOWNLOAD_DIR:-~}"           # $HOME/Downloads or $HOME
    local sumtype=sha512                               # support only *.sha512* checksum files

    [ "$1" ] && Options "$@"
    ReadConfig

    local header
    local header_lines=(
        "Navigation keys:  Up/Down/PgUp/PgDn; TAB=Select, ENTER=Accept, ESC=Quit"
        "Other keys:       Ctrl-E = Edit config file"
        "                  Ctrl-H = Help"
        "Config file:      $configfile2"
        "Download folder:  $TARGETDIR"
        "Fetching app:     $getter"
        "Tip:              Names with trailing '/' are folders, navigate to them if needed."
    )
    printf -v header "%s\n" "${header_lines[@]}"

    local fzf=(
        fzf
        --exact +i --tac --multi
        --footer="$header" --footer-border=bold --footer-label=" SELECTING FILE(S) TO DOWNLOAD "
        --bind "ctrl-e:execute-silent(xdg-open '$configfile'),ctrl-h:execute-silent(RunInTerminal '$progname -h')"
    )
    local selected="" items=() items_orig=()
    local subdir=""
    while true ; do
        # keep the names of files, drop other info
        readarray -t items < <("$progname"-show-all-info "$subdir") || exitcode=$?
        case "$exitcode" in
            0) ;;
            130) exit 1 ;;   # interrupted
            *)
                # shellcheck disable=2155
                local string="$(curl-exit-code-to-string "$exitcode" "$APP")"
                if [ "$string" ] ; then
                    DIE "$APP: '$string'"
                else
                    DIE "Failed connecting to $URLBASE ($APP exit code $exitcode)."
                fi
                ;;
        esac
        readarray -t items_orig < <(printf "%s\n" "${items[@]}"      | awk '{print $1}' | sort)
        readarray -t items      < <(printf "%s\n" "${items_orig[@]}" | "${fzf[@]}")
        [ "${items[0]}" ] || exit 0                        # nothing selected

        for selected in "${items[@]}" ; do
            case "$selected" in
                *"/") case "$selected" in
                          "/") subdir=""
                               ;;
                          *)   case "$subdir" in
                                   "") subdir="${selected%/}" ;;
                                   *)  subdir="$subdir/${selected%/}" ;;
                               esac
                               ;;
                      esac
                      ;;
                "")   echo2 "Nothing selected."
                      exit 1
                      ;;
                *)
                    # Collect cleanup_files, urls, targets for the selected ISO
                    cleanup_files+=("$selected")
                    case "$subdir" in
                        "") urls+=("$URLBASE/$selected") ;;
                        *)  urls+=("$URLBASE/$subdir/$selected") ;;
                    esac
                    [ "$TARGETDIR" ] && local t_file="$TARGETDIR/$selected" || t_file="$PWD/$selected"
                    targets+=("$t_file")

                    # Check if an accompanying .sha512sum file exists on the remote server
                    if [[ "$selected" == *.iso ]] ; then
                        local sha_file="${selected}.$sumtype"       # local sha_file="${selected}.sha512sum"
                        # Verify if the sha512sum file is present in the original item list from the server
                        sha_file=$(printf "%s\n" "${items_orig[@]}" | grep -F "$sha_file")
                        if [ "$sha_file" ] ; then
                            cleanup_files+=("$sha_file")
                            case "$subdir" in
                                "") urls+=("$URLBASE/$sha_file") ;;
                                *)  urls+=("$URLBASE/$subdir/$sha_file") ;;
                            esac
                            [ "$TARGETDIR" ] && targets+=("$TARGETDIR/$sha_file") || targets+=("$PWD/$sha_file")
                        fi
                    fi
                    ;;
            esac
        done
        if [ "${urls[0]}" ] ; then
            FetchIsoFiles
            CheckTheSum
            exit 0
        fi
        urls=()
        cleanup_files=()
        targets=()
    done

    # Exit code:
    #    0       download OK
    #    else    failure, nothing downloaded
}

FetchIsoFiles() {
    local target
    local url
    local targetopt=""
    local ix reply

    for ((ix=0; ix < ${#urls[@]}; ix++)) ; do
        url="${urls[$ix]}"
        target="${targets[$ix]}"

        if [ -e "$target" ] ; then
            # shellcheck disable=2162
            read -p "${target} exists, download again (y/N)? " reply
            case "$reply" in
                "" | [Nn]*) continue ;;
            esac
        fi

        # Only prompt the user if it is NOT a checksum file
        if [[ "$target" != *."$sumtype" && "$target" != *."${sumtype}sum" ]]; then
            case "$(echo -e "Download to $target\nExit now" | fzf)" in
                "" | Exit*) exit 0 ;;
            esac
        fi

        # Notify the user about the start of the download
        echo2 "==> Downloading ${target##*/} ..."

        case "$getter" in
            curl)
                targetopt="-o$target"
                /bin/curl --fail -L "$targetopt" -u "$HELLO" "$url" || exitcode=$?
                [ $exitcode = 0 ] || DIE "downloading $url failed, exit code $exitcode from 'curl'" "$exitcode"
                ;;
            wget)
                targetopt="-O$target"
                /bin/wget -q "$targetopt" --show-progress --user "$ACCOUNT" --password "$PASSWORD" "$url" || exitcode=$?
                [ $exitcode = 0 ] || DIE "downloading $url failed, exit code $exitcode from 'wget'" "$exitcode"
                ;;
        esac

        # Notify the user about the successful completion of the download
        if [ $exitcode = 0 ]; then
            echo2 "==> Downloaded ${target##*/} successfully."
        fi
    done
}

FetchIsoFile() {                      # old implementation, not tested, still works??
    # Fetch the given ISO file.
    local file="$1"
    local subdir="$2"
    local url
    local targetopt=""
    local msg_head="Download"
    local msg_middle=""
    local msg_tail="Exit now"

    cleanup_files+=("$file")
    if printf "%s\n" "${items_orig[@]}" | grep "$file".sha512sum >/dev/null; then
        cleanup_files=("$file".sha512sum "${cleanup_files[@]}")
        msg_middle="[$file and its .sha512sum]"
    else
        msg_middle="[$file]"
    fi

    case "$(echo -e "$msg_head $msg_middle\n$msg_tail" | fzf)" in
        "" | Exit*) exit 0 ;;
    esac

    for file in "${cleanup_files[@]}" ; do
        case "$subdir" in
            "") url="$URLBASE/$file" ;;
            *)  url="$URLBASE/$subdir/$file" ;;
        esac
        [ "$TARGETDIR" ] && file="$TARGETDIR/$file" || file="$PWD/$file"
        echo2 "\n==> Downloading to $file ...\n"
        case "$getter" in
            curl)
                targetopt="-o$file"
                # targetopt+=" -m2000"
                /bin/curl --fail -L "$targetopt" -u "$HELLO" "$url" || exitcode=$?     #  --remote-name-all
                [ $exitcode = 0 ] || DIE "downloading $url failed, exit code $exitcode from 'curl'" "$exitcode"
                ;;
            wget)
                targetopt="-O$file"
                /bin/wget -q "$targetopt" --show-progress --user "$ACCOUNT" --password "$PASSWORD" "$url" || exitcode=$?
                [ $exitcode = 0 ] || DIE "downloading $url failed, exit code $exitcode from 'wget'" "$exitcode"
                ;;
        esac
    done
}

CheckTheSum() {
    local checksumfile isofile
    local ret=0
    local target_item

    # Automatically detect the downloaded checksum file from the targets array
    for target_item in "${targets[@]}"; do
        if [[ "$target_item" == *."$sumtype" || "$target_item" == *."${sumtype}sum" ]]; then
            checksumfile="$target_item"
            break
        fi
    done

    # If a checksum file was found, proceed with automatic verification
    if [ "$checksumfile" ] ; then
        if [ -e "$checksumfile" ] ; then
            isofile="${checksumfile%.*}"
            if [ -e "$isofile" ] ; then
                pushd "$TARGETDIR" >/dev/null || DIE "'pushd $TARGETDIR' failed"

                # Extract only the base filename for clean output and execution
                local check_target="${checksumfile##*/}"
                echo2 -n "==> Verifying integrity of ${isofile##*/} ... "

                if ${sumtype}sum -c "$check_target" &>/dev/null ; then
                    echo2 "OK"
                    rm -f "$check_target"
                    echo "==> Removed $check_target."
                else
                    errorline "FAIL (the ISO file or checksum file is invalid)"
                    ret=1
                fi
                popd >/dev/null || DIE "'popd' failed"
            else
                errorline "==> '$isofile' not found."
                ret=2
            fi
        else
            INFO "no checksum file $checksumfile --> not verifying the ISO."
            ret=2
        fi
    fi
    return $ret
}

Main "$@"
