#!/usr/bin/env bash
# shellcheck disable=SC1090,SC2046,SC2206,SC2207

set -euo pipefail

export LANG=C.UTF-8

global_basedir=${global_basedir:-$(realpath "$(dirname "$0")")}

# By default the new genesis creation is disabled
: "${WB_MODULAR_GENESIS:=0}"

# Separate dataset and protocols caching using create-testnet-data
: "${WB_GENESIS_RIPPER:=0}"

# By default, enable the new database storage backend for `locli`
: "${WB_LOCLI_DB:=1}"

. "$global_basedir"/lib.sh
. "$global_basedir"/env.sh
. "$global_basedir"/chaindb.sh
. "$global_basedir"/chain-fragment.sh
. "$global_basedir"/manifest.sh
. "$global_basedir"/nomad.sh
. "$global_basedir"/publish.sh
. "$global_basedir"/run.sh
. "$global_basedir"/scenario.sh

. "$global_basedir"/analyse/analyse.sh
. "$global_basedir"/profile/profile.sh
. "$global_basedir"/genesis/genesis.sh
. "$global_basedir"/genesis/genesis-jq.sh
. "$global_basedir"/genesis/genesis-modular.sh
. "$global_basedir"/genesis/genesis-ripper.sh
. "$global_basedir"/topology/topology.sh

. "$global_basedir"/evaluate/evaluate.sh

. "$global_basedir"/backend/backend.sh
. "$global_basedir"/backend/nomad.sh
. "$global_basedir"/backend/nomad/cloud.sh
. "$global_basedir"/backend/nomad/exec.sh
. "$global_basedir"/backend/supervisor.sh

usage_main() {
    set +x
    test $# -lt 1 || msg "Unknown op: $1"
     __usage "OP" "Top-level OPs" <<EOF
    $(red profile)      ($(red p))      Cluster profile ops.   Default subop is $(yellow "${profile_default_op:?"profile_default_op not set"}")
    $(red run)          ($(red r))      Managing cluster runs. Default subop is $(yellow "${run_default_op:?"run_default_op not set"}")
    $(red analyse)      ($(red a))      Analyse cluster runs.  Default subop is $(yellow "${analyse_default_op:?"analyse_default_op not set"}")
    $(red publish)      ($(red u))      Bench data publish.    Default subop is $(yellow "${publish_default_op:?"publish_default_op not set"}")

    $(blue Secondary top-level OPs):

    $(red chaindb)      ($(red c))      ChainDB
    $(red chain-fragment) ($(red cf))   Export a run as an ouroboros-consensus-tools chain fragment
    $(red genesis)      ($(red g))      Genesis
    $(red nomad)        ($(red n))      Nomad and Vault
    $(red topology)     ($(red t))      Topology generation
    $(red evaluate)     ($(red e))      Evaluate workbench module-based configuration

    $(white wb) $(blue options):

    $(helpopt --trace / --debug)        Enable workbench tracing (for error reports) ('set -x')
    $(helpopt --enable-modular-genesis) Enable NixOS modules based genesis configuration
    $(helpopt --help)                   This short help
    $(helpopt --help-full / --aliases)  Extended help (extra ops & subop aliases)
EOF
}

usage_extra() {
	cat >&2 <<EOF
  $(blue Extra top-level OPs):

    $(red start $(green \[FLAGS..]))       Start a run of profile chosen at 'nix-shell' entry
    $(red finish)                Finish an active cluster run
    $(red scenario)     ($(red s))      Run scenario control
    $(red backend)      ($(red b))      Backend calls
    $(red call) $(green ARGS..)       Call internal functions with arguments

  $(blue List of top-level) $(yellow subop) $(blue aliases, per) $(red op).  $(blue For detailed help:) $(red OP --help):

  ..$(red profile):$(color yellow)
          profile-names                   names             lsp
          profile-json                                      pj
          profile-describe                pdesc             pd
          profile-node-specs              node-specs        specs

  ..$(red run):$(color yellow)
          list-runs                       runs              lsr
          list-remote                     remote            lsrr
          list-verbose                    verb              lsrv
          list-verbose-remote             rverb             lsrv
          list-sets                       sets              lss
          list-sets-remote                rsets             lssr
          set-add                         add               sa
          run-or-set                                        ros
          list-pattern                    lspat             lsrp
          fix-cardano-ops-run-structure   fix-cardano-ops   fcors  (also: fix-legacy / flrs)
          fetch-run                       fetch             fr
          fetch-analysis                  fa

  ..$(red analyse):$(color yellow)
          compare                                           cmp
          variance                                          var
          standard                        full              std
          block-propagation               blockprop         bp
          performance                                       perf
          performance-host                perf-host
          trace-frequencies               trace-freq        freq tf
          chain-rejecta-reasons           chain-rejecta     rejecta
  ..$(red publish):$(color yellow)
          local                                             loc
$(color white)
EOF
}

usage_start()
{
    cat <<EOF
$(red USAGE:)
   $(helpcmd start \[FLAGS..])

   $(blue Flags):

      $(helpopt --batch-name NAME)               Override the batch name (default: $(green "${batchName:-default}"))
      $(helpopt --era-name NAME)                 $(red REQUIRED). Era name (workbench-level; embedded in the run tag).
                                                 One of: byron shelley allegra mary alonzo babbage conway dijkstra

      $(helpopt --iterations \| --times \| --iter \| -n ITERATIONS)
                                                 Run this many iterations of the profile.

      $(helpopt --profile-data DIR)              $(red REQUIRED). Path to generic profile data.
      $(helpopt --backend-data DIR)              Path to backend-specific data.

      $(helpopt --idle)                          Use the $(blue idle) scenario.
      $(helpopt --scenario \| -s SCENARIO)       Use the specified scenario.
                                                 See: $(helptopcmd wb scenario --help)

      $(helpopt --no-analysis)                   Do not run analysis at the run's end.
      $(helpopt --analysis-can-fail)             Do not fail on failed analysis.
      $(helpopt --filters F,F,F,F)               Comma-separated list of filters, instead of profile-specified defaults.

      $(helpopt --supervisor \| --backend-supervisor)
                                                 Use the $(blue supervisor) backend.

      $(helpopt --trace \| --debug)              Trace the start-cluster script
      $(helpopt --trace-wb \| --trace-workbench) Trace the workbench script
      $(helpopt --verbose)                       Print more of what's going
      $(helpopt --help)                          This help message
EOF
}

start() {
    local batch_name=
    local era_name=
    local profile_data=
    local backend_data=
    local genesis_cache_entry=
    local node_source=.
    local node_rev=
    local ident=${ID:-}
    local cabal_mode=
    local prebuild_done=
    local manifest="{}"
    local iterations=1
    local no_retry_failed_runs=t
    local no_analysis=
    local analysis_can_fail=

    local run_args=()
    local backend_args=()
    local run_start_args=()
    local analyse_args=(--dump-machviews)
    while test $# -gt 0
    do case "$1" in
        --batch-name )                   batch_name=$2;   shift;;
        --era-name )                     era_name=$2;     shift;;
        --ident )                        ident=$2; shift;;
        --iterations | --times | --iter | -n ) iterations=$2; no_retry_failed_runs=; shift;;
        --cache-dir )                    setenvjqstr 'cacheDir' "$2"; shift;;
        --base-port )                    setenvjq    'basePort' "$2"; shift;;

        --backend-data )                 backend_data=$2; shift;;

        --cabal-mode | --cabal )         cabal_mode=t;;
        --node-source )                  node_source=$2; shift;;
        --node-rev )                     node_rev=$2; shift;;

        ## Allocate inputs (captured into named locals, not a forwarding array).
        --genesis-cache-entry )          genesis_cache_entry=$2; shift;;
        --profile-data )                 profile_data=$2; shift;;
        -- )                             shift ## Bulk-pass to backend:
                                         backend_args=("$@")
                                         break;;

        ## Start
        --idle )                         run_start_args+=($1);;
        --scenario | -s )                run_start_args+=($1 $2); shift;;

        ## Analyse
        --no-analysis )                  no_analysis=true;;
        --analysis-can-fail | -af )      analysis_can_fail=t;;
        --dump-logobjects )              analyse_args+=($1);;
        --filters )                      analyse_args+=($1 $2); shift;;
        --locli-file )                   export WB_LOCLI_DB=0;;         # use the legacy file backend of `locli`

        ## Aux
        --verbose | -v )                 export verbose=t;;
        --trace | --debug )              set -x;;
        --trace-wb | --trace-workbench ) export WB_EXTRA_FLAGS=--trace;;

        --help )                         usage_start
                                         exit 1;;
        * ) fatal "while parsing remaining '$(blue wb start)$(red \' args):  $(white "$@")";;
       esac; shift; done

    ## Only validate what wb start itself consumes BEFORE the call to `run
    ## allocate`. Inputs that allocate consumes (batch, era, profile-data,
    ## manifest, genesis-cache-entry) are validated inside allocate so the
    ## contract lives in one place.
    test -n "${WB_BACKEND_NAME:-}" \
        || fatal "wb start: WB_BACKEND_NAME is not set (pass --supervisor or --nomad, or enter a workbench shell)"
    test -n "$backend_data" \
        || fatal "wb start: --backend-data is required"
    test -n "$profile_data" \
        || fatal "wb start: --profile-data is required (we read .name from it for the iteration banner)"
    test -f "$profile_data/profile.json" \
        || fatal "wb start: missing $profile_data/profile.json"

    if test -n "$cabal_mode"
    then . $(dirname "$0")/lib-cabal.sh
    fi

    backend validate
    backend assert-stopped
    backend setenv-defaults "$backend_data"

    ## Read profile.json once for our own display + analyse-decision use; the
    ## tag's profile_name is derived independently inside `run allocate`.
    local profile_name analysis_type
    profile_name=$(jq '.name' -r "$profile_data/profile.json")
    analysis_type=$(jq '.analysis.type' -r "$profile_data/profile.json")

    local top_i runs=()
    for ((top_i=0; top_i<iterations; top_i++))
    do newline
       if test -n "$cabal_mode" -a -z "$prebuild_done"
       then
            (
              # This step is resource intensive so we use a lockfile to avoid
              # running it in parallel to a benchmark.
              acquire_lock
              workbench-prebuild-executables
            )
            prebuild_done=t
       fi

       # manifest generation needs the build plan that is created in the previous step
       progress "manifest" "component versions:"
       local manifest
       manifest=$(manifest collect-from-checkout  \
                      "$node_source"              \
                      "$node_rev"                 \
                      "${WB_MANIFEST_PACKAGES[@]}"
                 )
       manifest render "$manifest"

       progress "top-level" "profile $(with_color 'yellow' $profile_name), iteration $(with_color 'white' $((top_i+1))) of $(with_color 'yellow' $iterations), identified as:  $(if test -n "$ident"; then echo $(white $ident); else echo $(red UNIDENTIFIED); fi)"

       ## All-named-flags call to `run allocate`. No positionals, no env
       ## smuggle: every input is explicit. allocate itself validates them.
       ## Flag names match the ones `wb start` itself accepts.
       local allocate_argv=(
           --batch-name    "$batch_name"
           --profile-data  "$profile_data"
           --era-name      "$era_name"
           --backend-name  "$WB_BACKEND_NAME"
           --manifest      "$manifest"
       )
       test -n "$genesis_cache_entry" \
           && allocate_argv+=(--genesis-cache-entry "$genesis_cache_entry")
       allocate_argv+=(-- "${backend_args[@]}")
       run "${run_args[@]}" allocate "${allocate_argv[@]}"
       local run
       run=$(run current-tag)
       runs+=($run)

       current_run_path=$(run current-path)
       mkdir -p "$current_run_path"

       run "${run_args[@]}" start "${run_start_args[@]}" "$run"

       if test -n "$no_analysis" -o "$analysis_type" = null; then continue; fi

       progress "top-level | analysis" "analysis type $(with_color yellow "$analysis_type") on $(with_color white "$run")"

       local runspec
       if test -n "$ident"; then
         runspec="$ident":$run
       else
         runspec=$run
       fi

       if analyse "${analyse_args[@]}" "$analysis_type" "$runspec"; then
         progress "run | analysis" "done for $(white "$runspec")"
       else
         if test -n "$analysis_can_fail" -a -z "$no_retry_failed_runs"; then
           progress "run | analysis" "log processing failed, but --analysis-can-fail prevents failure:  $(with_color red "$runspec")"
           iterations=$((iterations + 1))
         else
           fail "analysis failed:  $run"
           false
         fi
       fi
    done

    if test -z "$no_analysis" -a "$analysis_type" != null -a "$iterations" -gt 1
    then analyse variance "${runs[@]}"
    fi
}

finish()
{
    while test $# -gt 0
    do case "$1" in
        --trace | --debug )              set -x;;
        --trace-wb | --trace-workbench ) export WB_EXTRA_FLAGS=--trace;;
        * ) break;; esac; shift; done

    run stop $(run current-tag)
}

while test $# -gt 0
do case "$1" in
       --nomad      | --backend-nomad )      export WB_BACKEND_NAME=nomad      ;;
       --supervisor | --backend-supervisor ) export WB_BACKEND_NAME=supervisor ;;
       --enable-modular-genesis )            export WB_MODULAR_GENESIS=1  ;;

       --cls )                          echo -en "\ec">&2;;
       --verbose | -v )                 export verbose=t;;
       --trace | --debug )              set -x;;
       --trace-wb | --trace-workbench ) export WB_EXTRA_FLAGS=--trace;;
       --help )
           usage_main; exit 1;;
       --help-full | --help-all | --help-extra | --help-aliases | --aliases )
           usage_main; usage_extra; exit 1;;
       * ) break;; esac; shift; done

main() {
    # if $1 is not set ...
    if [ ! -v 1 ]; then
      usage_main
      exit 1
    fi
    local op=$1
    shift

    case "$op" in
        ## Public, primary:
        #
        start            )        start               "$@";;
        finish           )        finish              "$@";;

        ## Public, secondary:
        profile      | profiles | prof | ps | p )
                                  profile             "$@";;
        run          | r )        run                 "$@";;
        analyse      | a )        analyse             "$@";;
        publish      | u )        publish             "$@";;
        evaluate     | e )        evaluate            "$@";;

        ## Public, internals-y:
        chaindb      | c )        chaindb             "$@";;
        chain-fragment | cf )     chain_fragment      "$@";;
        genesis      | g )        genesis             "$@";;
        manifest     | m )        manifest            "$@";;
        nomad        | n )        wb_nomad            "$@";;
        topology     | topo | t ) topology            "$@";;

        ## 'profile' aliases:
          profile-names                 | names           | lsp           \
        | profile-json                                    | pj            \
        | profile-describe              | pdesc           | pd            \
        | profile-node-specs            | node-specs      | specs         \
          )                       profile "$op"        "$@";;

        ## 'run' aliases:
          list-runs                     | runs            | lsr           \
        | list-remote                   | remote          | lsrr          \
        | list-verbose                  | verb            | lsrv          \
        | list-verbose-remote           | rverb           | lsrrv         \
        | list-sets                     | sets            | lss           \
        | list-sets-remote              | rsets           | lssr          \
        | set-add                       | add             | sa            \
        | run-or-set                                      | ros           \
        | list-pattern                  | lspat           | lsrp          \
        | fix-cardano-ops-run-structure | fix-cardano-ops | fcors         \
        | fix-legacy-run-structure      | fix-legacy      | flrs          \
        | fetch-run                     | fetch           | fr            \
        | fetch-analysis                | fa                              \
        | trim                                                            \
        | package                       | pack                            \
        | set-identifier                                  | setid         \
          )                       run $op             "$@";;

        ## 'analyse' aliases:
          compare                                         | cmp           \
        | variance                                        | var           \
        | standard                      | full            | std           \
        | block-propagation             | blockprop       | bp            \
        | performance                                     | perf          \
        | performance-host              | perf-host                       \
        | trace-frequencies             | trace-freq      | freq | tf     \
        | chain-rejecta-reasons         | chain-rejecta   | rejecta       \
        | render-comparison-pdf         | render-pdf      | pdf           \
          )                       analyse "$op"        "$@";;

        ## Internals:
        #
        scenario     | s )        scenario            "$@";;
        backend      | b )        backend             "$@";;
        call )                                        "$@";;
        path )                    echo "$global_basedir"  ;;

        ## Bail for help:
        * )
          usage_main "$op"
          exit 1
          ;;
    esac
}

main "$@"
