#!/bin/bash
# bajar-paquetes: baja paquetes del servidor de descargas, comprobando su md5,
# y si se pide los instala. La contraparte de subir-paquetes. Reemplaza a
# download.sh e installsumma.sh, que bajaban sin comprobar el certificado y
# averiguando la IP del servidor con 8.8.8.8 (07/10/2026, ver BIN.md).
#
#   bajar-paquetes [opciones] PROYECTO[/PAQUETE]...
#   bajar-paquetes [opciones] ARCHIVO.summa...
#
# Primero baja PROYECTO.summa (queda en el directorio) y despues, con lo que
# dice ese .summa (PACKAGES y VERSION), los binarios. Con un .summa que ya se
# tiene, baja directamente los binarios que el dice.
#
#   PROYECTO      todos sus paquetes (los dice PROYECTO.summa, arriba en el
#                 servidor): tabular2 baja libtabular2-0, libtabular2-dev y
#                 tabular; summapack baja summapack, libmem-util0 y -dev
#   PROYECTO/PAQUETE   uno solo: tabular2/libtabular2-0
#   --formato F   deb, rpm, tgz, tar.gz o summa. Por omision, el del sistema:
#                 deb en Debian y Ubuntu, rpm en Fedora, Red Hat y SUSE, tgz
#                 en los demas
#   --inestable   la ultima subida, o la estable si no hay nada mas nuevo;
#                 por omision, la estable (la VERSION de PROYECTO.summa)
#   --version V   una version dada (6.0-38)
#   --dir D       donde dejarlos (por omision ~/.cache/summapack)
#   --instalar    instalarlos: apt, dnf/zypper, o instalar-tgz para los .tgz
#   --sin-dependencias  solo lo pedido. Por omision, tambien las
#                 dependencias que son de nuestros proyectos (libslex0,
#                 libmem-util0...) y no estan instaladas, o estan en una
#                 version menor que la pedida; las del sistema (libc6...) las
#                 resuelve apt o dnf desde sus repositorios
#   --prueba      mostrar que bajaria e instalaria, sin hacerlo (sin bajar,
#                 no ve las dependencias)
#
# En el servidor (ver subir-paquetes): PROYECTO.summa arriba (dice los
# paquetes y la version estable), los estables en PROYECTO/, y en
# PROYECTO/otros/ los .md5, lo demas y PAQ_UNSTABLE... mientras haya algo
# inestable. Cada archivo se baja con su .md5 y queda con su nombre de verdad
# (calcula_6.0-38_amd64.deb). Si ya esta en el directorio con el md5
# correcto, no se vuelve a bajar.
#
# El servidor: BAJAR_URL en el entorno o en ~/.config/simusol/subir; por
# omision https://simusol.org/downloads. Admite file:// para probar.
#
# Es un solo archivo, sin mas que bash, curl y md5sum: installsumma lo baja
# del servidor para instalar summapack en una maquina que no lo tiene.
set -euo pipefail

# ~/.config/simusol/subir solo completa lo que no venga en el entorno: el
# entorno manda (07/10/2026: al reves, una prueba con SUBIR_SERVIDOR=local
# termino corriendo contra el servidor real)
for _v in BAJAR_URL BAJAR_DIR; do eval "_entorno_$_v=\${$_v-}"; done
[ -f "$HOME/.config/simusol/subir" ] && . "$HOME/.config/simusol/subir"
for _v in BAJAR_URL BAJAR_DIR; do eval "[ -z \"\$_entorno_$_v\" ] || $_v=\$_entorno_$_v"; done
URL=${BAJAR_URL:-https://simusol.org/downloads}
DIR=${BAJAR_DIR:-$HOME/.cache/summapack}

formato=""; etiqueta=estable; instalar=0; prueba=0; con_dependencias=1; nombres=()
while [ $# -gt 0 ]; do
    case "$1" in
        --formato)   formato=$2; shift ;;
        --inestable) etiqueta=UNSTABLE ;;
        --version)   etiqueta=$2; shift ;;
        --dir)       DIR=$2; shift ;;
        --instalar)  instalar=1 ;;
        --prueba)    prueba=1 ;;
        --sin-dependencias) con_dependencias=0 ;;
        -h|--help)   sed -n '2,45s/^# \{0,1\}//p' "$0"; exit 0 ;;
        -*)          echo "bajar-paquetes: no conozco $1 (--help)" >&2; exit 2 ;;
        *)           nombres+=("$1") ;;
    esac
    shift
done
set -- "${nombres[@]+"${nombres[@]}"}"
[ $# -gt 0 ] || { echo "uso: bajar-paquetes [opciones] PROYECTO[/PAQUETE]... (--help)" >&2; exit 2; }

# el formato del sistema, segun distro de summapack (distro.sh al lado,
# cuando lo baja installsumma; si no, el instalado). Pedido del autor,
# 07/10/2026: una sola deteccion de la distribucion, la de distro.sh.
sistema() {
    local d=''
    if [ -f "$(dirname "$0")/distro.sh" ]; then d=$(sh "$(dirname "$0")/distro.sh" 2>/dev/null)
    elif command -v distro >/dev/null; then d=$(distro 2>/dev/null); fi
    case "$d" in
        UBUNTU|DEBIAN|ELEMENTARY|ANTIX|WINDOWS10UBUNTU|BODHI|DEBIANBSD|DEBIANGNUHURD) echo deb ;;
        FEDORA|REDHAT|SUSE|OPENSUSE|MANDRIVA|MANDRAKE|PCLINUXOS|YELLOWDOG) echo rpm ;;
        '') # sin distro: por la herramienta que haya
            if command -v dpkg >/dev/null; then echo deb
            elif command -v rpm >/dev/null; then echo rpm
            else echo tgz; fi ;;
        *)  echo tgz ;;
    esac
}
[ -n "$formato" ] || formato=$(sistema)
case "$formato" in deb|rpm|tgz|tar.gz|summa) ;; *) echo "bajar-paquetes: formato desconocido: $formato" >&2; exit 2 ;; esac

arq_deb=$(dpkg --print-architecture 2>/dev/null || { case "$(uname -m)" in x86_64) echo amd64 ;; aarch64) echo arm64 ;; *) uname -m ;; esac; })
arq_rpm=$(uname -m)

# los nombres de un paquete con una version (o UNSTABLE)
nombres() {
    local n=$1 e=$2
    case "$formato" in
        deb)    echo "${n}_${e}_${arq_deb}.deb ${n}_${e}_all.deb" ;;
        tgz)    echo "${n}_${e}_${arq_deb}.tgz ${n}_${e}_all.tgz" ;;
        rpm)    echo "${n}-${e}.${arq_rpm}.rpm ${n}-${e}.noarch.rpm" ;;
        tar.gz) echo "${n}_${e}.tar.gz" ;;
        summa)  echo "${n}_${e}.summa" ;;
    esac
}

traer() {   # traer ARCHIVO_REMOTO DESTINO: falla en silencio si no esta
    curl -fsSL --retry 2 -o "$2" "$URL/$1" 2>/dev/null
}

mkdir -p "$DIR"

# el PROYECTO.summa del servidor, una vez por proyecto: dice los paquetes
# (PACKAGES) y la version estable (VERSION, la agrega subir-paquetes --estable)
declare -A summas=()
summa_de() {   # deja en SUMMA el .summa del proyecto; no se llama en un $(...)
    local p=$1
    if [ -z "${summas[$p]:-}" ]; then
        # primero el .summa: se baja y queda en DIR, y de el sale todo lo demas
        if ! traer "$p.summa" "$DIR/$p.summa.parcial"; then
            rm -f "$DIR/$p.summa.parcial"
            echo "bajar-paquetes: no hay $p.summa en $URL (se pide el proyecto: tabular2, no libtabular2-0; para uno solo, PROYECTO/PAQUETE)" >&2
            return 1
        fi
        mv "$DIR/$p.summa.parcial" "$DIR/$p.summa"
        echo "summa: $DIR/$p.summa"
        summas[$p]=$DIR/$p.summa
    fi
    SUMMA=${summas[$p]}
}
campo() { sed -n "s/^$2=\"\\(.*\\)\"\$/\\1/p" "$1" | tail -1; }

declare -A dependencia=()   # lo agregado como dependencia de otro
pedidos=()
for a in "$@"; do
    # un .summa que ya se tiene: los binarios que el dice
    if [ -f "$a" ] && [ "${a%.summa}" != "$a" ]; then
        p=$(basename "$a" .summa); p=${p%%_*}
        summas[$p]=$(readlink -f "$a")
        a=$p
    fi
    case "$a" in
        */*) pedidos+=("$a") ;;
        *)   case "$formato" in
                 tar.gz|summa) pedidos+=("$a/$a") ;;
                 *) summa_de "$a" || exit 1
                    for q in $(campo "$SUMMA" PACKAGES); do pedidos+=("$a/$q"); done ;;
             esac ;;
    esac
done

# intentar MD5 ARCHIVO...: baja el .md5 y el archivo (del primer lugar donde
# este) y comprueba la suma; deja en hallado lo bajado
intentar() {
    local m=$1; shift
    local md5tmp suma real u
    if [ $prueba = 1 ]; then echo "  [prueba] probaria $URL/$m y $URL/$1"; return 1; fi
    md5tmp=$(mktemp)
    traer "$m" "$md5tmp" || { rm -f "$md5tmp"; return 1; }
    suma=$(cut -d' ' -f1 < "$md5tmp")
    real=$(awk '{print $2}' < "$md5tmp" | sed 's/^\*//')
    [ -n "$real" ] || real=$(basename "$1")
    real=$(basename "$real")
    if [ -f "$DIR/$real" ] && [ "$(md5sum < "$DIR/$real" | cut -d' ' -f1)" = "$suma" ]; then
        echo "ya estaba: $DIR/$real"
    else
        rm -f "$DIR/$real.parcial"
        for u in "$@"; do traer "$u" "$DIR/$real.parcial" && break; done
        [ -s "$DIR/$real.parcial" ] || { rm -f "$md5tmp" "$DIR/$real.parcial"; echo "bajar-paquetes: esta $m pero no se pudo bajar el archivo" >&2; exit 1; }
        if [ "$(md5sum < "$DIR/$real.parcial" | cut -d' ' -f1)" != "$suma" ]; then
            rm -f "$DIR/$real.parcial" "$md5tmp"
            echo "bajar-paquetes: $real bajo con otro md5 que el del servidor: se descarta" >&2
            exit 1
        fi
        mv "$DIR/$real.parcial" "$DIR/$real"
        echo "bajado: $DIR/$real"
    fi
    cp "$md5tmp" "$DIR/$real.md5"
    rm -f "$md5tmp"
    hallado=$DIR/$real
}

# ---- dependencias: las de nuestros proyectos que no esten instaladas (o
# esten en una version menor que la pedida) se bajan tambien; las del sistema
# (libc6, python3...) las resuelve el instalador (apt, dnf) desde sus
# repositorios. Pedido del autor, 07/10/2026.
declare -A proyecto_de=()   # paquete -> proyecto, de los .summa del servidor
mapa_cargado=0
cargar_mapa() {
    [ $mapa_cargado = 0 ] || return 0
    mapa_cargado=1
    local lista f p q
    if [ "${URL#file://}" != "$URL" ]; then
        lista=$(cd "${URL#file://}" 2>/dev/null && ls -1 -- *.summa 2>/dev/null) || true
    else
        lista=$(curl -fsSL "$URL/" 2>/dev/null | grep -o 'href="[^"/?]*\.summa"' | sed 's/^href="//; s/"$//' | sort -u) || true
    fi
    for f in $lista; do
        p=${f%.summa}
        summa_de "$p" > /dev/null 2>&1 || continue
        for q in $(campo "$SUMMA" PACKAGES); do proyecto_de[$q]=$p; done
    done
}

# las dependencias de un paquete bajado, una por renglon (a | b, con version)
dependencias_de() {
    local f=$1 deb t c
    case "$f" in
        *.deb) { dpkg-deb -f "$f" Pre-Depends 2>/dev/null; echo; dpkg-deb -f "$f" Depends 2>/dev/null; } | tr ',' '\n' ;;
        *.rpm) rpm -qpR "$f" 2>/dev/null | grep -v '^rpmlib(\|^/' ;;
        *.tgz)
            # el .tgz no trae sus dependencias: se leen del .deb del mismo
            # paquete (esta al lado en el servidor)
            deb=$(basename "${f%.tgz}.deb"); t=$(mktemp -d)
            if traer "$P_DE_TGZ/$deb" "$t/$deb" || traer "$P_DE_TGZ/otros/$deb" "$t/$deb"; then
                if command -v dpkg-deb >/dev/null; then
                    dpkg-deb -f "$t/$deb" Depends 2>/dev/null | tr ',' '\n'
                elif command -v ar >/dev/null; then
                    for c in control.tar.zst control.tar.xz control.tar.gz; do
                        (cd "$t" && ar x "$deb" "$c" 2>/dev/null) && break
                    done
                    tar -xOf "$t"/control.tar.* ./control 2>/dev/null | sed -n 's/^Depends: //p' | tr ',' '\n'
                fi
            fi
            rm -rf "$t" ;;
    esac | sed 's/^ *//; s/ *$//' | grep -v '^$' || true
}

# la version instalada de un paquete (vacio si no esta)
instalada() {
    local q=$1 v
    if command -v dpkg-query >/dev/null && v=$(dpkg-query -W -f='${Status} ${Version}' "$q" 2>/dev/null) \
       && [ "${v#install ok installed }" != "$v" ]; then echo "${v#install ok installed }"; return; fi
    if command -v rpm >/dev/null && v=$(rpm -q --qf '%{VERSION}-%{RELEASE}' "$q" 2>/dev/null); then echo "$v"; return; fi
    [ -f "/var/lib/summapack/tgz/$q.version" ] && cat "/var/lib/summapack/tgz/$q.version"
    return 0
}
# cumple VERSION OP PEDIDA
cumple() {
    local v=$1 op=$2 w=$3
    [ -n "$op" ] || return 0
    if command -v dpkg >/dev/null; then dpkg --compare-versions "$v" "$op" "$w"; return; fi
    case "$op" in
        '>='|'>>'|'>') [ "$(printf '%s\n%s\n' "$w" "$v" | sort -V | head -1)" = "$w" ] ;;
        '<='|'<<'|'<') [ "$(printf '%s\n%s\n' "$w" "$v" | sort -V | head -1)" = "$v" ] ;;
        *)             [ "$v" = "$w" ] ;;
    esac
}

declare -A en_cola=()
bajados=()
k=0
for pq in "${pedidos[@]}"; do en_cola[${pq#*/}]=1; done
while [ $k -lt ${#pedidos[@]} ]; do
    pq=${pedidos[$k]}; k=$((k + 1))
    p=${pq%%/*}; nombre=${pq#*/}
    hallado=''
    # lo inestable: el nombre fijo, mientras haya algo inestable (las
    # dependencias que se agregan van siempre en su version estable)
    if [ "$etiqueta" = UNSTABLE ] && [ -z "${dependencia[$pq]:-}" ]; then
        for c in $(nombres "$nombre" UNSTABLE); do
            intentar "$p/otros/$c.md5" "$p/otros/$c" && break
        done
    fi
    # lo estable (o una version dada): el nombre exacto
    if [ -z "$hallado" ]; then
        if [ "$etiqueta" = estable ] || [ "$etiqueta" = UNSTABLE ] || [ -n "${dependencia[$pq]:-}" ]; then
            summa_de "$p" || exit 1; v=$(campo "$SUMMA" VERSION)
            [ -n "$v" ] || { echo "bajar-paquetes: $p.summa no dice VERSION: nada estable todavia (--inestable)" >&2; exit 1; }
        else
            v=$etiqueta
        fi
        for c in $(nombres "$nombre" "$v"); do
            intentar "$p/otros/$c.md5" "$p/$c" "$p/otros/$c" && break
        done
    fi
    if [ $prueba = 1 ]; then
        bajados+=("$DIR/$(nombres "$nombre" "${v:-$etiqueta}" | cut -d' ' -f1)")
        continue
    fi
    [ -n "$hallado" ] || { echo "bajar-paquetes: no hay $p/$nombre ($formato, $etiqueta) en $URL" >&2; exit 1; }
    bajados+=("$hallado")
    # sus dependencias
    [ $con_dependencias = 1 ] || continue
    case "$formato" in deb|rpm|tgz) ;; *) continue ;; esac
    P_DE_TGZ=$p
    while IFS= read -r d; do
        # a | b: alcanza con una
        resuelta=0; candidata=''
        IFS='|' read -ra alts <<< "$d"
        for alt in "${alts[@]}"; do
            alt=$(echo "$alt" | sed 's/^ *//; s/ *$//')
            q=${alt%% *}; q=${q%%:*}
            op=$(echo "$alt" | sed -nE 's/^[^ ]+ *\(? *(>=|<=|>>|<<|=|>|<) *([^ )]+).*$/\1/p')
            w=$(echo "$alt" | sed -nE 's/^[^ ]+ *\(? *(>=|<=|>>|<<|=|>|<) *([^ )]+).*$/\2/p')
            if [ -n "${en_cola[$q]:-}" ]; then resuelta=1; break; fi
            iv=$(instalada "$q")
            if [ -n "$iv" ] && cumple "$iv" "$op" "$w"; then resuelta=1; break; fi
            cargar_mapa
            if [ -z "$candidata" ] && [ -n "${proyecto_de[$q]:-}" ]; then candidata=$q; fi
        done
        [ $resuelta = 0 ] || continue
        [ -n "$candidata" ] || continue   # es del sistema: la resuelve el instalador
        dp=${proyecto_de[$candidata]}
        echo "  dependencia de $nombre: $dp/$candidata${iv:+ (instalada $iv, se pide $op $w)}"
        en_cola[$candidata]=1
        dependencia[$dp/$candidata]=1
        pedidos+=("$dp/$candidata")
    done < <(dependencias_de "$hallado")
done

[ $instalar = 1 ] || exit 0

orden() {
    if [ $prueba = 1 ]; then echo "  [prueba] $*"; else echo "+ $*"; "$@"; fi
}
sudo_si_hace_falta() { if [ "$(id -u)" = 0 ]; then orden "$@"; else orden sudo "$@"; fi; }

case "$formato" in
    deb)
        rutas=(); for b in "${bajados[@]}"; do rutas+=("$(readlink -f "$b" 2>/dev/null || echo "$b")"); done
        sudo_si_hace_falta apt-get install --yes "${rutas[@]}" ;;
    rpm)
        if command -v dnf >/dev/null; then sudo_si_hace_falta dnf install -y "${bajados[@]}"
        elif command -v zypper >/dev/null; then sudo_si_hace_falta zypper install -y "${bajados[@]}"
        else sudo_si_hace_falta rpm -Uvh "${bajados[@]}"; fi ;;
    tgz)
        it="$(dirname "$0")/instalar-tgz"; [ -x "$it" ] || it=$(command -v instalar-tgz)
        sudo_si_hace_falta "$it" "${bajados[@]}" ;;
    *)
        echo "bajar-paquetes: un $formato no se instala" >&2; exit 2 ;;
esac
