#!/usr/bin/env bash

# source: https://github.com/Tieske/test.sh

if [[ "$T_INIT_COUNT" == "" ]]; then
  # first time, initialize global variables
  T_PROJECT_NAME=""
  T_FILE_NAME=""
  T_COUNT_FAILURE=0
  T_COUNT_SUCCESS=0
  T_CURRENT_CHAPTER=""
  T_CHAPTER_START_FAILURES=0
  T_CHAPTER_START_SUCCESSES=0
  T_CURRENT_TEST=""
  T_COLOR_YELLOW="\033[1;33m"
  T_COLOR_RED="\033[0;31m"
  T_COLOR_GREEN="\033[1;32m"
  T_COLOR_CLEAR="\033[0m"
  T_INIT_COUNT=0
  T_FAILURE_ARRAY=()
  T_DEBUGGING=""

  figlet -v > /dev/null 2>&1
  T_FIGLET_AVAILABLE=$?
else
  # assuming we're being sourced again, just exit
  return 0
fi


function texit {
  # internal function only
  unset T_PROJECT_NAME
  unset T_FILE_NAME
  unset T_COUNT_FAILURE
  unset T_COUNT_SUCCESS
  unset T_CURRENT_CHAPTER
  unset T_CHAPTER_START_FAILURES
  unset T_CHAPTER_START_SUCCESSES
  unset T_CURRENT_TEST
  unset T_COLOR_YELLOW
  unset T_COLOR_RED
  unset T_COLOR_GREEN
  unset T_COLOR_CLEAR
  unset T_INIT_COUNT
  unset T_FAILURE_ARRAY
  unset T_FIGLET_AVAILABLE
  unset T_DEBUGGING
  exit "$1"
}


function tfooter {
  # internal function only
  # Arguments:
  # 1) successes
  # 2) failures
  # 3) [optional] boolean; if set Project title, otherwise Chapter title
  local indent=""
  if [[ "$3" == "" ]]; then
    indent="  "
    local chapter
    if [[ "$T_FILE_NAME" == "" ]]; then
      chapter=$T_CURRENT_CHAPTER
    else
      chapter="$T_CURRENT_CHAPTER ($T_FILE_NAME)"
    fi
    echo "------------------------------------------------------------------------------------------------------------------------"
    echo -e "$T_COLOR_YELLOW$indent""Chapter : $chapter$T_COLOR_CLEAR"
  else
    echo "========================================================================================================================"
    echo -e "$T_COLOR_YELLOW$indent""Project  : $T_PROJECT_NAME$T_COLOR_CLEAR"
  fi
  echo -e "$T_COLOR_YELLOW$indent""Successes: $1$T_COLOR_CLEAR"
  echo -e "$T_COLOR_YELLOW$indent""Failures : $2$T_COLOR_CLEAR"
}


function theader {
  # internal function only
  local header=$1
  echo "========================================================================================================================"
  if [ $T_FIGLET_AVAILABLE -eq 0 ]; then
    figlet -c -w 120 "$header"
  else
    printf "%*s\n" $(( (${#header} + 120) / 2)) "$header"
  fi
  echo "========================================================================================================================"
}


function tmessage {
  echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME [  info   ] $*$T_COLOR_CLEAR"
}


function tdebug {
  if [[ "$T_CURRENT_TEST" == "" ]]; then
    echo "calling tdebug without a test, call ttest first"
    exit 1
  fi
  T_DEBUGGING=true
  set -x
}


function tinitialize {
  # Initializes either a test suite or a single test file. Every tinitialize
  # MUST be followed by a tfinish, after the tests are completed.
  # Arguments:
  # 1) [required] name of the test suite  (ignored if already set)
  # 2) [optional] filename of the testfile
  if [[ ! "$T_CURRENT_CHAPTER" == "" ]]; then
    echo "calling tinitialize after testing already started"
    exit 1
  fi
  if [[ "$1" == "" ]]; then
    echo "calling tinitialize without project name"
    exit 1
  fi

  if [[ $T_INIT_COUNT -eq 0 ]]; then
    # first time we're being initialized
    T_PROJECT_NAME=$1
    theader "$T_PROJECT_NAME"
    ((T_INIT_COUNT = T_INIT_COUNT + 1))
  else
    # we're being called multiple times, because multiple files run in a row
    # do not intialize again, just update the counter
    ((T_INIT_COUNT = T_INIT_COUNT + 1))
  fi
  T_FILE_NAME=$2
}


function tchapter {
  # Initializes a test chapter.
  # Call after tinitialize, and before ttest.
  if [[ ! "$T_CURRENT_TEST" == "" ]]; then
    echo "calling tchapter while test is unfinished, call tfailure or tsuccess first"
    exit 1
  fi
  if [[ "$1" == "" ]]; then
    echo "calling tchapter without chapter name"
    exit 1
  fi

  if [[ ! "$T_CURRENT_CHAPTER" == "" ]]; then
    tfooter $((T_COUNT_SUCCESS - T_CHAPTER_START_SUCCESSES)) $((T_COUNT_FAILURE - T_CHAPTER_START_FAILURES))
  fi

  T_CURRENT_CHAPTER="$*"
  T_CHAPTER_START_FAILURES=$T_COUNT_FAILURE
  T_CHAPTER_START_SUCCESSES=$T_COUNT_SUCCESS

  theader "$T_CURRENT_CHAPTER"
}


function ttest {
  # Marks the start of a test.
  # The test MUST be finished with either tsuccess or tfailure.
  # Arguments:
  # 1) name of the test
  if [[ "$T_CURRENT_CHAPTER" == "" ]]; then
    echo "calling ttest without chapter, call tchapter first"
    exit 1
  fi
  if [[ "$1" == "" ]]; then
    echo "calling ttest without test description"
    exit 1
  fi
  T_CURRENT_TEST="$*"
  echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME [  start  ] $T_CURRENT_CHAPTER: $T_CURRENT_TEST$T_COLOR_CLEAR"
}


function tfailure {
  # Marks the end of a test, with a failure.
  # no arguments
  if [[ "$T_DEBUGGING" == "true" ]]; then set +x; T_DEBUGGING=""; fi
  if [[ "$T_CURRENT_TEST" == "" ]]; then
    echo "calling tfailure without a test, call ttest first"
    exit 1
  fi
  [[ ! "$1" == "" ]] && tmessage "$*"
  local failure="$T_CURRENT_CHAPTER: $T_CURRENT_TEST"
  echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME$T_COLOR_RED [ failed  ]$T_COLOR_YELLOW $failure$T_COLOR_CLEAR"

  if [[ ! $T_FILE_NAME == "" ]]; then
    failure="$failure ($T_FILE_NAME)"
  fi

  T_FAILURE_ARRAY+=("$failure")

  ((T_COUNT_FAILURE = T_COUNT_FAILURE + 1))
  T_CURRENT_TEST=""
}


function tsuccess {
  # Marks the end of a test, as a success.
  # no arguments
  if [[ "$T_DEBUGGING" == "true" ]]; then set +x; T_DEBUGGING=""; fi
  if [[ "$T_CURRENT_TEST" == "" ]]; then
    echo "calling tsuccess without a test, call ttest first"
    exit 1
  fi
  [[ ! "$1" == "" ]] && tmessage "$*"
  echo -e "$T_COLOR_YELLOW$T_PROJECT_NAME$T_COLOR_GREEN [ success ]$T_COLOR_YELLOW $T_CURRENT_CHAPTER: $T_CURRENT_TEST$T_COLOR_CLEAR"
  ((T_COUNT_SUCCESS = T_COUNT_SUCCESS + 1))
  T_CURRENT_TEST=""
}


function tfinish {
  # Finishes either a test suite or a single test file.
  # no arguments
  if [[ ! "$T_CURRENT_TEST" == "" ]]; then
    echo "calling tfinish while test is unfinished, call tfailure or tsuccess first"
    exit 1
  fi

  if [[ ! "$T_CURRENT_CHAPTER" == "" ]]; then
    tfooter $((T_COUNT_SUCCESS - T_CHAPTER_START_SUCCESSES)) $((T_COUNT_FAILURE - T_CHAPTER_START_FAILURES))
    T_CURRENT_CHAPTER=""
  fi

  ((T_INIT_COUNT = T_INIT_COUNT - 1))
  if [[ $T_INIT_COUNT -eq 0 ]]; then
    # this was the last testfile running, so actually wrap it up
    tfooter $((T_COUNT_SUCCESS)) $((T_COUNT_FAILURE)) Project
    local failure
    for failure in "${T_FAILURE_ARRAY[@]}"; do
      echo -e "$T_COLOR_YELLOW  $failure$T_COLOR_CLEAR"
    done

    if [ "$T_COUNT_FAILURE" -eq 0 ] && [ "$T_COUNT_SUCCESS" -gt 0 ]; then
      if [ $T_FIGLET_AVAILABLE -eq 0 ]; then
        # split in lines and colorize each individually for CI
        figlet -c -w 120 "Success!" | while IFS= read -r line; do echo -e "$T_COLOR_GREEN$line $T_COLOR_CLEAR"; done
      else
        echo -e "$T_COLOR_GREEN  Overall succes!$T_COLOR_CLEAR"
      fi
      texit 0
    else
      if [ $T_FIGLET_AVAILABLE -eq 0 ]; then
        # split in lines and colorize each individually for CI
        figlet -c -w 120 "Failed!" | while IFS= read -r line; do echo -e "$T_COLOR_RED$line $T_COLOR_CLEAR"; done
      else
        echo -e "$T_COLOR_RED  Overall failed!$T_COLOR_CLEAR"
      fi
      texit 1
    fi
  else
    # we've finished a file, but not the last one yet.
    T_FILE_NAME=""
  fi
}


function tcreate {
  # Creates a new testfile from template.
  # Arguments:
  # 1) filename of the new testfile (.test.sh extension auto-appended)
  # 2) test suite name
  if [ "$1" == "" ]; then
    echo "first argument missing: filename to create"
    texit 1
  elif [ "$2" == "" ]; then
    echo "second argument missing: test suite name"
    texit 1
  elif [ ! "$3" == "" ]; then
    echo "too many arguments"
    texit 1
  fi

  local FILENAME="$1"
  if [[ "$FILENAME" != *.test.sh ]]; then
    FILENAME=$FILENAME.test.sh
  fi

  if [ -f "$FILENAME" ]; then
    echo "file already exists: $FILENAME"
    texit 1
  fi

cat <<EOF > "$FILENAME"
#!/usr/bin/env bash

: '
There is one dependency; "test.sh", the "figlet" utility is optional.

Usage test.sh:
  1: ./test.sh [--suite <suite name>] [files/dirs...]
  2: ./test.sh --create <testfile> <suite name>

  1: Runs tests. When "files/dirs" is not provided, it will run all "*.test.sh" files
  located in the same directory as "test.sh". The suite name defaults to "unknown
  test suite".

  2: Creates a new template test file

Usage test files:
  ./this.test.sh [path-to-test.sh]

  When "path-to-test.sh" is not provided it defaults to the same directory where
  the test file is located.

Assuming "test.sh" is in the same directory as this file:

  /some/path/this.test.sh                       # runs only this file
  /some/path/test.sh                            # runs all "/some/path/*.test.sh" files
  /some/path/test.sh this.test.sh that.test.sh  # runs "this.test.sh" and "that.test.sh" files

When not in the same directory

  /some/path/this.test.sh other/path/test.sh    # runs only this file
  /other/path/test.sh /some/path/this.test.sh   # runs only this file


The test themselves are located below this comment in the "run_test" function
'

function run_test {
  # the suite name below will only be used when running this file directly, when
  # running through "test.sh" it must be provided using the "--suite" option.
  tinitialize "$2" "\${BASH_SOURCE[0]}"

  tchapter "great tests"

  ttest "ensures the file exists"
  tdebug  # this enables 'set -x' until the first tfailure or tsuccess call
  if [ ! -f "some/local/file" ]; then
    tmessage "The file did not exist"
    tfailure
  else
    tmessage "The file was found"
    tsuccess
  fi

  tchapter "awesome tests"

  ttest "ensures the file does NOT exist"
  if [ ! -f "some/local/file" ]; then
    tsuccess "a success message"
  else
    tfailure "a failure message"
  fi

  tfinish
}

# No need to modify anything below this comment

# shellcheck disable=SC1090  # do not follow source
[[ "\$T_PROJECT_NAME" == "" ]] && set -e && if [[ -f "\${1:-\$(dirname "\$(realpath "\$0")")/test.sh}" ]]; then source "\${1:-\$(dirname "\$(realpath "\$0")")/test.sh}"; else source "\${1:-\$(dirname "\$(realpath "\$0")")/run.sh}"; fi && set +e
run_test
EOF

  if [ ! $? -eq 0 ]; then
    echo "failed to write file $FILENAME"
    texit 1
  fi
  chmod +x "$FILENAME"
  #bash ./test.sh "$FILENAME"

  echo "Successfully created a new test file: $FILENAME"
  echo "Instructions are in the file."
  texit 0
}


function main {
  # usage: test.sh [--suite <suitename>] [filenames...]
  # if no filenames given then will execute all "*.test.sh" files in the
  # same directory
  local FILE_LIST=()
  local FILENAME
  local SUITE_NAME

  if [[ "$1" == "--create" ]]; then
    shift
    tcreate "$@"
    texit 0
  elif [[ "$1" == "--suite" ]]; then
    shift
    SUITE_NAME=$1
    shift
  else
    SUITE_NAME="unknown test suite"
  fi

  if [[ $# -gt 0 ]]; then
    # filenames passed; only execute filenames passed in
    while [[ $# -gt 0 ]]; do
      if [ ! -f "$1" ] ; then
        if [ ! -d "$1" ] ; then
          echo "test file not found: $1"
          texit 1
        fi
        # it's a directory, add all files in it
        for FILENAME in "$1"/*.test.sh; do
          if [ -f "$FILENAME" ] ; then
            FILE_LIST+=("$FILENAME")
          fi
        done
      else
        # add a single file
        FILE_LIST+=("$1")
      fi
      shift
    done

  else
    # no parameters passed, go execute all test files we can find
    local MY_PATH
    if [[ "$0" == "/dev/stdin" ]]; then
      # script is executed from stdin, probably through run.sh
      MY_PATH=$PWD
    else
      MY_PATH=$(dirname "$(realpath "$0")")
    fi
    for FILENAME in "$MY_PATH"/*.test.sh; do
      if [[ "$FILENAME" != "$(realpath "$0")" ]]; then
        if [ -f "$FILENAME" ] ; then
          FILE_LIST+=("$FILENAME")
        fi
      fi
    done
  fi

  tinitialize "$SUITE_NAME"

  for FILENAME in ${FILE_LIST[*]}; do
    # shellcheck disable=SC1090
    source "$FILENAME"
  done

  tfinish
}


# see 'main' for usage
if [[ $0 == "${BASH_SOURCE[0]}" ]]; then
  # this script is executed, not sourced, so initiate a test run
  main "$@"
fi
