#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS runs

function display_help() {
    uecho "$scriptname: Lancer un script avec le protocole runs

USAGE
    $scriptname [options] rscriptname name=value...
    $scriptname [options] @recipe name=value...
    $scriptname [options] -f rscript name=value...
    $scriptname [options] -r recipe name=value...

OPTIONS
Configuration
    --init
    --verify
        Vérifier le nom d'hôte et créer si nécessaire le répertoire d'hôte
        correspondant à l'hôte courant ou à l'hôte spécifié avec l'option -h
        Avec --verify, la création du répertoire d'hôte n'est pas effectuée.
    --sysinfos DATA
        Avec l'option --init, initialiser le fichier sysinfos.conf avec DATA, si
        le fichier n'a pas déjà été provisionné. Sans cette option, un fichier
        vide avec des commentaires est créé à la place.
    --create RSCRIPT
        Créer un modèle de script avec le nom donné.
        Avec l'option -h, le script est créé dans le répertoire d'hôte
        correspondant à l'hôte spécifié

Gestion des scripts
    -s  Forcer l'exécution du script avec l'utilisateur root si ce n'est pas
        déjà le cas
    -f RSCRIPT
        Lancer le script individuel spécifié au lieu de chercher dans les
        répertoires de \$RUNSSCRIPTSPATH
    -r RECIPE
        Lancer les scripts spécifiés dans le fichier de recettes individuel
        spécifié.
    -h HOSTNAME[.DOMAIN]
        Spécifier que les scripts sont destinés à être lancés sur l'hôte
        spécifié. Les scripts sont alors aussi cherchés dans les répertoires
        {\$RUNSHOSTSPATH}/\$hostname.\$domain (par défaut) et
        {\$RUNSHOSTSPATH}/\$domain/\$hostname (le cas échéant)
        L'option --host est équivalente, sauf que son argument est facultatif et
        que sa valeur par défaut est l'hôte courant, soit $MYHOST
    --list
        Afficher la liste des scripts qui sont disponibles. Avec l'option -h,
        inclure aussi les scripts spécifiques à cet hôte.
        Avec cette option, les arguments supplémentaires agissent comme des
        filtres (regexp utilisée avec l'opérateur == de la commande [[). Les
        noms des scripts doivent valider au moins un filtre.
    --info
        Afficher la la description du script et la valeur de chaque variable
        définies
    --desc-only
        Afficher seulement la description du script
    -z  Forcer la réinstallation des scripts qui se basent sur shouldrun/setdone"
}

set_defaults runs

function runs_path_required() {
    die "Vous devez définir $1 dans ~/etc/default/runs"
}

action=runs
create_verbose=
init_sysinfos=--NOT-SET--
list_verbose=
run_as_root=
rscripts=()
recipes=()
runshost=
runssysname=
runssysdist=
runssysver=
runsbits=
runsscriptspath=
runsmodulespath=
runshostspath=
runsexportdir= # répertoire dans lequel exporter les fichiers
runsworkdir= # si runsexportdir n'est pas spécifié, répertoire de travail
runsvarsfile= # fichier contenant des définitions à charger
runs_init "$scriptdir"
parse_opts "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    --i '$action=init;create_verbose=false' \
    --init '$action=init;create_verbose=true' \
    --verify action=verify \
    --sysinfos: init_sysinfos= \
    -c,--c '$action=create;create_verbose=false' \
    --create '$action=create;create_verbose=true' \
    -l,--l '$action=list;list_verbose=false' \
    --list '$action=list;list_verbose=true' \
    --desc-only '$action=runs;RUNSACTION=runs_action_desc' \
    -d,--dump,-i,--info '$action=runs;RUNSACTION=runs_action_dump' \
    --export '$action=runs;RUNSACTION=runs_action_export' \
    -s run_as_root \
    -f: rscripts \
    -r: recipes \
    -h:,-H: runshost= \
    --host:: '$set@ runshost "$MYHOST"' \
    --sysname: runssysname= \
    --sysdist: runssysdist= \
    --sysver: runssysver= \
    --bits: runsbits= \
    --runsscriptspath: runsscriptspath= \
    --runsmodulespath: runsmodulespath= \
    --runshostspath: runshostspath= \
    --runsexportdir: runsexportdir= \
    --runsworkdir: runsworkdir= \
    --runsvarsfile: runsvarsfile= \
    -z RUNSRESET=1 \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

[ -n "$runsscriptspath" ] && RUNSSCRIPTSPATH="$runsscriptspath"
[ -n "$runsmodulespath" ] && RUNSMODULESPATH="$runsmodulespath"
[ -n "$runshostspath" ] && RUNSHOSTSPATH="$runshostspath"

if [ "$action" == runs \
    -o "$action" == init -o "$action" == verify \
    -o \( -n "$runshost" -a \( "$action" == create -o "$action" == list \) \) ]; then
    # configurer le nom d'hôté ici, parce qu'avec l'option -s, le fichier de
    # configuration ~/etc/default/runs ne sera plus disponible
    runs_initdomains
    runs_inithost "$runshost"
    host="$RUNSHOST"
    splithost "$host" hostname domain
    if [ -z "$domain" ]; then
        host="$(runs_find_host "$host")"
        splithost "$host" hostname domain
    fi
    [ -n "$domain" ] || host="$(runs_add_domain "$host")"
    if [ "$host" != "$RUNSHOST" ]; then
        enote "Le nom d'hôte '$RUNSHOST' a été automatiquement corrigé en '$host'"
        if ask_yesno "Acceptez-vous cette modification?" O; then
            RUNSHOST="$host"
            runshost="$host" # pour l'option -s
        fi
    fi
fi

if ! is_root && [ -n "$run_as_root" ]; then
    # reconstruire la ligne de commande
    args=()
    if [ "$action" == "init" ]; then
        args=("${args[@]}" --init)
    elif [ "$action" == "verify" ]; then
        args=("${args[@]}" --verify)
    elif [ "$action" == "create" ]; then
        is_yes "$create_verbose" &&
        args=("${args[@]}" --create) ||
        args=("${args[@]}" -c)
    elif [ "$action" == "runs" ]; then
        if [ "$RUNSACTION" == "runs_action_run" ]; then
            :
        elif [ "$RUNSACTION" == "runs_action_desc" ]; then
            args=("${args[@]}" --desc-only)
        elif [ "$RUNSACTION" == "runs_action_dump" ]; then
            args=("${args[@]}" --dump)
        else
            ewarn "RUNSACTION inconnu: $RUNSACTION"
        fi
    else
        ewarn "action inconnue: $action"
    fi
    for rscript in "${rscripts[@]}"; do
        args=("${args[@]}" -f "$rscript")
    done
    for recipe in "${recipes[@]}"; do
        args=("${args[@]}" -r "$recipe")
    done
    args=("${args[@]}" ${runshost:+-h "$runshost"}
        ${runssysname:+--sysname "$runssysname"} ${runssysdist:+--sysdist "$runssysdist"} ${runssysver:+--sysver "$runssysver"} ${runsbits:+--bits "$runsbits"}
        ${RUNSSCRIPTSPATH:+--runsscriptspath "$RUNSSCRIPTSPATH"}
        ${RUNSMODULESPATH:+--runsmodulespath "$RUNSMODULESPATH"}
        ${RUNSHOSTSPATH:+--runshostspath "$RUNSHOSTSPATH"}
        ${runsexportdir:+--runsexportdir "$runsexportdir"} ${runsworkdir:+--runsworkdir "$runsworkdir"}
        ${runsvarsfile:+--runsvarsfile "$runsvarsfile"}
        ${RUNSRESET:+-z})
    # puis relancer le script en root
    run_as root "${args[@]}" -- "$@"
fi

# si on donne un chemin relatif à '.' ou '..' ou un chemin absolu, forcer -f
if [ -z "${rscripts[*]}" -a -z "${recipes[*]}" -a -n "$1" ]; then
    if withpath "$1"; then
        rscripts=("$1")
        shift
    fi
fi

################################################################################
if [ "$action" == "init" -o "$action" == "verify" ]; then
    if [ "$init_sysinfos" == "--NOT-SET--" ]; then
        init_sysinfos=
    elif [ -z "$init_sysinfos" ]; then
        # Valeur par défaut de sysinfos pour l'hôte courant
        init_sysinfos="\
sysname=(${MYSYSNAME[*]})
sysdist=(${MYSYSDIST[*]})
sysver=(${MYSYSVER[*]})
bits=$MYBITS"
    fi

    __runs_setpath
    [ -n "${RUNSHOSTSDIRS[*]}" ] || runs_path_required RUNSHOSTSPATH
    runshost="${runshost:-$RUNSHOST}"

    resolv_ips __runs_ips "$runshost"
    if [ -z "${__runs_ips[*]}" ]; then
        eerror "$runshost ne semble pas être une adresse valide."
        ask_yesno "Voulez-vous réellement utiliser cette adresse, bien qu'elle n'existe pas dans le DNS?" N || die
    fi

    if [ "$action" == "init" ]; then
        etitle "$runshost" runs_initdir "$runshost" "$create_verbose" "$init_sysinfos"
    fi

################################################################################
elif [ "$action" == "create" ]; then
    if [ -n "$runshost" ]; then
        __runs_setpath
        [ -n "${RUNSHOSTSDIRS[*]}" ] || runs_path_required RUNSHOSTSPATH
        etitle "$runshost" runs_initdir "$runshost" "$create_verbose"
    fi

    # générer les scripts spécifié
    overwrite=no
    error=
    new_rscripts=()
    for rscript in "$@"; do
        etitle "$(ppath "$rscript")" runs_create_rscript "$rscript" "$runshost" "$create_verbose" "$overwrite" || error=1
    done

    # Puis éditer les fichiers nouvellement créés
    if [ -z "$error" -a -n "${new_rscripts[*]}" ]; then
        etitle "Edition des fichiers"
        if ask_yesno "Voulez-vous éditer le script nouvellement créé?" O; then
            "${EDITOR:-vi}" "${new_rscripts[@]}"
        fi
        eend
    fi

################################################################################
elif [ "$action" == "list" ]; then
    function find_items() {
        # Trouver tous les scripts runs et les répertoire uinstallable à partir
        # du répertoire $2, et mettre les chemins dans le tableau $1
        local dest="$1"
        local runsdir="$2"
        # $3 est un préfixe à enlever des chemins, pour qu'ils soient exprimés
        # relativement à un chemin qui vaut $2 par défaut
        local prefix="${3:-$runsdir/}"
        local items item

        [ -d "$runsdir" ] || return
        array_lsall items "$runsdir"
        for item in "${items[@]}"; do
            if __runs_is_runsscript "$item"; then
                array_add "$dest" "${item#$prefix}"
            elif [ -d "$item" ]; then
                find_items "$dest" "$item" "$prefix"
            fi
        done
    }

    __runs_setpath
    [ -n "${RUNSSCRIPTSDIRS[*]}" ] || runs_path_required RUNSSCRIPTSPATH
    [ -n "${RUNSMODULESDIRS[*]}" ] || runs_path_required RUNSMODULESPATH
    [ -z "$runshost" -o -n "${RUNSHOSTSDIRS[*]}" ] || runs_path_required RUNSHOSTSPATH

    splithost "$runshost" hostname domain

    # chercher tous les scripts runs et les répertoires uinstallables
    rscriptpaths=()
    if [ -n "$runshost" ]; then
        for runsdir in "${RUNSHOSTSDIRS[@]}"; do
            find_items rscriptpaths "$runsdir/$runshost/runs"
            find_items rscriptpaths "$runsdir/$domain/$hostname/runs"
            find_items rscriptpaths "$runsdir/$runshost"
            find_items rscriptpaths "$runsdir/$domain/$hostname"
        done
    fi
    for runsdir in "${RUNSSCRIPTSDIRS[@]}" "${RUNSMODULESDIRS[@]}"; do
        find_items rscriptpaths "$runsdir"
    done

    # les trier en enlevant les doublons, recalculer le nom canonique
    # RUNSSCRIPT, puis les afficher
    array_from_lines rscriptpaths "$(array_to_lines rscriptpaths | sort -u)"
    for rscriptpath in "${rscriptpaths[@]}"; do
        runs_find_scriptfile "$rscriptpath" "$runshost" || continue
        if [ -n "$*" ]; then
            # filtrer
            match=
            for filter in "$@"; do
                [[ "$filter" == *\** ]] || filter="$filter*"
                eval "[[ \"\$rscriptpath\" == $filter ]] && match=1"
            done
            [ -n "$match" ] || continue
        fi
        if is_yes "$list_verbose"; then
            echo "$rscriptpath $RUNSSCRIPT"
        else
            echo "$rscriptpath"
        fi
    done

################################################################################
elif [ "$action" == "runs" ]; then
    if [ -n "${rscripts[*]}" ]; then
        # sans l'option -f, il faut définir RUNS*PATH
        __runs_setpath
        [ -n "${RUNSSCRIPTSDIRS[*]}" ] || runs_path_required RUNSSCRIPTSPATH
        [ -n "${RUNSMODULESDIRS[*]}" ] || runs_path_required RUNSMODULESPATH
        [ -z "$runshost" -o -n "${RUNSHOSTSDIRS[*]}" ] || runs_path_required RUNSHOSTSPATH
    fi

    # initialisation
    [ -f "$runsvarsfile" ] && source "$runsvarsfile"
    runs_initsysinfos "$runssysname" "$runssysdist" "$runssysver" "$runsbits"
    runs_initworkdir "$runsexportdir" "$runsworkdir" "$scriptdir"

    if [ -n "${rscripts[*]}" ]; then
        # scripts individuels
        for rscript in "${rscripts[@]}"; do
            etitle "$(ppath "$rscript")" \
                runs_rscript "$rscript" "$@" || die "Arrêt du déploiement"
        done

    elif [ -n "${recipes[*]}" ]; then
        # fichiers de recette
        for recipe in "${recipes[@]}"; do
            [ -f "$recipe" ] || die "$recipe: fichier introuvable"
            etitle "$(ppath "$recipe")" \
                runs_recipe "$recipe" "$@" || die "Arrêt du déploiement"
        done

    else
        # suite de scripts et/ou de recettes séparés par --
        # note: si on spécifie plusieurs scripts séparés par --, il faut le
        # faire de cette manière:
        #     runs -- script1 ... -- script2 ... -- @recipe ...
        # le premier -- est important, il permet de ne pas traiter les arguments
        # suivants, et donc de ne pas fusionner les valeurs script1 avec script2
        while [ -n "$1" ]; do
            if [ "$1" == -- ]; then
                shift
                continue
            elif [ "${1#@}" != "$1" ]; then
                # recette à chercher dans RUNSSCRIPTSPATH
                # prendre les arguments jusqu'à --
                recipe="${1#@}"; shift
                args=()
                while [ -n "$1" -a "$1" != "--" ]; do
                    args=("${args[@]}" "$1")
                    shift
                done
                etitle "@$recipe" \
                    runs_recipepath "$recipe" "${args[@]}" || die "Arrêt du déploiement"
                shift
            else
                # script à chercher dans RUNSSCRIPTSPATH
                # prendre les arguments jusqu'à --
                args=()
                while [ -n "$1" -a "$1" != "--" ]; do
                    args=("${args[@]}" "$1")
                    shift
                done
                etitle "${args[0]}" \
                    runs_rscriptpath "${args[@]}" || die "Arrêt du déploiement"
            fi
        done
    fi

    # traitement après déploiement
    runs_after_export
fi
