#!/usr/bin/env bash
# Download a single version, a minor series (--all), or a major series (--allversion).
set -Eeuo pipefail

readonly ROOT_BASE="/mnt/lti-storage/MikroTik_Versions"
readonly BASE_URL="https://download.mikrotik.com/routeros"
readonly MAX_PARALLEL=28

usage() {
    printf 'Usage: %s VERSION [--all] [--windows] | MAJOR --allversion [--windows]\nExamples:\n  %s 6.49.23\n  %s 7.23 --all\n  %s 7 --allversion\n  %s 6 --allversion\n' \
        "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" "${0##*/}" >&2
}

if [[ $# -lt 1 || $# -gt 3 ]]; then usage; exit 2; fi
requested_version=$1
shift
mode=single
windows=false
for option in "$@"; do
    case $option in
        --all)
            if [[ $mode != single ]]; then usage; exit 2; fi
            mode=minor ;;
        --allversion)
            if [[ $mode != single ]]; then usage; exit 2; fi
            mode=major ;;
        --windows) windows=true ;;
        *) usage; exit 2 ;;
    esac
 done
case $mode in
    major)
        if ! [[ $requested_version =~ ^[67]$ ]]; then
            printf 'Error: --allversion requires 6 or 7.\n' >&2
            exit 2
        fi ;;
    minor)
        if ! [[ $requested_version =~ ^[67]\.[0-9]+$ ]]; then
            printf 'Error: --all requires a base version such as 7.23 or 6.49.\n' >&2
            exit 2
        fi ;;
    single)
        if ! [[ $requested_version =~ ^[67]\.[0-9]+(\.[0-9]+)?((beta|rc|test)[0-9]+|[._-][A-Za-z0-9]+)*$ ]]; then
            printf 'Error: invalid RouterOS 6.x or 7.x version: %s\n' "$requested_version" >&2
            exit 2
        fi ;;
esac
for command in aria2c curl; do
    if ! command -v "$command" >/dev/null 2>&1; then
        printf 'Error: %s is required. Install with: sudo apt install aria2 curl\n' "$command" >&2
        exit 1
    fi
done
if "$windows"; then
    if ! command -v gnome-terminal >/dev/null 2>&1; then
        printf 'Error: --windows requires gnome-terminal.\n' >&2
        exit 1
    fi
    if [[ -z ${DISPLAY:-} && -z ${WAYLAND_DISPLAY:-} ]]; then
        printf 'Error: --windows requires a graphical desktop session.\n' >&2
        exit 1
    fi
fi
readonly SCRIPT_PATH="$(realpath -- "${BASH_SOURCE[0]}")"
launched_count=0
major_version=${requested_version%%.*}
readonly ROOT_DIR="${ROOT_BASE}/routeros ${major_version}.x"
download_list=$(mktemp)
trap 'rm -f -- "$download_list"' EXIT

# Return 3 only for HTTP 404. Other verification errors stop the run.
download_version() {
    local version=$1 destination="${ROOT_DIR}/$1" verify_file http_code file local_file
    local missing_count=0 skipped_count=0
    local -a files
    if [[ $major_version == 6 ]]; then
        verify_file="routeros-mipsbe-${version}.npk"
    else
        verify_file="routeros-${version}-mipsbe.npk"
    fi
    printf '\nVerifying RouterOS %s exists...\n' "$version"
    if ! http_code=$(curl --ipv4 --silent --show-error --head --location \
        --max-time 20 --output /dev/null --write-out '%{http_code}' \
        "${BASE_URL}/${version}/${verify_file}"); then
        printf 'Error: could not verify RouterOS %s.\n' "$version" >&2
        return 2
    fi
    if [[ $http_code == 404 ]]; then
        printf 'RouterOS %s was not found. Directory was not created: %s\n' "$version" "$destination"
        return 3
    fi
    if ! [[ $http_code =~ ^2[0-9][0-9]$ ]]; then
        printf 'Error: verifying RouterOS %s returned HTTP %s.\n' "$version" "$http_code" >&2
        return 2
    fi
    if "$windows"; then
        # Each child runs in single-version mode with its own temporary list.
        if ! gnome-terminal --window --title="RouterOS ${version} downloads" -- \
            bash -c '
                bash "$1" "$2"
                status=$?
                printf "\nRouterOS %s download process exited with status %s.\n" "$2" "$status"
                exit "$status"
            ' download-window "$SCRIPT_PATH" "$version"; then
            printf 'Error: could not open download window for RouterOS %s.\n' "$version" >&2
            return 2
        fi
        ((launched_count += 1))
        printf 'Opened download window for RouterOS %s.\n' "$version"
        return 0
    fi
    mkdir -p -- "$destination" || return 2
    files=(
        "all_packages-arm-${version}.zip" "all_packages-arm64-${version}.zip"
        "all_packages-mipsbe-${version}.zip" "all_packages-mmips-${version}.zip"
        "all_packages-ppc-${version}.zip" "all_packages-smips-${version}.zip"
        "all_packages-tile-${version}.zip" "all_packages-x86-${version}.zip"
        "chr-${version}.img.zip" "chr-${version}.ova"
        "chr-${version}.vdi.zip" "chr-${version}.vmdk.zip"
        "netinstall-${version}.zip" "netinstall64-${version}.zip" "netinstall-${version}.tar.gz"
        "flashfig.exe" "btest.exe" "dude-install-${version}.exe" "mikrotik.mib"
    )
    if [[ $major_version == 6 ]]; then
        files+=("routeros-x86-${version}.npk" "routeros-arm-${version}.npk"
            "routeros-arm64-${version}.npk" "routeros-mipsbe-${version}.npk"
            "routeros-mmips-${version}.npk" "routeros-smips-${version}.npk"
            "routeros-tile-${version}.npk")
    else
        files+=("routeros-${version}.npk" "routeros-${version}-arm.npk"
            "routeros-${version}-arm64.npk" "routeros-${version}-mipsbe.npk"
            "routeros-${version}-mmips.npk" "routeros-${version}-ppc.npk"
            "routeros-${version}-smips.npk" "routeros-${version}-tile.npk"
            "mikrotik-${version}-arm64.iso")
    fi
    : > "$download_list" || return 2
    for file in "${files[@]}"; do
        local_file="${destination}/${file}"
        if [[ -s $local_file && ! -e ${local_file}.aria2 ]]; then
            printf 'Already exists, skipping: %s\n' "$file"
            ((skipped_count += 1))
            continue
        fi
        if [[ -e $local_file && ! -s $local_file ]]; then
            rm -f -- "$local_file" || return 2
        fi
        printf '%s/%s/%s\n' "$BASE_URL" "$version" "$file" >> "$download_list" || return 2
        ((missing_count += 1))
    done
    printf '\nRouterOS %s\nDestination: %s\nExisting files skipped: %s\nFiles to download: %s\n\n' \
        "$version" "$destination" "$skipped_count" "$missing_count"
    if [[ $missing_count -eq 0 ]]; then
        printf 'Everything in this download list already exists.\n'
        return 0
    fi
    if ! aria2c --dir="$destination" --input-file="$download_list" \
        --max-concurrent-downloads="$MAX_PARALLEL" --split=2 --min-split-size=1M \
        --max-connection-per-server=4 --continue=true --auto-file-renaming=false \
        --file-allocation=none --disable-ipv6=true --console-log-level=notice \
        --summary-interval=1 --timeout=30 --retry-wait=3 --max-tries=5; then
        printf 'Warning: some RouterOS %s downloads failed.\n' "$version" >&2
        return 1
    fi
    printf '\nCompleted missing RouterOS %s downloads.\n' "$version"
}

if [[ $mode == single ]]; then
    if download_version "$requested_version"; then exit 0; else exit 1; fi
fi

failures=0
# Sets series_found; download failures still count as an existing release.
download_series() {
    local base=$1 patch=0 consecutive_missing=0 version status
    local previous_missing='' last_missing=''
    series_found=false
    while ((consecutive_missing < 2)); do
        if ((patch == 0)); then version=$base; else version="${base}.${patch}"; fi
        status=0
        download_version "$version" || status=$?
        case $status in
            0) consecutive_missing=0; series_found=true ;;
            1) consecutive_missing=0; series_found=true; failures=1 ;;
            3)
                ((consecutive_missing += 1))
                previous_missing=$last_missing
                last_missing=$version ;;
            *) return 2 ;;
        esac
        ((patch += 1))
    done
    printf '\nStopped series %s after two consecutive missing versions: %s and %s.\n' \
        "$base" "$previous_missing" "$last_missing"
}

if [[ $mode == minor ]]; then
    download_series "$requested_version" || exit 1
else
    # Include .0 for older release series, then .1, .2, and onward.
    minor=0
    missing_series=0
    while ((missing_series < 2)); do
        download_series "${major_version}.${minor}" || exit 1
        if "$series_found"; then
            missing_series=0
        else
            ((missing_series += 1))
        fi
        ((minor += 1))
    done
    printf '\nStopped major series %s after two consecutive missing minor series: %s.%s and %s.%s.\n' \
        "$major_version" "$major_version" "$((minor - 2))" "$major_version" "$((minor - 1))"
fi
if "$windows"; then
    printf '\nScan finished. Opened %s download windows. Downloads continue in those windows; check each window for its result.\n' "$launched_count"
fi
exit "$failures"
