#!/usr/bin/env bash
set -eET

export flags=()
num_jobs=1
filter=''
extended_syntax=''

while [[ "$#" -ne 0 ]]; do
  case "$1" in
  -c) ;;

  -f)
    shift
    filter="$1"
    flags+=('-f' "$filter")
    ;;
  -j)
    shift
    num_jobs="$1"
    ;;
  -T)
    flags+=('-T')
    ;;
  -x)
    flags+=('-x')
    extended_syntax=1
    ;;
  *)
    break
    ;;
  esac
  shift
done

filename=$1
TESTS_FILE="$2"

if [[ ! -f "$filename" ]]; then
  printf 'Testfile "%s" not found\n' "$filename" >&2
  exit 1
fi

BATS_TEST_FILENAME="$filename"

# shellcheck source=lib/bats-core/preprocessing.bash
# shellcheck disable=SC2153
source "$BATS_ROOT/lib/bats-core/preprocessing.bash"

bats_run_setup_file() {
  # shellcheck source=lib/bats-core/tracing.bash
  # shellcheck disable=SC2153
  source "$BATS_ROOT/lib/bats-core/tracing.bash"
  # shellcheck source=lib/bats-core/test_functions.bash
  # shellcheck disable=SC2153
  source "$BATS_ROOT/lib/bats-core/test_functions.bash"

  exec 3<&1

  BATS_STACK_TRACE=()
  # shellcheck disable=2034
  BATS_CURRENT_STACK_TRACE=() # used in tracing.bash

  # these are defined only to avoid errors when referencing undefined variables down the line
  # shellcheck disable=2034
  BATS_TEST_NAME=      # used in tracing.bash
  # shellcheck disable=2034
  BATS_TEST_COMPLETED= # used in tracing.bash

  BATS_SETUP_FILE_COMPLETED=
  BATS_TEARDOWN_FILE_COMPLETED=
  # shellcheck disable=2034
  BATS_ERROR_STATUS= # used in tracing.bash
  trap 'bats_debug_trap "$BASH_SOURCE"' DEBUG
  trap 'bats_error_trap' ERR
  trap 'bats_file_teardown_trap' EXIT

  touch "$BATS_OUT"
  # get the setup_file/teardown_file functions for this file (if it has them)
  # shellcheck disable=SC1090
  source "$BATS_TEST_SOURCE"
  setup_file >>"$BATS_OUT" 2>&1

  BATS_SETUP_FILE_COMPLETED=1
}

bats_run_teardown_file() {
  # avoid running the therdown trap due to errors in teardown_file
  trap 'bats_file_exit_trap' EXIT
  local status=0
  # rely on bats_error_trap to catch failures
  teardown_file >>"$BATS_OUT" 2>&1

  BATS_TEARDOWN_FILE_COMPLETED=1
}

bats_file_teardown_trap() {
  bats_error_trap
  local status=0
  bats_run_teardown_file

  bats_file_exit_trap
}

bats_file_exit_trap() {
  trap - ERR EXIT
  if [[ -z "$BATS_SETUP_FILE_COMPLETED" || -z "$BATS_TEARDOWN_FILE_COMPLETED" ]]; then
    if [[ -z "$BATS_SETUP_FILE_COMPLETED" ]]; then
      FAILURE_REASON='setup_file'
    else
      FAILURE_REASON='teardown_file'
    fi
    printf "not ok %d %s\n" "$((test_number + 1))" "$FAILURE_REASON failed" >&3
    bats_print_stack_trace "${BATS_STACK_TRACE[@]}" >&3
    bats_print_failed_command >&3
    while IFS= read -r line; do
      printf "# %s\n" "$line"
    done <"$BATS_OUT" >&3
    if [[ -n "$line" ]]; then
      printf '# %s\n' "$line"
    fi
    rm -rf "$BATS_OUT"
    status=1
  fi
  bats_cleanup_preprocessed_source
  exit $status
}

function setup_file() {
  return 0
}

function teardown_file() {
  return 0
}

bats_run_tests() {
  status=0
  tests_to_run=()
  local line_number=0
  test_number=''
  while read -r test_line; do
    test_file=${test_line%%$'\t'*}
    test_name=${test_line##*$'\t'}
    if [[ "$test_file" == "$filename" ]]; then
      tests_to_run+=("$test_name")
      # save the first test's number for later iteration
      # this assumes that tests for a file are stored consecutive in the file!
      if [[ -z $test_number ]]; then
        test_number=$line_number
      fi
    fi
    ((++line_number))
  done <"$TESTS_FILE"

  if [[ "$num_jobs" != 1 ]]; then
    test_pids_and_numbers=()
    output_folder="$BATS_RUN_TMPDIR/parallel_output"
    parallel_log_file="$BATS_RUN_TMPDIR/parallel.log"
    for test_name in "${tests_to_run[@]}"; do
      # Only handle non-empty lines
      if [[ $test_name ]]; then
        ((++test_number))
        test_output_dir="$output_folder/$test_number"
        mkdir -p "$test_output_dir"
        # parallelize across files using parallel's semaphore mode
        # use --results to gather output in separate files
        # use --fg mode to retrieve exit codes (all non zero exit codes seem to mapped to 1), but which requires us to suppress its output
        # shellcheck disable=SC2154 #bats_parallel_args is inherited from exec-suite
        parallel --semaphore -j "$num_jobs" "${bats_parallel_args[@]}" --fg -- bats-exec-test "${flags[@]}" "$filename" "$test_name" "$test_number" >"$test_output_dir/stdout" 2>"$test_output_dir/stderr" &
        pid=$!
        test_pids_and_numbers+=("$pid,$test_number")
      fi
    done
    for test_pid_and_number in "${test_pids_and_numbers[@]}"; do
      test_pid=${test_pid_and_number%,*}
      test_number=${test_pid_and_number##*,}
      test_output_dir="$output_folder/$test_number"
      # fetch the return code
      wait "$test_pid" || status=1
      # wait for tests to finish in the order they were started and output their results as they come in
      cat "$test_output_dir/stdout"
      # additionally output stderr as parallel does by default
      cat "$test_output_dir/stderr" >&2
    done
  else
    for test_name in "${tests_to_run[@]}"; do
      # Only handle non-empty lines
      if [[ $test_name ]]; then
        ((++test_number))
        if [[ "${#flags[@]}" -gt 0 ]]; then
          bats-exec-test "${flags[@]}" "$filename" "$test_name" "$test_number" || status=1
        else
          bats-exec-test "$filename" "$test_name" "$test_number" || status=1
        fi
      fi
    done
  fi
  export status
}

if [[ -n "$extended_syntax" ]]; then
  printf "suite %s\n" "$(basename "$filename")"
fi

bats_preprocess_source "$filename"
bats_run_setup_file
bats_run_tests
bats_run_teardown_file

exit $status
