#!/bin/zsh -f

mkdir -p _bm-logs

BENCHMARK_SUITE=ab/syntcomp21/crit
TIMEOUT_FACTOR=1.7

# -fuse-linker-plugin requires the gold linker, which is unavailable on macOS
if [[ "$(uname)" == Darwin ]]; then
    opt='-march=native -Ofast -flto -pipe -DNO_VERBOSE -DNDEBUG'
else
    opt='-march=native -Ofast -flto -fuse-linker-plugin -pipe -DNO_VERBOSE -DNDEBUG'
fi
opt_justtest=''

declare -A confs

# WARNING: The actual defaults are set in configuration.hh
# defaults=$(<<EOF 
# -DDEFAULT_K=255
# -DDEFAULT_KMIN=2
# -DDEFAULT_KINC=3
# -DDEFAULT_UNREAL_X='UNREAL_X_BOTH'
# -DVECTOR_ELT_T='char'
# -DSTATIC_ARRAY_MAX='300'
# -DSTATIC_MAX_BITSETS='8ul'
# -DSIMD_IS_MAX='true'
# -DAUT_PREPROCESSOR='aut_preprocessors::surely_losing'
# -DBOOLEAN_STATES='boolean_states::forward_saturation'
# -DIOS_PRECOMPUTER='ios_precomputers::standard'
# -DACTIONER='actioners::standard'
# -DINPUT_PICKER='input_pickers::critical_pq'
# -DARRAY_AND_BITSET_DOWNSET_IMPL='vector_backed'
# -DVECTOR_AND_BITSET_DOWNSET_IMPL='vector_backed'
# EOF
#         )

# Experimentally determined
best=$(<<EOF
-DDEFAULT_KMIN=2
-DDEFAULT_KINC=3
-DDEFAULT_UNREAL_X='UNREAL_X_BOTH'
-DAUT_PREPROCESSOR='aut_preprocessors::standard'
-DBOOLEAN_STATES='boolean_states::forward_saturation'
-DIOS_PRECOMPUTER='ios_precomputers::powset'
-DINPUT_PICKER='input_pickers::critical'
-DSIMD_IS_MAX='false'
-DARRAY_AND_BITSET_DOWNSET_IMPL='vector_backed'
-DVECTOR_AND_BITSET_DOWNSET_IMPL='vector_backed'
-DDECOMPOSE_SPEC=0
EOF
    )

confs=(
    # Dummy configuration: does not actually change any acacia-bonsai build
    # flags, but tells the benchmark step below to run the `ltlsynt/…`
    # meson suites (i.e. ltlsynt on the same inputs) instead of `ab/…`.
    [ltlsynt]=" "
    # These are variations on the default configuration
    [base]=" "
    # [kmin5_kinc2]="-DDEFAULT_KMIN=5 -DDEFAULT_KINC=2"
    # [kmin5_kinc1]="-DDEFAULT_KMIN=5 -DDEFAULT_KINC=1"
    # [kmin2_kinc1]="-DDEFAULT_KMIN=2 -DDEFAULT_KINC=1"
    # [kmin2_kinc3]="-DDEFAULT_KMIN=2 -DDEFAULT_KINC=3"
    # [x_is_form]="-DDEFAULT_UNREAL_X=UNREAL_X_FORMULA"
    # [x_is_aut]="-DDEFAULT_UNREAL_X=UNREAL_X_AUTOMATON"
    [base_nosimd]="-DNO_SIMD"
    [base_simdnomax]="-DSIMD_IS_MAX=false"
    [base_autpreproc_standard]="-DAUT_PREPROCESSOR=aut_preprocessors::standard"
    [base_autpreproc_nopreproc]="-DAUT_PREPROCESSOR=aut_preprocessors::no_preprocessing"
    [base_booleanstates_none]="-DBOOLEAN_STATES=boolean_states::no_boolean_states"
    [base_noiosprecom_delegate]="-DIOS_PRECOMPUTER=ios_precomputers::delegate -DACTIONER='actioners::no_ios_precomputation'"
    [base_iosprecom_fake_vars]="-DIOS_PRECOMPUTER=ios_precomputers::fake_vars"
    [base_iosprecom_powset]="-DIOS_PRECOMPUTER=ios_precomputers::powset"
    [base_iosprecom_mona]="-DIOS_PRECOMPUTER=ios_precomputers::mona"
    [base_inputpicker_critical_pq]="-DINPUT_PICKER=input_pickers::critical_pq"
    [base_inputpicker_critical_rnd]="-DINPUT_PICKER=input_pickers::critical_rnd"
    [base_inputpicker_critical_fullrnd]="-DINPUT_PICKER=input_pickers::critical_fullrnd"
    [base_downset_vector_or_kdtree]="-DARRAY_AND_BITSET_DOWNSET_IMPL='vector_or_kdtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='vector_or_kdtree_backed'"
    [base_downset_kdtree]="-DARRAY_AND_BITSET_DOWNSET_IMPL='kdtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='kdtree_backed'"
    [base_downset_vector]="-DARRAY_AND_BITSET_DOWNSET_IMPL=vector_backed -DVECTOR_AND_BITSET_DOWNSET_IMPL=vector_backed"
    [base_downset_vectorbin]="-DARRAY_AND_BITSET_DOWNSET_IMPL=vector_backed_bin -DVECTOR_AND_BITSET_DOWNSET_IMPL=vector_backed_bin -DARRAY_IMPL=simd_array_backed_sum -DVECTOR_IMPL=simd_vector_backed"
    # These are variations on the best configuration
    [best]="$best"
    [best_decomp]="$best -DDECOMPOSE_SPEC=1"
    [best_no_array_cap_max]="$best -DNO_ARRAY_CAP_MAX"  # STATIC_ARRAY_CAP_MAX will be set to 0
    [best_no_bitsets]="$best -DNO_ARRAY_CAP_MAX -DUSE_BOOLVEC_OVER_BITSET"  # same, and x_and_boolvec used instead of x_and_bitset
    [best_mona]="$best -DIOS_PRECOMPUTER=ios_precomputers::mona"
    [best_noiosprecom_delegate]="$best -DIOS_PRECOMPUTER=ios_precomputers::delegate -DACTIONER='actioners::no_ios_precomputation'"
    [best_downset_vector_or_kdtree]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='vector_or_kdtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='vector_or_kdtree_backed'"
    [best_downset_kdtree]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='kdtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='kdtree_backed'"
    [best_downset_sharingtree]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='sharingtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='sharingtree_backed'"
    [best_downset_simple_sharingtree]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='simple_sharingtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='simple_sharingtree_backed'"
    [best_downset_sharingtrie]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='sharingtrie_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='sharingtrie_backed'"
    [best_decomp_kdtree_mona]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='kdtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='kdtree_backed' -DIOS_PRECOMPUTER=ios_precomputers::mona -DDECOMPOSE_SPEC=1"
    [best_decomp_kdtree_mona_no_bitsets]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='kdtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='kdtree_backed' -DIOS_PRECOMPUTER=ios_precomputers::mona -DDECOMPOSE_SPEC=1 -DNO_ARRAY_CAP_MAX -DUSE_BOOLVEC_OVER_BITSET"
    [best_decomp_sharingtrie_mona]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='sharingtrie_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='sharingtrie_backed' -DIOS_PRECOMPUTER=ios_precomputers::mona -DDECOMPOSE_SPEC=1"
    [best_decomp_simpsharingtree_mona]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='simple_sharingtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='simple_sharingtree_backed' -DIOS_PRECOMPUTER=ios_precomputers::mona -DDECOMPOSE_SPEC=1"
    [best_decomp_sharingtree_mona]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='sharingtree_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='sharingtree_backed' -DIOS_PRECOMPUTER=ios_precomputers::mona -DDECOMPOSE_SPEC=1"
    [best_decomp_sharingtrie_mona_no_bitsets]="$best -DARRAY_AND_BITSET_DOWNSET_IMPL='sharingtrie_backed' -DVECTOR_AND_BITSET_DOWNSET_IMPL='sharingtrie_backed' -DIOS_PRECOMPUTER=ios_precomputers::mona -DDECOMPOSE_SPEC=1 -DNO_ARRAY_CAP_MAX -DUSE_BOOLVEC_OVER_BITSET"
    [best_decomp_mona_no_bitsets]="$best -DDECOMPOSE_SPEC=1 -DIOS_PRECOMPUTER=ios_precomputers::mona -DNO_ARRAY_CAP_MAX -DUSE_BOOLVEC_OVER_BITSET"
    [best_decomp_mona]="$best -DDECOMPOSE_SPEC=1 -DIOS_PRECOMPUTER=ios_precomputers::mona"
)

mode= # print, list
force=false
justtest=false
native_file=
donot=()
conflist=(${(k)confs})
benchsuites=(--suite=$BENCHMARK_SUITE)

if (( $# == 0 )); then
  secs=10

  cat <<EOF
No option given; this will build, compile, and benchmark ${#confs} different configurations.
To build/compile/benchmark with default (debug) options, run:

  $ meson setup build
  $ cd build
  $ meson compile
  $ meson test --benchmark --suite=$BENCHMARK_SUITE -t $TIMEOUT_FACTOR

EOF
  echo -n "Waiting $secs seconds before starting; hit Ctrl-C to cancel: "
  for i in {$secs..1}; do
    echo -n "$i "
    sleep 1
  done
  echo "."
fi

while getopts "hplBCjRfb:t:c:n:" option; do
    case $option; in
        h) cat <<EOF
usage: $0 [-hplBCjR] [-b BENCHMARK[,BENCHMARK]] [-c CONF[,CONF,...]] [-n NATIVE_FILE]
  -h: Print this message.
  -p: Do not build/compile/benchmark, instead, print the CXXFLAGS.
  -l: Do not build/compile/benchmark, instead, list configurations.
  -B: Do not build.
  -C: Do not compile.
  -j: Just test instead of benchmarking.
  -R: Do not benchmark.
  -f: Do not fail when a build, compile, or benchmark does, continue as if they passed.
  -b BENCHMARK: Run a specific benchmark suite (default: $BENCHMARK_SUITE).
  -t TIMEOUT: Use timeout factor TIMEOUT (default: $TIMEOUT_FACTOR).  Actual time is multiplied by 10.
  -c CONF,...: Only consider configurations listed.
  -n NATIVE_FILE: Path to a meson native file passed to \`meson setup\`.
EOF
           exit 1;;
        p) mode=print;;
        l) mode=list;;
        B) donot+=build;;
        C) donot+=compile;;
	j) justtest=true;;
        R) donot+=benchmark;;
        f) force=true;;
        c) conflist=(${(@s:,:)OPTARG});;
	t) TIMEOUT_FACTOR=$OPTARG;;
	b) benchsuites=()
	   for b in ${(@s:,:)OPTARG}; do
	     benchsuites+=(--suite="$b")
	   done;;
	n) native_file=$OPTARG;;
    esac
done

## If we're just testing, go for a fast compilation
rel="--buildtype=release"
if $justtest; then
    opt=$opt_justtest
    echo "Using opt $opt for faster compile-to-test times"
    rel=""
fi

## Print and list mode
if [[ $mode == print || $mode == list ]]; then
    for name in $conflist; do
        echo -n "- $name"
        if [[ $mode == print ]]; then
            # echo -n ": $opt $defaults $confs[$name]" | tr '\n' ' '
	    # defaults are set in configuration.hh
            echo -n ": $opt $confs[$name]" | tr '\n' ' '
        fi
        echo
    done
    exit
fi

## Build
if ! (( $donot[(Ie)build] )); then
    for name in $conflist; do
        param=$confs[$name]
        [[ $param == "" ]] && { echo "error: $name, unknown configuration."; $force || exit 2 }
        build=build_$name
        log=_bm-logs/$name.log
        rm -f $log
        if [[ -e $build ]]; then
            echo "$build exists, not rebuilding, remove folder to rebuild."
        else
            echo -n "building $build (logfile: $log)... "
            # if CXXFLAGS="$opt $defaults $param $CXXFLAGS" meson setup $build $rel &>> $log; then
	    # defaults are set in configuration.hh
            native_flag=()
            [[ -n $native_file ]] && native_flag=(--native-file "$native_file")
            if CXXFLAGS="$opt $defaults $param $CXXFLAGS" meson setup $build $rel $native_flag &>> $log; then
                echo "done."
            else
                echo "FAILED; please remove $build to recompile."
                cat $log
                $force || exit 2
            fi
        fi
    done
fi

## Compile
if ! (( $donot[(Ie)compile] )); then
    for name in $conflist; do
        build=build_$name
        log=_bm-logs/$name.log
        if [[ -e $build/compiled ]]; then
            echo "$name already compiled, remove $build/compiled to recompile"
            continue
        fi
        if ! [[ -e $build ]]; then
	    echo "$name isn't built, skipping."
	    continue
        fi
        cd $build
        echo -n "compiling $name (logfile: $log)... "
        if meson compile &>> ../$log; then
            echo "done"
            touch compiled
        else
            echo "FAILED."
            cat ../$log
            $force || exit 3
        fi
        cd ..
    done
fi

## Benchmark
if ! (( $donot[(Ie)benchmark] )); then
    for name in $conflist; do
        build=build_$name
        log=_bm-logs/$name.log
        if [[ -e $build/benchmarked ]]; then
            echo "skipping already benchmarked $name, remove $build/benchmarked to rebenchmark"
            continue
        fi
        if [[ ! -e $build/compiled ]]; then
            echo "skipping uncompiled $name, create $build/compiled if compiled by hand"
            continue
        fi
        cd $build
	## For the dummy ltlsynt configuration, redirect every --suite=ab/…
	## argument to --suite=ltlsynt/… so that the same meson run now
	## picks up the ltlsynt-backed benchmarks instead of the ab ones.
	if [[ $name == ltlsynt ]]; then
	    this_suites=()
	    for b in $benchsuites; do
	        this_suites+=("${b//ab\//ltlsynt/}")
	    done
	else
	    this_suites=($benchsuites)
	fi
	if $justtest; then
	    echo -n "testing $name on $this_suites (logfile: $log)... "
	    meson test $this_suites -t $TIMEOUT_FACTOR &>> ../$log
	else
	    echo -n "benchmarking $name on $this_suites (logfile: $log)... "
	    meson test --benchmark $this_suites -t $TIMEOUT_FACTOR &>> ../$log
	fi
        if grep -q '^Fail:[[:space:]]*[1-9]' ../$log; then
            echo "FAILED; testlog stored at $log, _bm-logs/$name.json left untouched"
            $force || exit 5
        else
            echo "done; testlog stored at $log"
            touch benchmarked
        fi
        cd ..
        cp $build/meson-logs/testlog.json _bm-logs/$name.json
    done
fi
