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

status=0
program="${0##*/}"
PROGRAM="$(echo "$program" | tr a-z- A-Z_)"

_STUB_PLAN="${PROGRAM}_STUB_PLAN"
_STUB_RUN="${PROGRAM}_STUB_RUN"
_STUB_INDEX="${PROGRAM}_STUB_INDEX"
_STUB_RESULT="${PROGRAM}_STUB_RESULT"
_STUB_END="${PROGRAM}_STUB_END"
_STUB_LOG="${PROGRAM}_STUB_LOG"

[ -n "${!_STUB_LOG}" ] || eval "${_STUB_LOG}"="${BATS_TEST_TMPDIR}/${program}-stub-log"
if test -z "${!_STUB_END}"; then echo "$program" "$@" >>"${!_STUB_LOG}"; fi

[ -e "${!_STUB_PLAN}" ] || exit 1
[ -n "${!_STUB_RUN}" ] || eval "${_STUB_RUN}"="${BATS_TEST_TMPDIR}/${program}-stub-run"


# Initialize or load the stub run information.
eval "${_STUB_INDEX}"=1
eval "${_STUB_RESULT}"=0
if test -e "${!_STUB_RUN}"; then source "${!_STUB_RUN}"; fi


match_pattern() {
  local argument="${1?}"
  local pattern="${2:?}"
  case "$argument" in
    $pattern ) return 0;;
    * ) return 1;;
  esac
}

# Loop over each line in the plan.
index=0
while IFS= read -r line; do
  index=$(($index + 1))

  if [ -z "${!_STUB_END}" ] && [ $index -eq "${!_STUB_INDEX}" ]; then
    # We found the plan line we're interested in.
    # Start off by assuming success.
    result=0

    # Split the line into an array of arguments to
    # match and a command to run to produce output.
    command=" $line"
    if [ "$command" != "${command/ : }" ]; then
      patterns="${command%% : *}"
      command="${command#* : }"
    fi

    # Naively split patterns by whitespace for now.
    # In the future, use a sed script to split while
    # respecting quoting.
    set -f
    patterns=($patterns)
    set +f
    arguments=("$@")

    # Match the expected argument patterns to actual
    # arguments.
    for (( i=0, j=0; i<${#patterns[@]}; i++, j++ )); do
      pattern="${patterns[$i]}"

      # ** never backtracks to keep things simple -- so a command line
      # may fail to match if the next pattern can match multiple times.
      # Assuming that we won't have such cases
      # since it's mostly intended to locate specific options
      # and we do not have repeatable options as of this writing.
      if [[ $pattern == "**" ]]; then
        if ((i==${#patterns[@]}-1)); then
          # no next pattern: ** and all patterns success
          break;
        fi
        next_pattern="${patterns[i+1]}"
        for (( fj=j; fj<${#arguments[@]}; fj++ )); do
          if match_pattern "${arguments[fj]}" "$next_pattern"; then
            # ** matched, the next pattern matched, too
            # and they consumed arguments up to fj
            i=$((i+1))
            j=$fj
            break
          fi
        done
        if ((fj==${#arguments[@]})); then
          # next pattern never matched
          result=1; break
        fi
      else
        if (( j<${#arguments[@]} )) && \
            match_pattern "${arguments[j]}" "$pattern"; then
          continue
        else
          result=1; break
        fi
      fi
    done

    # If the arguments matched, evaluate the command
    # in a subshell. Otherwise, log the failure.
    if [ $result -eq 0 ] ; then
      set +e
      ( eval "$command" )
      status="$?"
      set -e
    else
      eval "${_STUB_RESULT}"=1
    fi
  fi
done < "${!_STUB_PLAN}"


if [ -n "${!_STUB_END}" ]; then
  # If the number of lines in the plan is larger than
  # the requested index, we failed.
  if [ $index -ge "${!_STUB_INDEX}" ]; then
    eval "${_STUB_RESULT}"=1
  fi
  if [ "${!_STUB_RESULT}" -ne 0 ]; then
    {
      echo "index: $index; stub index: ${!_STUB_INDEX}"
      echo "plan:"
      cat "${!_STUB_PLAN}" || true
      echo "run:"
      cat "${!_STUB_RUN}" || true
      echo "log:"
      cat "${!_STUB_LOG}" || true
    } >&2
  fi

  # Clean up the run file.
  rm -f "${!_STUB_RUN}"
  rm -f "${!_STUB_LOG}"

  # Return the result.
  exit "${!_STUB_RESULT}"

else
  # If the requested index is larger than the number
  # of lines in the plan file, we failed.
  if [ "${!_STUB_INDEX}" -gt $index ]; then
    eval "${_STUB_RESULT}"=1
  fi

  # Write out the run information.
  { echo "${_STUB_INDEX}=$((${!_STUB_INDEX} + 1))"
    echo "${_STUB_RESULT}=${!_STUB_RESULT}"
  } > "${!_STUB_RUN}"

  exit "$status"

fi
