jq() {
  command jq -L"$INSIDE_DUNE"/test/blackbox-tests "$@"
}

is_linked() {
  nlinks=$(dune_cmd stat hardlinks "$1")
  [ "$nlinks" -gt 1 ] && echo linked || echo not linked
}

export XDG_CACHE_HOME="$PWD/.cache"

init_oxcaml_project() {
  cat > "dune-project" <<- EOF
	(lang dune 3.20)
	(using oxcaml 0.1)
	EOF
}

make_dune_project() {
  version="$1"
  cat > dune-project <<- EOF
	(lang dune $version)
	EOF
}

make_dir_with_dune() {
  path="$1"
  mkdir -p "$path"
  cat > "$path/dune"
}

make_dummy_intf() {
  dir="$1"
  name="$2"
  cat >> "$dir/$name.mli" <<- EOF
	type t
	val f : t -> unit
	EOF
}

make_dummy_impl() {
  dir="$1"
  name="$2"
  cat >> "$dir/$name.ml" <<- EOF
	type t = int
	let f _ = ()
	EOF
}

make_lib_impl() {
  name="$1"
  implements="$2"
  make_dir_with_dune "$name" <<- EOF
	(library
	  (name $name)
	  (implements $implements))
	EOF
}

target_cmi() {
  echo "./$1/.$1.objs/byte/$1.cmi"
}

build_target_cmi() {
  echo "./_build/default/$1/.$1.objs/byte/$1.cmi"
}

# Git related helper scripts

# These variables are used by Git and set here so the name and email of the
# committer stay the same between different runs
export GIT_AUTHOR_NAME="Test Name"
export GIT_AUTHOR_EMAIL="test@example.com"
export GIT_COMMITTER_NAME="${GIT_AUTHOR_NAME}"
export GIT_COMMITTER_EMAIL="${GIT_AUTHOR_EMAIL}"
# Set various GIT variables to ensure git behaves as "default" as possible in the tests
export GIT_CONFIG_GLOBAL="/dev/null"
export GIT_CONFIG_SYSTEM="/dev/null"

export DUNE_RUNNING=0

start_dune () {
    ( (dune build "$@" --passive-watch-mode > .#dune-output 2>&1) || (echo exit $? >> .#dune-output) ) &
    DUNE_PID=$!;
    export DUNE_RUNNING=1;
    wait_for_rpc_server
}

timeout="$(command -v timeout || echo gtimeout)"

with_timeout () {
    $timeout 2 "$@"
    exit_code=$?
    if [ "$exit_code" = 124 ]
    then
        echo Timed out
        cat .#dune-output
    else
        return "$exit_code"
    fi
}

with_timeout_quiet () {
    output=$(mktemp)
    $timeout 2 "$@" >"$output" 2>&1
    exit_code=$?
    if [ "$exit_code" = 124 ]
    then
        echo Timed out
        cat "$output"
        cat .#dune-output
    elif [ "$exit_code" != 0 ]
    then
        cat "$output"
    fi
    rm -f "$output"
    return "$exit_code"
}

shutdown_dune () {
    with_timeout dune shutdown
}

shutdown_dune_quiet () {
    with_timeout_quiet dune shutdown
}

wait_for_dune_exit () {
    # On Linux, we may run into a bash pid aliasing bug that causes wait to
    # reject the pid. Therefore we use tail to wait instead.
    if [ "$(uname -s)" = "Linux" ]
    then
        # wait for all child processes
        tail --pid="$DUNE_PID" -f /dev/null;
    else
        # wait for dune to exit
        wait "$DUNE_PID";
    fi
}

wait_for_pid_to_exit_with_timeout () {
    pid=$1
    iterations=$2
    while kill -0 "$pid" 2>/dev/null
    do
        if [ "$iterations" = 0 ]
        then
            return 124
        fi
        iterations=$((iterations - 1))
        sleep 0.01
    done
}

stop_dune () {
    shutdown_dune;
    wait_for_dune_exit;
    cat .#dune-output;
}

build () {
    with_timeout dune rpc build --wait "$@"
}

stop_dune_quiet () {
    shutdown_dune_quiet;
    wait_for_dune_exit_with_timeout;
}

build_quiet () {
    with_timeout_quiet dune rpc build --wait "$@"
}

wait_for_rpc_server () {
    with_timeout_quiet dune rpc ping --wait
}

summarize_rpc_trace () {
    dune trace cat | jq -r '
      select(.cat == "rpc")
      | if .name == "accept"
           and .args.stage == "stop"
           and .args.status == "close"
        then "accept stop close"
        elif .name == "accept"
             and .args.stage == "stop"
             and (.args | has("error"))
        then "accept stop error"
        elif .name == "startup-failure"
        then "startup failure"
        elif .name == "request" and .args.meth == "build"
        then "build \(.args.stage)"
        elif .name == "shutdown"
        then "shutdown \(.args.stage)"
        else empty
        end'
}

wait_for_dune_exit_with_timeout () {
    exit_code=0
    wait_for_pid_to_exit_with_timeout "$DUNE_PID" 200 || exit_code=$?
    if [ "$exit_code" = 124 ]
    then
        summarize_rpc_trace
    fi
    return "$exit_code"
}

wait_for_file () {
    until [ -e "$1" ]
    do
        sleep 0.01
    done
}

file_status() {
  [ -e "$1" ] && echo "$1 exists" || echo "$1 missing"
}

censor() {
  # we want to substitute it to $DIGEST
  # shellcheck disable=SC2016
  dune_cmd subst '[0-9a-f]{32}' '$DIGEST'
}

censor_ppx() {
  # shellcheck disable=SC2016
  dune_cmd subst '[0-9a-f]{32}/ppx.exe' '$DIGEST/ppx.exe'
}

censor_cinaps() {
  # shellcheck disable=SC2016
  dune_cmd subst '\.cinaps\.[0-9a-f]+/' '$CINAPS/'
}

# CR-someday rgrinberg: get rid of this one or fuse it into censor
strip_sandbox() {
  # we want to substitute it to $SANDBOX
  # shellcheck disable=SC2016
  dune_cmd subst '[^ ]*.sandbox[/\\][^/\\]+' '$SANDBOX'
}
