#!/bin/bash -

# run as root 

# Copyright (c) 2025 Ernst Reissner. All rights reserved.
# This software is provided for internal use by SUZ Donauwoerth only.
# Unauthorized copying, distribution, or commercial use, 
# as a whole or in parts outside SUZ Donauwoerth is strictly prohibited 
# without the written permission of the author.

###############################################################################
# instTexlive.sh — Install or update a TeX Live yearly installation
#
# SYNOPSIS
#   instTexlive.sh <YEAR> 
#
# DESCRIPTION
#   Installs TeX Live of release given by <YEAR> where year is 2008 or later 
#   because this script presupposed `tlmgr` which has been introduced in that year. 
#   Note that windows like operating systems are supported since 2019 whereas unixoid system go back until 2015. 
#   In 2014 and earlier, the installer can be downloaded but the installer cannot load the database, at least if using utah server. 
#   It is possible to extend this script to earlier versions. 
#   The earliest versions of texlive seem to date back until 1996. 
#   Installation is based on the following files which are expected in the same folder 
#   - instTexliverc: a configuration file which is sourced and provides 
#     - ROOT, the root of the texlive installations 
#     - SERVER_HIST, the server for historical releases 
#     - SERVER_CURR, the server for the current release 
#     - get_binaries, the plattforms for which texlive is to be installed, 
#       where a platform is roughly the combination of hardware architecture and operating system 
#     - SERVER_PATCH, the server for single file patches 
#     - SERVER_TDS_PATCH, the server for tds paches, i.e. files `package.tds.gz` 
#     - SCHEME, defining the size of the base installation 
#     - PAPER, the paper size if not specified otherwise in latex files 
#     - INST_DOCFILES, whether doc files shall be installed 
#     - INST_SRCFILES, whether src files shall be installed 
#     - CONF_SHELL_ESCAPE_RESTRICTED, whether shell escape is restricted. 
#     A more detailled documentation is found in instTexliverc directly. 
#   - texlive<YEAR><SCHEME>.pkg: a text file which lists the packages to be installed 
#     in addition to the scheme $SCHEME. 
#     The file may be empty also. 
#   Both, `instTexliverc` and `texlive<YEAR><SCHEME>.pkg` shall be adapted to the needs of the user. 
#   As this script downloads from the internet, it requires the according access. 
#   This script is intended to be run on a bash shell 
#   which presupposes a unixoid underlying operating system or an emulation e.g. on windows. 
#   Note that this has nothing to do with the platforms for which the installation is performed. 
#   So this script is a cross platform installer. 
#   Although written for bash, this script requires the shell to have access on Perl, 
#   because it invokes the perl scripts `install-tl` and `tlmgr`. 
#   The required version is not totally clear and may depend on <YEAR>, just try. 
#
#   Some explanation referring to the year: 
#   TeX Live is released once per year. Each yearly release remains active from
#   some date in spring until some date in spring of the next year, 
#   after which it is frozen and archived. 
#   Each release is identified by the <YEAR> of publishing given with 4 digits. 
#   Note that this script is tested back to 2017 and reaches even further back adapting the SERVER_HIST using ftp. 
#   Various releases can coexist, each in a folder given by 
#
#       /usr/local/texlive/<YEAR>
#
#   By default, executables of texlive are collected in 
#
#       /usr/local/texlive/<YEAR>/bin
#
#   Unlike LaTeX classes and styles, executables may depend on the so called texlive platform. 
#   Thus for each platform supported `bin` has an according folder holding the binaries of that platform 
#   This script must be invoked as root which is checked if possible. 
#   It installs, updates and patches a TeX Live installation for a given <YEAR>. 


#   Starting with the list https://www.tug.org/texlive/distro.html of distributions (which need not be complete), 
#   we can extract the operating systems this script must work for. 
#   It is Linux, Darwin (mac os), FreeBSD, NetBSD, OpenBSD, DragonFly (also a kind of BSD), Windows (cygwin), SunOS. 
#   There is a chance that it works on further unixoid operating systems. 
#   Although for windows we have mentioned cygwin only, 
#   the underlying OS is windows and this script will work on any environments providing a bash like shell, 
#   be it cygwin, mysys or mingw. 
#   Of course the installed TeX Live will work also in any case. 
#   This script and the installed TeX Live distribution will work with WSL (windows subsystem for linux) 
#   because it behaves exactly like Linux and is in particular also identified as Linux. 
#
#   This script presupposes bash shell in particular understanding unix style paths like git bash. 
#   It is not written for CMD or PowerShell. 
#
# PACKAGE LIST
#   The packages to be installed beyond the scheme given in instTexliverc 
#   is listed in texlive<YEAR><SCHEME>.pkg
#   Format:
#       - One or more package names per line.
#       - Inline comments allowed:  <pkg> <pkg>  # comment
#       - Full-line comments allowed:  # ...
#       - Blank lines allowed.
#       - No line continuation (“\”) supported.
#
#       The file is filtered using:
#           sed 's/#.*//' | sed '/^\s*$/d'
#   Further documentation is found in the comment lines of texlive<YEAR><SCHEME>.pkg
#
# REQUIREMENTS
#   - linux, unix, WSL, mac.
#   - Must be executed as root.
#   - Network access required for installation and updates.
#   - wget, tar, unzip must be available.
#
# ACTIONS PERFORMED
#   - Installation from CTAN mirrors (tlnet-final/<YEAR>).
#   - tlmgr configuration (repository auto-selection).
#   - tlmgr update --self and update --all.
#   - Installation of user-defined packages.
#   - Application of custom server patches into texmf-dist.
#   - Application of tds patches into texmf-dist.
#   - Regeneration of TeX Live filename database (texhash).
#   - PATH cleanup and activation of the selected version.
#
# EXIT CODES
#   0  success
#   1  problem at start 
#      - running not as root, 
#      - invalid arguments, 
#      - revision year not found 
#      - missing tools (perl, wget, unzip, awk, cut, head, tar) 
#        or degraded ones (wget without option --spider)
#      - invalid setting, e.g. PAPER neither a4 nor letter EBD: rework: not at start 
#   2  failure downloading the installer 
#   3  installation error 
#      - unexpected folder structure, 
#      - cannot enter installation folder, 
#      - installer itself failed
#   4  architecture/platform failrure 
#      - current platform not detected 
#      - current platform erroneously identified
#      - current platform not supported 
#      - current platform not selected (for installing)
#      - bin folder not accessible/not supported 
#   5  missing config file 'instTexliverc` or package list 'texlive<YEAR><SCHEME>.pkg
#
# EXAMPLES
#   Install or update TeX Live 2025:
#       sudo ./instTexlive.sh 2025 
# SEE ALSO 
#   Documentation of `tlmgr`: https://tug.org/texlive/doc/tlmgr.html
#   Sibling `switchTexlive.sh`
#
# AUTHOR
#   Your Name <ernst.reissner@simuline.eu>
#
###############################################################################

# TBD: documentation of paches 

# messaging 
error() { printf "\e[91m✖ ERROR: %s\e[0m\n" "$1" >&2; }
warn()  { printf "\e[93m⚠ WARN: %s\e[0m\n" "$1"; }
info()  { printf "\e[94mℹ INFO: %s\e[0m\n" "$1"; }
succ()  { printf "\e[92m✔ DONE: %s\e[0m\n" "$1"; }

check_windows_tools() {
  local missing=0
  command -v unzip >/dev/null 2>&1 || { info "Missing 'unzip'."; missing=1; }
  command -v awk   >/dev/null 2>&1 || { info "Missing 'awk'.";   missing=1; }
  command -v cut   >/dev/null 2>&1 || { info "Missing 'cut'.";   missing=1; }
  command -v head  >/dev/null 2>&1 || { info "Missing 'head'.";  missing=1; }

  return $missing
}

check_unixoid_tools() {
  local missing=0
  command -v tar  >/dev/null 2>&1 || { info "Missing 'tar'."; missing=1; }
  return $missing
}

info "Analyzing the environment..."

((BASH_VERSINFO[0] > 4)) || ((BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] > 2)) \
  || {
    error "Found bash version ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]} but required 4.3."
    # name references local -n since 4.3
    exit 1
  }

# TBD: hide the following in a function 
# internal: ARCH, OS, 
# output: isWindowsShell, INST_TL_FILE, TL_PLATFORM 
# we could even reduce: INST_TL_FILE depends on isWindowsShell

# the architecture for which to install (the current one)
ARCH=$(uname -m)

# preliminary setup OS (operating system for unix, shell for windows)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
# This is really the OS for unixoid OS, 
# whereas for windows it is the environment 
# except for WSL (windows subsystem for linux) it is practically linux. 
# For some installed TeX Live, `tlmgr platform list` unveils also the operating systems supported. 
# These are listed below and are tied to so called 'plattform', a TeX Live specific term. 
# Typically, the platform has the form $ARCH-$OS, where $ARCH is the architecture. 
#
# - 'freebsd', 'openbsd', 'netbsd' are currently the supported bsd unix. 
#   For them the plattform ist just `$ARCH-$OS`
# - `sunos` signifies solaris and the platform is `$ARCH-solaris`. 
# - 'linux', for all linux derivates but also for WSL (windows subsystem for linux). 
#   If the linux uses the traditional `glibc`, the platform is `$ARCH-linux`, 
#   but if `musl` is used instead, the plattform is `$ARCH-linuxmusl`. 
# - 'darwin' for mac. 
#   Here, we must distinguish the older mac with $ARCH=x86_64 and the newer one with $ARCH=arm64. 
#   For the older mac, the platform is `x86_64` and for the newer one it is `universal-darwin` 
#   not mentioning the architecture. 
# - 'mingw32_nt-*' or 'mingw64_nt-*' for git shell and 'msys_nt-*' for msys (minimal system). 
#   For these the platform is just `windows` and so is the operating system. 
# - `cygwin*`: Here the operating system is windows but the platform is `$ARCH-cygwin`
# - for all the rest we assume it is a unixoid operating system and the platform is `$ARCH-$OS`. 
#   We issue a warning. 



# We do not distinguish between the two supported shells mingw and mysys. 
# Note that for unknown OS, we assume unixoid system 

case "$OS" in
  mingw*|msys*|cygwin*)
    isWindowsShell=true
    INST_TL_FILE="install-tl.zip"
    # check whether critical apps with the used options are available 
    check_windows_tools || { error "Missing tool(s). Aborting."; exit 1; }
    case "$OS" in
      cygwin*)
        TL_PLATFORM="$ARCH-$OS"
        # currently only for $ARCH-x86_64
        ;;
      *)
        TL_PLATFORM="windows"
        ;;
    esac
    OS=windows
   ;;
  *)
    isWindowsShell=false
    INST_TL_FILE="install-tl-unx.tar.gz"
    case "$OS" in
      freebsd*|openbsd*|netbsd*)
        # kind of bsd 
        TL_PLATFORM="$ARCH-$OS"
        ;;
      sunos*)
        TL_PLATFORM="$ARCH-solaris"
        ;;
      darwin*)
        if [ "$ARCH" = "x86_64" ]; then
            TL_PLATFORM="$ARCH-darwinlegacy"
        else
            TL_PLATFORM="universal-darwin"
        fi
        ;;
      linux*)
        # 
        TL_PLATFORM="$ARCH-$OS"
        if ldd --version 2>&1 | grep -q musl; then
          TL_PLATFORM="${TL_PLATFORM}musl"
        fi
        ;;
      *)
        # Here, we may have some exotic unix or something totally different. 
        # Nevertheless, it is better to try with linux installer than with windows one 
        warn "OS ($OS) not explicitly supported; trying with installer for linux and platform '$ARCH-$OS'; report if works."
        TL_PLATFORM="$ARCH-$OS"
        ;;
    esac
    check_unixoid_tools || { error "Missing tool(s). Aborting."; exit 1; }
    ;;
esac

# TBD: if this is going to be reliable, the code before is partially superfluous. 

# END OF FUNCTION 


# ensuring correct usage 
# TBD> Root, this may be weakened. 
# maybe add root to group texadmin 
# what about windows 
if [ "$isWindowsShell" = true ] || [ -z "${EUID+x}" ]; then
    warn "Root check skipped: hope you run as root or via an elevated shell."
else
    [ "$EUID" -eq 0 ] || { error "Please run as root or via sudo. Aborting."; exit 1; }
fi

[ "$#" -eq 1 ] || { error "Usage: $0 <year in 4 digits>. Aborting."; exit 1; }
YEAR="$1"
[[ "$YEAR" =~ ^[0-9]{4}$ ]] || { error "Year must be exactly 4 digits. Aborting."; exit 1; }

# folder for this script, config file instTexliverc and texlive<YEAR><SCHEME>.pkg
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

CONFIG_FILE="$SCRIPT_DIR/instTexliverc"
[ -f "$CONFIG_FILE" ] \
  || { error "Required config file $CONFIG_FILE not found. Aborting."; exit 5; }
source "$CONFIG_FILE"

# check of tools required for all platforms is deferred because we need a server 
command -v perl >/dev/null 2>&1 \
  || { error "Perl is required to run the TeX Live installer. Aborting."; exit 1; }
command -v wget  >/dev/null 2>&1 || { error "Missing 'wget'.";  exit 1; }
# Verify wget is executable and supports help
if ! wget --help >/dev/null 2>&1; then
  warn "wget --help is not available, skipping --spider check."
  warn "If wget --spider is not available, it erroneously errors 'revision not found on server'."
else
  # Now perform the spider check
  wget --help 2>&1 | grep -q "\--spider" \
    || { error "Required 'wget --spider' not supported."; exit 1; }
fi

ERRORS_WGET=(
# Excerpt from wget manual page: 
# Meaning                                     Code Explanation 
  "No problems occurred."                     # 0
  "Generic error code."                       # 1
  "Parse error."                              # 2  e.g., error in command-line options
  "File I/O error."                           # 3
  "Network failure."                          # 4
  "SSL/TLS verification failure."             # 5
  "Username/password authentication failure." # 6
  "Protocol errors."                          # 7
  "Server issued an error response."          # 8 e.g., 404 Not Found or 500 Internal Server Error
)

ERRORS_TAR=(
# Excerpt from tar manual page:  
# Meaning                                     Code 
  "Successful termination."                   # 0
  "Some files differ."                        # 1
# If tar was invoked with the --compare (--diff, -d) command line option, this means that some files in the archive differ from their
# disk counterparts.  If tar was given one of the --create, --append or --update options, this exit code means that some files were  changed  while  being
# archived and so the resulting archive does not contain the exact copy of the file set.
  "Fatal error."                              # 2
#  This means that some fatal, unrecoverable error occurred.
)


# example: errorMsg "wget" $? ERRORS_WGET
errorMsg() {
  local exec="$1"
  local code="$2"
  local -n codes="$3"
  error "executing $exec yields returns ${codes[$code]:-unknown code $code}."
}

ERRORS_UNZIP=(
  [0]="no errors or warnings."
  [1]="success despite one or more warning errors."
  #[1]="one or more warning errors were encountered, but processing completed successfully anyway."
  [2]="generic error in the zipfile format."
  [3]="severe error in the zipfile format."
  [4]="unzip was unable to allocate memory for one or more buffers during program initialization."
  [5]="unzip was unable to allocate memory or unable to obtain a tty to read the decryption password(s)."
  [6]="unzip was unable to allocate memory during decompression to disk."
  [7]="unzip was unable to allocate memory during in-memory decompression."
  # [8] is currently not used
  [9]="specified zipfiles not found."
  [10]="invalid options on the command line."
  [11]="no matching files found."
  [50]="the disk full during extraction."
  [51]="ZIP archive ended prematurely."
  [80]="user aborted."
  [81]="testing or extraction of one or more files failed (compression or decryption methods unsupported."
  [82]="no files were found due to bad decryption password(s)."
)

# tlmgr: code analysis: 
# $F_OK = 0 (kein Bit gesetzt)
# $F_WARNING = 1 (Bit 0)
# $F_ERROR = 2 (Bit 1)
# $F_NOPOSTACTION = 4 (Bit 2)

#return codes of texlive? 


errorMsgTlmgr() {
  local code="$1"
  local F_WARNING=1
  local F_ERROR=2
  local F_NOPOSTACTION=4

  (( (code & F_ERROR) == 0 ))        || warn "tlmgr raised an error"
  (( (code & F_WARNING) == 0 ))      || warn "tlmgr raised a warning"
  (( (code & F_NOPOSTACTION) == 0 )) || warn "tlmgr skipped postactions"

  local known_bits=$(( F_WARNING | F_ERROR | F_NOPOSTACTION ))
  (( (code & ~known_bits) == 0 ))    || warn "tlmgr with unexpected bits set in return code"
}

info "Analyzing the server..."

# Setup the folder to install and relevant subfolders 
# folder to install texlive of the given year 
INSTALL_DIR="$ROOT/$YEAR"
# the subfolder of the installation with the binaries for the current platform  
BIN_DIR="$INSTALL_DIR/bin/$TL_PLATFORM"


# find out whether the revision $YEAR exists, 
# and if so whether it is frozen already which is writen into 
# $IS_FROZEN: 0 means true, else false 
# TBD: change that 
wget --spider --quiet "$SERVER_HIST/$YEAR/" 2>/dev/null || {
    errorMsg "wget" $? ERRORS_WGET
    error "Revision $YEAR not found on server by wget. Aborting."; exit 1
  }
info "Found revision $YEAR on server."
wget --spider --quiet "$SERVER_HIST/$YEAR/tlnet-final/" 2>/dev/null
IS_FROZEN=$?
if [ "$IS_FROZEN" -eq 0 ]; then
  info "Revision $YEAR on server is frozen."
  repo="$SERVER_HIST/$YEAR/tlnet-final"
else
  info "Revision $YEAR on server is live."
  repo="$SERVER_CURR"
fi

# may correct $PL_PLATFOM and as a result of this $BON_DIR
# may also exit the script 
reinstallByNeed() {
if [ ! -d "$BIN_DIR" ]; then
  # Here, the revision $YEAR is not yet installed, 
  # at least not for the current platform $TL_PLATFORM 
  if [ -d "$INSTALL_DIR" ]; then
    # Here, the revision $YEAR is principially installed, 
    # it is only the platform which is missing. 
    info "Reinstalling existing release $YEAR for platform $TL_PLATFORM..."
  else
    # Here, the revision $YEAR is not yet installed at all 
    info "Installing texlive $YEAR for platform $TL_PLATFORM..."
  fi

  # To ensure consistency, eliminate $INST_TL_FILE and download anew 
  rm -f "$INST_TL_FILE"
  # try URL for life release 
  info "Try downloading installer..."
  if [ "$IS_FROZEN" -eq 0 ]; then
    # is frozen 
    URL="$SERVER_HIST/$YEAR/tlnet-final/$INST_TL_FILE"
  else
    # TBD: clarify whether "$SERVER_CURR"/$INST_TL_FILE works also, then $repo/$INST_TL_FILE is the better choice 
    URL="$SERVER_HIST/$YEAR/$INST_TL_FILE"
  fi

  # URL="https://mirror.ctan.org/systems/texlive/$YEAR/tlnet-final/$INST_TL_FILE"
  # URL="https://ftp.tu-chemnitz.de/pub/tug/historic/systems/texlive/$YEAR/$INST_TL_FILE"
  wget "$URL" || {
      errorMsg "wget" $? ERRORS_WGET
      error "Downloading installer $INST_TL_FILE failed. Aborting."; exit 2; 
    }

  succ "Downloaded installer."

  INSTALLER="install-tl"
  if [ "$isWindowsShell" = true ]; then
    unzip -l "$INST_TL_FILE" >/dev/null 2>&1 || {
        errorMsg "unzip" $? ERRORS_UNZIP
        error "'unzip' does not support -l option or failed on '$INST_TL_FILE'. Aborting."; exit 1
      }
    INSTALLER_DIR=$(unzip -l "$INST_TL_FILE" | awk '/install-tl$/ {print $4}' | cut -d/ -f1 | head -1)
  else
    tar -tf "$INST_TL_FILE" >/dev/null 2>&1 || {
        errorMsg "tar" $? ERRORS_TAR
        error "'tar' does not support -tf option or failed on '$INST_TL_FILE'. Aborting."; exit 1
      }
    INSTALLER_DIR=$(tar -tf "$INST_TL_FILE" | grep -E "^[^/]+/$INSTALLER" | cut -d/ -f1 | head -1)
  fi
  [ -n "$INSTALLER_DIR" ] || { error "Unexpected folder structure of $INST_TL_FILE. Aborting."; exit 3; }

  rm -rf "$INSTALLER_DIR"
  if [ "$isWindowsShell" = true ]; then
    unzip "$INST_TL_FILE" || {
        errorMsg "unzip" $? ERRORS_UNZIP
        error "Extracting '$INST_TL_FILE' with 'unzip' failed. Aborting."; exit 1
      }
  else
    tar -xzf "$INST_TL_FILE" || {
        errorMsg "tar" $? ERRORS_TAR
        error "Extracting '$INST_TL_FILE' with 'tar' failed. Aborting."; exit 1
      }
  fi

  pushd "$INSTALLER_DIR" > /dev/null \
    || { error "Could not enter installation folder $INSTALLER_DIR. Aborting."; exit 3; }
    info "installing texlive $YEAR scheme $SCHEME into $INSTALL_DIR"
    chmod +x "$INSTALLER"
    # ./$INSTALLER --scheme "$SCHEME" --no-interaction -paper "a4" --texdir "$INSTALL_DIR" \
    #   || { error "Installer failed with exit code $?."; exit 3; }

    # if [ "$IS_FROZEN" -eq 0 ]; then
    #   repo="$SERVER_HIST/$YEAR/tlnet-final"
    #  else
    #   repo="$SERVER_CURR"
    # fi

    # ./$INSTALLER -repository "$repo" \
    #   -scheme "$SCHEME" -no-interaction -paper "$PAPER" -texdir "$INSTALL_DIR" \
    #   || { error "Installer failed with exit code $?. Aborting."; exit 3; }

    #

    # has return values 0 for ok and 1 for not ok only. 
    TL_PLATFORM_I=$(./$INSTALLER -print-platform 2>/dev/null | grep -oE '^[a-z0-9_-]+' | head -n 1)
    [ -n "$TL_PLATFORM_I" ] || { error "Current platform $TL_PLATFORM cannot be detected. Aborting."; exit 4; }

    if [ "$TL_PLATFORM" != "$TL_PLATFORM_I" ]; then
        warn "Current platform $TL_PLATFORM_I erroneously identified as $TL_PLATFORM_I."
        TL_PLATFORM="$TL_PLATFORM_I"
        BIN_DIR="$INSTALL_DIR/bin/$TL_PLATFORM"
        if [ -d "$BIN_DIR" ]; then
          popd > /dev/null
          return 0
        fi
    fi

    IS_SUPPORTED=$(get_binaries | grep -w "binary_$TL_PLATFORM")
    [ -n "$IS_SUPPORTED" ] || { error "Current platform $TL_PLATFORM is not supported. Aborting."; exit 4; }
    IS_ACTIVE=$(echo "$IS_SUPPORTED" | awk '{print $2}')
    [ "$IS_ACTIVE" = "1" ] || { error "Current platform $TL_PLATFORM not selected as required. Aborting."; exit 4; }



  # This invocation with the options --no-interaction -paper "$PAPER" --texdir "$INSTALL_DIR" allow installation back until 2022. 
  # With the following profile and replacing --scheme "$SCHEME" --no-interaction -paper "$PAPER" --texdir "$INSTALL_DIR" 
  # by -profile texlive.profile, we can go back until 2017 by need. 

  # transforming PAPER into isPaper
  case "${PAPER,,}" in # transforming into lower case 
    "a4")
        isLetter=0
        ;;
    "letter")
        isLetter=1
        ;;
    *)
        error "Invalid PAPER setting: '$PAPER'. Must be 'a4' or 'letter'. Aborting."
        exit 1
        ;;
  esac
  [[ "$CONF_SHELL_ESCAPE_RESTRICTED" -eq 1 ]] \
    || warn "DANGER: Shell escape is UNRESTRICTED in this installation!"

cat > "texlive.profile" <<EOF
selected_scheme scheme-$SCHEME
TEXDIR $INSTALL_DIR
TEXMFCONFIG ~/.texlive${YEAR}/texmf-config
#TEXMFCONFIG $INSTALL_DIR/texmf-config
TEXMFSYSCONFIG $INSTALL_DIR/texmf-config
TEXMFHOME ~/texmf
#TEXMFHOME $INSTALL_DIR/texmf-home
TEXMFLOCAL $INSTALL_DIR/texmf-local
TEXMFVAR ~/.texlive${YEAR}/texmf-var
#TEXMFVAR $INSTALL_DIR/texmf-var
TEXMFSYSVAR $INSTALL_DIR/texmf-var
#ASYMPTOTE_HOME = $INSTALL_DIR/texmf-config/asymptote

# if portable (see below), whhich is generally desirable, 
#TEXMFVAR is set to TEXMFSYSVAR and TEXMFCONFIG is set to TEXMFSYSCONFIG anyway 
#TEXMFHOME shall not be used. 

$(get_binaries)

# installation options 
instopt_adjustpath 0
# default: 
instopt_adjustrepo 1
instopt_letter $isLetter 
instopt_portable 0
instopt_write18_restricted $CONF_SHELL_ESCAPE_RESTRICTED

# turned options into tlpdbopt 
tlpdbopt_autobackup 0
#tlpdbopt_backupdir tlpkg/backups # not needed because tlpdbopt_autobackup 0
tlpdbopt_create_formats 1
tlpdbopt_desktop_integration 0
tlpdbopt_file_assocs 0
tlpdbopt_generate_updmap 0 
tlpdbopt_install_docfiles $INST_DOCFILES
tlpdbopt_install_srcfiles $INST_SRCFILES
tlpdbopt_post_code 1

# unused because of instopt_adjustpath 0?
tlpdbopt_sys_bin /usr/local/bin 
# unused because of instopt_adjustpath 0?
tlpdbopt_sys_info /usr/local/share/info
# unused because of instopt_adjustpath 0?
tlpdbopt_sys_man /usr/local/share/man
tlpdbopt_w32_multi_user 0
EOF
    # option -persistent-downloads not defined for older releases, 
    # but then a warning is emitted 
    # From $INSTALLER --help: 
    # --persistent-downloads
    # --no-persistent-downloads
    #     For network installs, activating this option makes the installer try
    #     to set up a persistent connection using the "Net::LWP" Perl module.
    #     This opens only one connection between your computer and the server
    #     per session and reuses it, instead of initiating a new download for
    #     each package, which typically yields a significant speed-up.

    #     This option is turned on by default, and the installation program
    #     will fall back to using "wget" if this is not possible. To disable
    #     usage of LWP and persistent connections, use
    #     "--no-persistent-downloads".

    # has return values 0 for ok and 1 for not ok only. 
    ./$INSTALLER -repository "$repo" -profile "texlive.profile" \
      || { error "Installer failed with exit code $?. Aborting."; exit 3; }

      
  popd > /dev/null
  [ -d "$BIN_DIR" ] \
    || { error "Unexpected: Folder $BIN_DIR for binaries is not accessible. Aborting."; exit 4; }
  
  # schema small is installed even with a reinstall with scheme minimal
  #succ "Installed texlive $YEAR with scheme $SCHEME for platform $TL_PLATFORM."
  return 1
else
  #info "Found texlive $YEAR for platform $TL_PLATFORM installed locally."
  return 0
fi
}

if reinstallByNeed; then
  succ "Installed texlive $YEAR with scheme $SCHEME for platform $TL_PLATFORM."
else
  info "Found texlive $YEAR for platform $TL_PLATFORM installed locally."
fi

# TBD: clarify whether we shall erase the installation folder. 

# Here, texlive $YEAR is installed with schema $SCHEME 
# at least for the current platform $TL_PLATFORM 

#pushd "$BIN_DIR" > /dev/null || { error "Unexpected: Platform $TL_PLATFORM is not accessible."; exit 4; }
PATH="$BIN_DIR:$PATH"

#tlmgr option repository https://ctan.uni-freiburg.de/systems/texlive/tlnet/
# ./tlmgr option repository https://mirror.ctan.org/systems/texlive/tlnet \
#   || { warn "Setting tlmgr mirror failed; skipping.";  }
# worked for 2024, 2023
# worked for 2025: drop altogether 
# ./tlmgr option repository https://ftp.tu-chemnitz.de/pub/tug/historic/systems/texlive/$YEAR \
#   || { warn "Setting tlmgr mirror failed; skipping.";  }

# warning in case of tlmgr has error or warning 
tlmgr option repository "$repo" ||
  {
    errorMsgTlmgr $?
    warn "Setting tlmgr mirror failed; skipping."
  }

# TBD: UPDATE only for live releases 
#   tlmgr: Remote database at https://ftp.mpi-inf.mpg.de/pub/tex/mirror/ftp.dante.de/pub/tex/systems/texlive/tlnet
# (revision 77141 of the texlive-scripts package)
# seems to be older than the local installation
# (revision 77146 of texlive-scripts);
# please use a different mirror and/or wait a day or two.

# from tlmgr --help: 
    # --persistent-downloads
    # --no-persistent-downloads
    #     For network-based installations, this option (on by default) makes
    #     "tlmgr" try to set up a persistent connection (using the "LWP" Perl
    #     module). The idea is to open and reuse only one connection per
    #     session between your computer and the server, instead of initiating
    #     a new download for each package.

    #     If this is not possible, "tlmgr" will fall back to using "wget". To
    #     disable these persistent connections, use
    #     "--no-persistent-downloads".


info "Updating tlmgr ..."
tlmgr update --self || {
    errorMsgTlmgr $?
    warn "Updating tlmgr failed; skipping."
  }

#tlmgr --repository=/usr/local/texlive/2025/tlpkg list --only-installed > installed-packages.txt
# xargs tlmgr install < installed-packages.txt

info "Updating packages ..."
tlmgr update --all || {
    errorMsgTlmgr $?
    warn "Updating packages failed; skipping."
  }

#sudo tlmgr install latexmk rubber epstool latex2rtf


info "Adding packages to installation..."
# made directory of this scirpt $(dirname "${BASH_SOURCE[0]} an absolute path 
PKG_FILE="$SCRIPT_DIR/texlive$YEAR$SCHEME.pkg"
[ -f "$PKG_FILE" ] \
  || { error "Required package list $PKG_FILE not found. Aborting update."; exit 5; }
tlmgr install $(sed 's/#.*//' "$PKG_FILE" | sed '/^\s*$/d') || {
    errorMsgTlmgr $?
    warn "Installing packages in $PKG_FILE failed; skipping."
  }



# This is for patching, but we define in any case 

patchRec() {
  # Recursively copies folder structure and files from a patch server to a local folder.
  #
  # INPUT:
  #   $1  - SERVER_DIR : URL of the patch server folder (must provide an HTML index with <tr> table rows)
  #   $2  - LOCAL_DIR  : Local target folder where the files/folders will be copied
  #
  # BEHAVIOR:
  #   - Ensures LOCAL_DIR exists (mkdir -p)
  #   - Downloads the HTML index from SERVER_DIR
  #   - Iterates over all <tr> lines in the index to extract <a href="..."> entries
  #   - Ignores entries with href="/" (Parent Directory)
  #   - Recursively processes subdirectories (href ending with '/')
  #   - Downloads files (wget -N) to the corresponding LOCAL_DIR path
  #
  # ERROR HANDLING (Return Value as bitmask):
  #   - Bit 0 (value 1) : Index fetch failure (could not read SERVER_DIR)
  #   - Bit 1 (value 2) : Any file download failure
  #   - Bits are OR-combined recursively, so RET will reflect errors in subfolders too


  local SERVER_DIR="$1"
  local LOCAL_DIR="$2"
  #info "in patching1 $SERVER_DIR  $LOCAL_DIR"

  # ensure existence of $LOCAL_DIR (in top level normally superfluous but necessary in deeper nestings)
  mkdir -p "$LOCAL_DIR"

  # assume server provides the folder entries in a table, each entry corresponds with a row 
  local html=$(wget -qO- "$SERVER_DIR/") || {
      errorMsg "wget" $? ERRORS_WGET
      warn "Could not fetch $SERVER_DIR"
      return
    }

  # The elements of the folder $SERVER_DIR are the lines in $html containing '<tr>', 
  # i.e. lines of a table 
  local lines=$(echo "$html" | grep '<tr>')

  # feed lines into the while loop 
  local RET=0
  while read -r line; do
    local href
    # href is all quoted in <a href="....">; all other lines skip 
    href=$(echo "$line" | grep -oP '(?<=<a href=")[^"]+') || continue

    # skip href="/..." and href="?..."
    [[ "$href" == /* ]] && continue
    [[ "$href" == \?* ]] && continue

    if [[ "$href" == */ ]]; then
      # folder, call recursively 
      # eliminate trailing slash 
      href="${href%/}"
      info "Patching folder $SERVER_DIR/$href"
      patchRec "$SERVER_DIR/$href" "$LOCAL_DIR/$href"
      RET=$(( RET | $? ))
    else
      # file, try to download 
      info "Patching file $SERVER_DIR/$href"
      wget -N "$SERVER_DIR/$href" -P "$LOCAL_DIR" || {
          errorMsg "wget" $? ERRORS_WGET
          warn "Downloading $href failed"
        }
     fi
  done <<< "$lines"
} # patchRec

if [ -n "$SERVER_PATCH" ]; then
  info "Applying patches from $SERVER_PATCH..."

  if wget --spider --quiet "$SERVER_PATCH/$YEAR/" 2>/dev/null; then

    # TBD: rework and evaluate return value of patchRec 
    # Here, the patch server provides a patch for this year 
    #                                INSTALL_DIR="$ROOT/$YEAR"
    patchRec "$SERVER_PATCH/$YEAR" "$INSTALL_DIR/texmf-local"

    # This is a hack 
    PYTEX_PATCH_DIR="$INSTALL_DIR/texmf-local/scripts/pythontex"
    if [ -d "$PYTEX_PATCH_DIR" ]; then
      #set -x  # debug on 

      pushd "$INSTALL_DIR" > /dev/null
        # do not overwrite what is already present, i.e. patched 
        cp -n texmf-dist/scripts/pythontex/* texmf-local/scripts/pythontex/
      popd

      pushd "$BIN_DIR" > /dev/null
        # Here, the user is root, because else this script would have failed earlier. 
        # Thus ln creates symlinks even for msys. 
        # (If not, this would not be critical anyway)
        ln -sf ../../texmf-local/scripts/pythontex/pythontex.py     pythontex
        ln -sf ../../texmf-local/scripts/pythontex/depythontex.py depythontex
      popd > /dev/null
      #set +x  # debug off 
    fi
  else
    info "No patches on $SERVER_PATCH for $YEAR."
  fi
else
  info "No patching configured."
fi


# TBD: If all are the same, a warning is needed that the patch is obsolete. 
# If at least one of the files in xxx.tds.zip differs from its counterpart in texml-dist, 
# as for example, does not exist in texml-dist or deviates, then the patch is still justified. 

# patch commutative-diagrams 
# PLEASE DO NOT REMOVE despite commutative-diagrams is now included in the base installation. 
# 
# Note that we install in tumbleweed installation of texlive only, not in the official one. 
patchTds() {
  local tdsPath="$1"
  local tdsFile=$(basename "$tdsPath") 

  local url="$SERVER_TDS_PATCH/$tdsPath"

  # OLD_TS=0 if $tdsFile does not exist or timestamp cannot be found for some reason 
  local OLD_TS=$(stat -c %Y "$tdsFile" 2>/dev/null || echo 0)
  # wget replaces only, if download succeeds 
  wget -N --no-if-modified-since "$url" || {
      errorMsg "wget" $? ERRORS_WGET
      warn "Downloading $tdsFile from $url failed, skip installation/update. "
    }

  local NEW_TS=$(stat -c %Y "$tdsFile" 2>/dev/null || echo 0)
  # If download failed, then tdsFile is not changed: either did not exist so after does not exist either. 
  # or it is not changed. 
  # In particular, the two timestamps coincide. 
  if [ "$NEW_TS" -ne "$OLD_TS" ]; then
  #if [ $(stat -c %Y "$tdsFile") -ne "$OLD_TS" ]; then
    # only here do anything. 
    unzip -o "$tdsFile"
  fi
}

PATCH_FILE="texlive${YEAR}.tds"
if [[ -f "$PATCH_FILE" ]]; then
  info "performing tds patching..."
  DIR="$INSTALL_DIR/texmf-local/"

  pushd "$DIR" > /dev/null || { error "Could not enter $DIR."; exit 6; } # we shall not exit because patching failed 
    while IFS= read -r line || [ -n "$line" ]; do
      # skip empty lines and lines starting with # 
      [[ -z "$line" || "$line" =~ ^# ]] && continue
      patchTds "$line"
    done < "$PATCH_FILE"
  popd > /dev/null

  info "...patching completed"
else
  info "Skipping tds patching: no patch file ($PATCH_FILE)"
fi




info "Rehashing installation."
texhash || warn "Rehashing failed; skipping."



