# This script sets the following parameters, which carry through to the
# rest of the program:
#
#     OPT_HARDLINK OPT_SUMS OPT_STAMP STAMPOPT_FILE (integers)
#     OPT_PHASE_{A,B,C,D,DD,E,F,G}                  (integers)
#     VER ISOCD_VER ARCH OLD TARGET ISOCD_TARGET MIRROR_PRIMARY
#     MAJOR_VER MINOR_VER INSTALLER_VER
#     DIST_CODENAME INSTALLER_R0_DATE
#     DIST DIST_BACKPORTS
#     LANGS (array)
#
# The script set this, too, which carries through:
#
#     OPT_BACKPORTS=1 (integer)
#
# However, the last can have a value other than 1 only if experimental options
# are enabled.
#
# Other things the script sets are only of local significance.  These can be
# ignored and, unless readonly, even overwritten by other scripts.
#
# The file is organized into these sections:
#
#     * USAGE MESSAGE
#     * COMMAND LINE
#     * OPTION CHECKING
#     * ARGUMENT PROCESSING

# -------------------------------------------------------------------------
# USAGE MESSAGE
# -------------------------------------------------------------------------
# usage: print_usage_text SINK
# SINK is 1 for stdout or 2 for stderr
readonly USAGE_TOPLINE=$(gettext\
 'MIRROR Release Including Backports, for Debian')
readonly USAGE_GENERAL=$(gettext 'general usage')
readonly USAGE_OPTIONS=$(gettext 'OPTIONS')
readonly USAGE_DEBVER=$(gettext 'DEBIAN-VER')
readonly USAGE_ARGS=$(gettext 'ARGUMENTS')
readonly USAGE_FORMAJV=$(gettext 'usage for %s')
readonly USAGE_FOR10=$(printf "$USAGE_FORMAJV" "$DIST10")
readonly USAGE_FOR11=$(printf "$USAGE_FORMAJV" "$DIST11")
readonly USAGE_FOR12=$(printf "$USAGE_FORMAJV" "$DIST12")
readonly USAGE_FOR13=$(printf "$USAGE_FORMAJV" "$DIST13")
readonly USAGE_FOR10_11_OR_12=$(printf "$(gettext 'usage for %s, %s or %s')"\
 "$DIST10" "$DIST11" "$DIST12")
readonly USAGE_CDREV=$(gettext 'CD-REV')
readonly USAGE_ARCH=$(gettext 'ARCH')
readonly USAGE_OLD=$(gettext 'OLD')
readonly USAGE_TARGET=$(gettext 'TARGET')
readonly USAGE_ISO=$(gettext 'ISO')
readonly USAGE_MIRROR=$(gettext 'MIRROR')
readonly USAGE_LANGS=$(gettext 'LANGS')
readonly USAGE_FILE=$(gettext 'FILE')
readonly USAGE_NOIMPL=$(gettext 'not yet implemented')
readonly USAGE_OPTL=$(gettext\
 'save local disk space by hard-linking files from OLD to TARGET')
readonly USAGE_OPTS=$(gettext\
 'change nothing, but print checksums from control')
readonly USAGE_OPTHELP=$(gettext 'print this help message')
readonly USAGE_OPTDISABLE=$(gettext 'disable further option processing')
readonly USAGE_STAMP=$(gettext\
 'stamp the named file with the referenced snapshot'"'"'s date and time')
readonly USAGE_ARG1=$(gettext 'DEBIAN-VER Debian version,  e.g.')
readonly USAGE_ARG2=$(gettext 'CD-REV     iso cd revision, e.g.')
readonly USAGE_ARG3=$(gettext 'ARCH       architecture,    e.g.')
readonly USAGE_ARG4=$(gettext 'OLD        old target dir,  e.g.')
readonly USAGE_ARG5=$(gettext 'TARGET     new target dir,  e.g.')
readonly USAGE_ARG6=$(gettext 'ISO        iso target dir,  e.g.')
readonly USAGE_ARG7=$(gettext 'MIRROR     mirror,          e.g.')
readonly USAGE_ARG8=$(gettext 'LANGS      languages,       e.g.')
readonly USAGE_ARG2a=$(printf "$(gettext 'for cd version %s')"\
 "11.$DOC_NEW_MINOR_VER.0")
readonly USAGE_ARG4a=$(gettext 'enter /dev/null if none')
readonly USAGE_ARG8a=$(gettext 'enter @ for all languages')
readonly UFN1='Debian'"'"'s snapshot service has limited capacity'
readonly UFN2='and must therefore ban abusers;'
readonly UFN3='but you can mitigate the load on snapshot.debian.org'
readonly UFN4='(and also on MIRROR) by specifying a former TARGET,'
readonly UFN5='rather than /dev/null, as OLD.'
readonly UFN6='For MIRROR, you may choose from the list at [%s]'
readonly UFN7='a single mirror that serves both %s and %s.'
readonly USAGE_FNa=$(gettext "$UFN1 $UFN2 $UFN3 $UFN4 $UFN5")
readonly USAGE_FNb=$(printf "$(gettext "$UFN6 $UFN7")"\
 "https://www.debian.org/mirror/" "debian/" "debian-cd/")
function print_usage_text {
cat <<END >&$1 --
$NAME - $USAGE_TOPLINE
$USAGE_GENERAL: $NAME [$USAGE_OPTIONS] $USAGE_DEBVER $USAGE_ARGS
$USAGE_FOR10_11_OR_12:
END
readonly USAGE_ARGTEXT=".N $USAGE_CDREV $USAGE_ARCH\
 $USAGE_OLD $USAGE_TARGET $USAGE_ISO $USAGE_MIRROR [$USAGE_LANGS...]"
if (($ENABLE_EXPERIMENTAL)); then
cat <<END >&$1 --
  $NAME [-lsiIB?-] [-p $USAGE_FILE] 10$USAGE_ARGTEXT
  $NAME [-lsiIB?-] [-p $USAGE_FILE] 11$USAGE_ARGTEXT
  $NAME [-lsiIB?-] [-p $USAGE_FILE] 12$USAGE_ARGTEXT
END
else
cat <<END >&$1 --
  $NAME [-ls?-] [-p $USAGE_FILE] 10$USAGE_ARGTEXT
  $NAME [-ls?-] [-p $USAGE_FILE] 11$USAGE_ARGTEXT
  $NAME [-ls?-] [-p $USAGE_FILE] 12$USAGE_ARGTEXT
END
fi
cat <<END >&$1 --
$USAGE_FOR13: ($USAGE_NOIMPL)
$USAGE_OPTIONS
    -l $USAGE_OPTL
    -s $USAGE_OPTS
END
(($ENABLE_EXPERIMENTAL)) && cat <<END >&$1 --
    -i (experimental) fetch only iso cd boot files
    -I (experimental) skip iso cd boot files
    -B (experimental) omit backports
END
cat <<END >&$1 --
    -? $USAGE_OPTHELP
    -- $USAGE_OPTDISABLE
    -p $USAGE_FILE $USAGE_STAMP
$USAGE_ARG1 11.$DOC_NEW_MINOR_VER
$USAGE_ARG2 0 ($USAGE_ARG2a)
$USAGE_ARG3 amd64
$USAGE_ARG4 ~/debian-11.$DOC_OLD_MINOR_VER ($USAGE_ARG4a)
$USAGE_ARG5 ~/debian-11.$DOC_NEW_MINOR_VER
$USAGE_ARG6 ~/debian-cd-11.$DOC_NEW_MINOR_VER.0
$USAGE_ARG7 mirror.example.org
$USAGE_ARG8 en fr de de_DE ($USAGE_ARG8a)
$(fold -s -w79 -- <<<"$USAGE_FNa  $USAGE_FNb" -)
END
true
}
readonly -f print_usage_text

# -------------------------------------------------------------------------
# COMMAND LINE
# -------------------------------------------------------------------------
# Read and process the command line.
#
# (The program begins to grow almost a little too large for the
# following, OPT-controlled architecture to remain entirely comfortable.
# The architecture might want redesign at some future date.)
declare -i OPT_HARDLINK=0
declare -i OPT_SUMS=0
declare -i OPT_PHASE_A=1   # check mirror availability
declare -i OPT_PHASE_B=1   # determine the backports date
declare -i OPT_PHASE_C=1   # fetch dist-root control
declare -i OPT_PHASE_D=1   # fetch per-area control
declare -i OPT_PHASE_DD=1  # stamp the release time
declare -i OPT_PHASE_E=1   # fetch pool files
declare -i OPT_PHASE_F=1   # fetch iso cd boot files
declare -i OPT_PHASE_G=1   # set permissions and stamp directories
declare -i OPT_BACKPORTS=1 # fetch backports (if other options allow it)
declare -i OPT_STAMP=0     # return the snapshot's date via a timestamp
declare STAMPOPT_FILE=''
(($#)) && [ "$1" = '--help' ] && { print_usage_text 1; exit 0; }
(($#)) && [ "$1" = '--version' ] && {
# English only: not to be translated.
cat <<END
$NAME ($PROJECT_NAME $NAME) $VERSION
Copyright (C) 2021 Thaddeus H. Black
License GPLv2: GNU GPL version 2 [/usr/share/common-licenses/GPL-2].
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
END
exit 0
}
declare AVAILABLE_OPTS=':ls-p:';\
 (($ENABLE_EXPERIMENTAL)) && AVAILABLE_OPTS=':lsiIB-p:';
readonly AVAILABLE_OPTS
declare -i CONTINUE_PROCESSING_OPTS=1 LAST_OPTIND
declare OPTLETTER
while true; do
    LAST_OPTIND=$OPTIND
    getopts "$AVAILABLE_OPTS" OPTLETTER || break
    case "$OPTLETTER" in
        l) OPT_HARDLINK=1;;
        s) OPT_SUMS=1; OPT_PHASE_A=0; OPT_PHASE_B=0; OPT_PHASE_C=0;\
            OPT_PHASE_DD=0; OPT_PHASE_F=0; OPT_PHASE_G=0;;
        i) OPT_PHASE_B=0; OPT_PHASE_C=0; OPT_PHASE_D=0;\
            OPT_PHASE_DD=0; OPT_PHASE_E=0;;
        I) OPT_PHASE_F=0;;
        B) OPT_BACKPORTS=0; OPT_PHASE_B=0;;
        -) CONTINUE_PROCESSING_OPTS=0;;
        p) OPT_STAMP=1; STAMPOPT_FILE=$(realpath -- "$OPTARG");;
        *)
            SINK=1; [ "$OPTARG" = '?' ] || SINK=2
            print_usage_text "$SINK"
            [ "$OPTARG" = '?' ] && exit 0; exit 1
        ;;
    esac
    (($CONTINUE_PROCESSING_OPTS || $LAST_OPTIND == $OPTIND)) || break;
done
readonly OPT_HARDLINK OPT_SUMS
readonly OPT_PHASE_A OPT_PHASE_B OPT_PHASE_C OPT_PHASE_D OPT_PHASE_DD
readonly OPT_PHASE_E OPT_PHASE_F OPT_PHASE_G
readonly OPT_BACKPORTS OPT_STAMP STAMPOPT_FILE
shift $(($OPTIND - 1))

# -------------------------------------------------------------------------
# OPTION CHECKING
# -------------------------------------------------------------------------
readonly MSG_OPTPS1='unable to execute the %s option because,'
readonly MSG_OPTPS2='when the %s option is in effect,'
readonly MSG_OPTPS3='the program does not connect to the snapshot service'
readonly MSG_OPTLS1='the %s option does nothing'
readonly MSG_OPTLS2='while the %s option is in effect'
readonly MSG_OPTPS="$(printf "$(gettext\
 "$MSG_OPTPS1 $MSG_OPTPS2 $MSG_OPTPS3")" '-p' '-s')"
readonly MSG_OPTLS="$(printf "$(gettext\
 "$MSG_OPTLS1 $MSG_OPTLS2")" '-l' '-s')"
(($OPT_STAMP && $OPT_SUMS)) && die "$MSG_OPTPS"
(($OPT_HARDLINK && $OPT_SUMS)) && warn "$MSG_OPTLS"

# -------------------------------------------------------------------------
# ARGUMENT PROCESSING
# -------------------------------------------------------------------------
(($# >= 7)) || { print_usage_text 2; exit 1; }
# As the program stands, the next two lines could be combined, but for
# future maintainability, to leave them separated seems prudent. (It is not
# impossible that $1, $2, $3 and $7 might be treated differently from one
# another in a future version of the program.)
forbid_spaces "$1" "$2" "$3" "$7"
require_names_to_be_acceptable "$1" "$2" "$3" "$7"
require_filepaths_to_be_acceptable "$4" "$5" "$6"
readonly VER="$1" ISOCD_VER="$1.$2" ARCH="$3" MIRROR_PRIMARY="$7"
declare OLD TARGET ISOCD_TARGET
OLD=$(realpath -e -- "$4")
TARGET=$(realpath -- "$5")
ISOCD_TARGET=$(realpath -- "$6")
readonly OLD TARGET ISOCD_TARGET
require_filepaths_to_be_acceptable "$OLD" "$TARGET" "$ISOCD_TARGET"
shift 7
require_names_to_be_acceptable "$@"
require_langs_to_be_acceptable "$@"
readonly MSG_OT='%s must not be both OLD and TARGET'
readonly MSG_OI='%s must not be both OLD and ISO'
readonly MSG_IT='ISO must be distinct from TARGET'
readonly MSG_CT1='%s already exists; if you do not want it,'
readonly MSG_CT2='then delete it before invoking this program'
readonly MSG_CT="$MSG_CT1 $MSG_CT2"
(($OPT_PHASE_C)) && [ "$TARGET" = "$OLD" ] && {
    die "$(printf "$(gettext "$MSG_OT")" "$TARGET")"
}
(($OPT_PHASE_F)) && [ "$ISOCD_TARGET" = "$OLD" ] && {
    die "$(printf "$(gettext "$MSG_OI")" "$ISOCD_TARGET")"
}
(($OPT_PHASE_C && $OPT_PHASE_F)) && [ "$ISOCD_TARGET" = "$TARGET" ] && {
    die "$(gettext "$MSG_IT")"
}
(($OPT_PHASE_C)) && [ -e "$TARGET" ] && {
    die "$(printf "$(gettext "$MSG_CT")" "$TARGET")"
}
(($OPT_PHASE_F)) && [ -e "$ISOCD_TARGET" ] && {
    die "$(printf "$(gettext "$MSG_CT")" "$ISOCD_TARGET")"
}
(($#)) || warn "$(gettext 'no languages were specified')"
declare -ar LANGS=("$@")
readonly MSG_MAJV='Debian version %s is not supported'
readonly MAJOR_VER=${VER%%.*}
readonly MINOR_VER=${VER#*.}
declare DIST_CODENAME
case "$MAJOR_VER" in
    10)
        DIST_CODENAME=$DIST10
    ;;
    11)
        DIST_CODENAME=$DIST11
    ;;
    12)
        DIST_CODENAME=$DIST12
    ;;
    # Future major versions of Debian can be added here.
    *) die "$(printf "$(gettext "$MSG_MAJV")" "$MAJOR_VER")"
esac
readonly DIST_CODENAME
declare DIST="$PROJECT_NAME$VER"; (($OPT_SUMS)) && DIST=$DIST_CODENAME
readonly DIST
readonly DIST_BACKPORTS="$DIST_CODENAME-backports"
readonly INSTALLER_VER="deb${MAJOR_VER}u${MINOR_VER}"
readonly IR0D_INPATH="dists/$DIST/main/installer-$ARCH"
readonly INSTALLER_R0_DATE=$(
    {
        if (($OPT_SUMS)); then
            "$FINDX" ''\
            '-mindepth 1 -maxdepth 1 -type d -printf '"'"'%P\n'"'"\
            "$TARGET/$IR0D_INPATH"
        else
            rsync -n --no-motd --\
            "$MIRROR_PRIMARY::$ARCHIVE_NAME/$IR0D_INPATH/*"
        fi
    }\
    | sed -rn 's/^(.*\s)?(\S+)\s*$/\2/;/^[[:digit:]]{8}$/p'\
    | sort | uniq | tail -n1
)
sed -rn <<<"$INSTALLER_R0_DATE" '/^[[:digit:]]{8}$/!q1'\
 || die "$(gettext 'cannot determine the installer'"'"'s date')"
true

