#!/usr/bin/bash
#shellcheck disable=SC1090

. "$(sfpath)" || exit 3

shellfu import pretty

shellfu import saturnin

shellfu import mkittool

#shellcheck disable=SC2034
_MKITTOOL_THINK=jobthink

usage() {
    mkusage "$@" \
            "[options] jobname[:branch]..." \
            "[options] -g|--group group" \
            "[options] -a|--all-jobs" \
            "[options] -l|--list-jobs" \
            "[options] -L|--list-groups" \
            "[options] --kill-cache" \
         -o \
            "-B|--rpmbuild-args args..  add given arguments to rpmbuild command" \
            "-C|--ssh-creds [user@]host  use given SSH credentials" \
            "-N|--nocheck       skip %check secion when building" \
            "-S|--only-srpms    only collect SRPMS" \
            "-R|--only-rpms     only collect RPMS" \
            "-w|--workdir DIR   use working directory DIR" \
            "-s|--storage DIR   use storage directory DIR" \
         -c \
            "-l|--list-jobs     list jobs and exit" \
            "-L|--list-groups   list groups and exit" \
            "-g|--group GROUP   run jobs in group GROUP" \
            "-a|--all-jobs      run all jobs" \
            "--kill-cache       remove all cached data and exit"
}

any2bool_true() {
    #
    # Coerce value into safely 'true' or 'false', prefer 'true'
    #
    __any2bool_UNSAFE "$1" true
}

any2bool_false() {
    #
    # Coerce value into safely 'true' or 'false', prefer 'false'
    #
    __any2bool_UNSAFE "$1" false
}

__any2bool_UNSAFE() {
    #
    # Coerce value into safely 'true' or 'false', prefer $2
    #
    # Note that we can't recover from bad $2 so DON'T use this
    # directly; use any2bool_true or any2bool_false.
    #
    local value="$1"
    local safe="$2"
    debug -v value safe
    case "$value" in
        yes)    echo true ;;
        no)     echo false ;;
        "")     echo "$safe" ;;
        *)      warn "unknown value, assuming: $safe instead of '$value'"
                echo "$safe";;
    esac
}

jobthink() {
    #
    # Think during a job
    #
    think "$JobSpec: $1"
}

expand_path() {
    #
    # Expand common variables or '~' in path $1
    #
    local path="$1"
    local cmd
    path=$(sed "s|^~|$HOME|" <<< "$path")
    cmd='echo "'"$path"'"'
    debug -v path cmd
    bash -n <<<"$cmd" || die
    env -i bash -c "$cmd"
}

get_conf() {
    #
    # Obtain config value $1 for $JobName
    #
    # Inherit from job spec, parent group spec or `[default]`
    #
    local key="$1"
    local value
    while true; do
        debug "plan A: from job: $key"
        value=$(saturnin__conf -j "rpmstuff.job.$JobName.$key")
        test -n "$value" && break
        debug "plan B: from provided group: $key"
        test -n "$Group" \
         && value=$(saturnin__conf -j "rpmstuff.group.$Group.$key")
        test -n "$value" && break
        debug "plan C: from parent group: $key"
        value=$(saturnin__conf -j "rpmstuff.group.$(pick_pgroup).$key")
        test -n "$value" && break
        debug "plan D: from default: $key"
        value=$(saturnin__conf -j "rpmstuff.default.$key")
        test -n "$value" && break
        debug "all plans failed: $key"
        return 1
    done
    echo "$value"
}

get_conf_ml() {
    #
    # Obtain multi-line config value $1 for $JobName
    #
    # Join from job spec, parent group spec or `[default]`
    #
    get_conf "$@"
}

is_member() {
    #
    # True if job $1 is member of group $2
    #
    local job="$1"
    local grp="$2"
    saturnin__conf -j "rpmstuff.group.$grp.job" \
      | grep -qxFe "$job"
}

ls_groups() {
    #
    # List all groups
    #
    saturnin__conf -j -P \
      | grep '^rpmstuff.group\..*\.job$' \
      | uniq \
      | sed -e 's/^rpmstuff.group.//; s/\.job$//'
}

ls_jobs_todo() {
    #
    # Print list of jobs to do
    #
    local arg
    if $all_jobs;
    then
        saturnin__conf -j "rpmstuff.default.job"
    elif test -n "$Group";
    then
        saturnin__conf -j "rpmstuff.group.$Group.job"
    else
        for arg in "$@";
        do
            debug -v arg
            test "$arg" == "-a" \
             && all_jobs=true ls_jobs_todo \
             && continue
            echo "$arg"
        done
    fi
}

maybe_clone() {
    #
    # Clone if needed
    #
    test -d "$RepoName"/.git && return 1
    jobthink "cloning local repo"
    git clone -o "$Remote" -b "$Branch" "$Uri" "$RepoName"
}

parents_of() {
    #
    # List groups job $1 belongs to
    #
    local jobname="$1"
    local group
    ls_groups \
      | while read -r group;
        do
            is_member "$jobname" "$group" && echo "$group"
        done \
      | grep .
}

pick_pgroup() {
    #
    # Which group $JobName should inherit from
    #
    local parents
    parents=$(parents_of "$JobName")
    test "$(wc -l <<<"$parents")" -gt 1 \
     && warn "inheritance will use only first group: $JobName is in $(paste -sd, <<<"$parents")"
    debug -v parents
    head -1 <<<"$parents"
}


#       # ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, #
# STEPS #                                                                 #
#       # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' #

check_params() {
    #
    # Make sure necessary params are set
    #
    local param
    for param in JobName Branch Uri RepoName SSHCreds Storage RPMTypes DoScrapStorage;
    do
        test -n "${!param}" || die "could not determine value: $param for $JobSpec"
    done
}

run_build() {
    #
    # Get to the build machine and run the build
    #
    local nocheck=""
    $NoCheck && nocheck="--nocheck"
    jobthink "cleaning up build dir"
    ssh "$SSHCreds" '
        rm -f .rpmmacros
        rm -rf rpmbuild
        rpmdev-setuptree
    ' || die
    jobthink "rebuilding RPMs"
    mkittool__pushd "$WorkDir"
    printf '%s' "$RPMMacros" >_rpmmacros
    scp "_rpmmacros"         "$SSHCreds:.rpmmacros"        || die
    scp "$RepoName"/*.tar.gz "$SSHCreds:rpmbuild/SOURCES"  || die
    scp "$RepoName"/*.spec   "$SSHCreds:rpmbuild/SPECS"    || die
    mkittool__popd "$WorkDir"
    #shellcheck disable=SC2029
    ssh "$SSHCreds" "
        echo 1000 >/proc/self/oom_score_adj
        cd rpmbuild
        rpmbuild $nocheck $RpmbuildArgs -ba SPECS/*.spec
    " || die
}

maybe_scrap_storage() {
    #
    # If allowed and not scrapped yet (look up scrap_log), scrap $Storage
    #
    local fullpath
    fullpath=$(readlink -m "$Storage")
    $DoScrapStorage                     || return 1
    grep -qsxe "$fullpath" "$ScrapLog"  && return 0
    jobthink "scrapping storage: $Storage"
    rm -f "$fullpath"/*.rpm
    echo "$fullpath" >> "$ScrapLog"
}

collect_rpms() {
    #
    # Collect fresh built rpms
    #
    jobthink "collecting: $RPMTypes"
    mkdir -p "$Storage"                               || die
    debug -v Storage
    grep -qw -e SRPMS -e RPMS <<<"$RPMTypes" \
     || die "no known rpm types (SRPMS, RPMS) selected to collect: $RPMTypes"
    grep -qw SRPMS <<<"$RPMTypes" && {
        scp "$SSHCreds":rpmbuild/SRPMS/*.rpm  "$Storage" || die
    }
    grep -qw RPMS <<<"$RPMTypes" && {
        scp "$SSHCreds":rpmbuild/RPMS/*/*.rpm "$Storage" || die
    }
}



#         # ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, #
# ACTIONS #                                                               #
#         # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' #

kill_cache() {
    #
    # Recklessly remove cache (including all workdirs)
    #
    # Workdirs may break in some cases invloving lots of rebasing and
    # resetting.
    #
    # Note that this will only kill the default cache; i.e. workdir
    # provided via -w|--workdir option will not be affected.
    #
    test -n "$HOME" || die "panic, empty HOME"
    rm -rf "$CacheDir"
    think "Cache is dead now.  You asked for it."
}

list_jobs() {
    #
    # Just print list of jobs
    #
    saturnin__conf -j -S \
      | grep '^rpmstuff.job\.' \
      | cut -d. -f3
}

list_groups() {
    #
    # Just print list of groups
    #
    saturnin__conf -j -S \
      | grep '^rpmstuff.group\.' \
      | cut -d. -f3
}

run_posthook() {
    #
    # Run post_hook, if any
    #
    test -n "$PostHook" || return 0
    think "running post-hook"
    bash -n <<<"$PostHook" || die "bad post_hook syntax: $PostHook"
    eval "$PostHook"
}

run_finalhook() {
    #
    # Run final_hook, if any
    #
    test -n "$FinalHook" || return 0
    think "running post-hook"
    bash -n <<<"$FinalHook" || die "bad final_hook syntax: $FinalHook"
    eval "$FinalHook"
}

run_job() {
    #
    # Run a single job $1
    #
    local JobName           # name of job to look for in config
    local Uri               # URI to fetch the branch from
    local Branch            # chosen branch
    local RepoName          # local repo name (inside workdir)
    local SSHCreds          # user@name to connect via ssh/scp/...
    local RPMMacros         # body of .rpmmacros to deploy first
    local MakeVars          # vars to add to 'make rpmstuff'
    local DoScrapStorage    # delete $Storage  before collecting?
    local PostHook          # post hook command

    test -n "$JobSpec" || return

    # collect job params
    #
    case "$JobSpec" in
        *:*) JobName=${JobSpec%:*}; Branch=${JobSpec#*:}      ;;
        *)   JobName=${JobSpec};    Branch=$(get_conf branch) ;;
    esac
    Branch=$(mkittool__parse_branch "$Branch") || return 3
    debug -v JobName Branch
    Uri=$(get_conf uri)
    Uri=$(expand_path "$Uri")   # uri can be path specified w/ ~ or '$HOME'
    RepoName=$(get_conf reponame) || {
        RepoName=$(basename "$Uri")
        RepoName=${RepoName%.git}
    }
    SSHCreds=${CustomSSHCreds:-$(get_conf ssh_creds)}
    RpmbuildArgs=${RpmbuildArgs:-$(get_conf rpmbuild_args)}
    RPMTypes=${RPMTypes:-$(get_conf rpmtypes)}
    RPMTypes=${RPMTypes:-SRPMS RPMS}
    RPMTypes=${RPMTypes^^}
    RPMMacros=$(get_conf_ml rpmmacros)
    MakeVars=($(get_conf makevars)) \
     || MakeVars=("PREFIX=/usr")
    DoScrapStorage=$(any2bool_false "$(get_conf scrap_storage)")
    case "$Storage" in
        "") Storage=$(expand_path "$(get_conf storage)") ;;
        *)  DoScrapStorage=false ;;
    esac
    PostHook=$(get_conf "post_hook")

    check_params
    think -v \
        WorkDir \
        RepoName Uri Branch \
        SSHCreds \
        MakeVars RPMMacros RpmbuildArgs NoCheck \
        Storage DoScrapStorage RPMTypes \
        PostHook

    # do the steps
    #
    mkittool__prepare_repo "$Uri" "$WorkDir" "$RepoName" "$Branch"
    mkittool__make_stuff "$WorkDir/$RepoName" rpmstuff "${MakeVars[@]}"
    run_build
    maybe_scrap_storage
    collect_rpms
    run_posthook
}

main() {
    local CacheDir          # local cache dir
    local WorkDir           # git repos cache
    local ScrapLog          # log to avoid double storage scrap
    local Storage           # where to store rpms
    local JobSpec           # job to start...
    local all_jobs=false    # ...AND/OR all jobs?
    local NoCheck=false     # Add --nocheck parameter (skip %check part)
    local RPMTypes          # types of RPMs to collect
    local RpmbuildArgs      # extra rpmbuild arguments
    local Group             # group to run jobs from
    local CustomSSHCreds    # custom ssh credentials (override INI file)
    local joblist=()        # list of jobs given on command line
    local FinalHook         # final hook, after all jobs
    local Remote            # git remote remote name for work repo clones
    declare -a joblist
    Remote=__build_rpmstuff__upstream__
    CacheDir=$(saturnin__get cache-home) || die "could not figure out cache dir"
    WorkDir=$CacheDir/workdir
    ScrapLog=$CacheDir/scrap_log


    # read params
    #
    while true; do case "$1" in
        -B|--rpmbuild-args) RpmbuildArgs="$2";   shift 2 || usage -w "missing rpmbuild arguments" ;;
        -C|--ssh-creds)   CustomSSHCreds="$2";   shift 2 || usage -w "missing credentials" ;;
        -S|--only-srpms)  RPMTypes=SRPMS;        shift ;;
        -R|--only-rpms)   RPMTypes=RPMS;         shift ;;
        -A|--all-rpms)    RPMTypes=RPMS\ SRPMS;  shift ;;
        -N|--nocheck)     NoCheck=true;          shift ;;
        -a|--all-jobs)    all_jobs=true;         shift ;;
        -g|--group)       Group="$2";            shift 2 || usage -w "no GROUP?" ;;
        --kill-cache)     kill_cache;            exit ;;
        -l|--list-jobs)   list_jobs;             exit ;;
        -L|--list-groups) list_groups;           exit ;;
        -w|--workdir)     WorkDir="$2";          shift 2 || usage -w "no DIR?" ;;
        -s|--storage)     Storage="$2";          shift 2 || usage -w "no DIR?" ;;
        -*)               usage -w "unknown argument: $1" ;;
        "")               break ;;
        *)                joblist+=( "$1"); shift ;;
    esac done
    $all_jobs || test -n "$Group" || test ${#joblist[@]} -gt 0 || usage

    # collect and check global params
    #
    test -n "$WorkDir" || WorkDir=$(get_conf workdir)
    debug -v WorkDir
    test -d "$WorkDir" || mkdir -p "$WorkDir" || die
    if test -n "$Storage";
    then
        mkdir -p "$Storage" || die
        Storage=$(readlink -f "$Storage")
    fi
    FinalHook=$(get_conf final_hook)
    debug -v Storage RPMTypes Group CustomSSHCreds FinalHook

    # do steps
    #
    for JobSpec in $(ls_jobs_todo "${joblist[@]}");
    do
        think "starting job: $JobSpec"
        debug -v JobSpec
        run_job
    done
    run_finalhook
    rm -f "$ScrapLog"
}

main "$@"
