#!/usr/bin/env bash
#
#  mftest                             Select a test to apply and build
#  mftest -b [#]                      Build the auto-detected environment
#  mftest -u [#]                      Upload the auto-detected environment
#  mftest [name] [index] [-y]         Set config options and optionally build a test
#

[[ -d Marlin/src ]] || { echo "Please 'cd' to the Marlin repo root." ; exit 1 ; }

perror() { echo -e "$0: \033[0;31m$1 -- $2\033[0m" ; }
errout() { echo -e "\033[0;31m$1\033[0m" ; }
bugout() { ((DEBUG)) && echo -e "\033[0;32m$1\033[0m" ; }

usage() {
  echo "
Usage: mftest [-t|--env=<env|index>] [-n|--num=<num>] [-m|--make] [-y|--build=<Y|n>]
       mftest [-a|--autobuild]
       mftest [-r|--rebuild]
       mftest [-s|--silent]
       mftest [-u|--autoupload] [-n|--num=<num>]

OPTIONS
  -t --env         The environment to apply / run, or the menu index number.
  -n --num         The index of the test to run. (In file order.)
  -m --make        Use the make / Docker method for the build.
  -y --build       Skip 'Do you want to build this test?' and assume YES.
  -h --help        Print this help.
  -a --autobuild   PIO Build using the MOTHERBOARD environment.
  -u --autoupload  PIO Upload using the MOTHERBOARD environment.
  -v --verbose     Extra output for debugging.
  -s --silent      Silence build output from PlatformIO.

env shortcuts: tree due esp lin lp8|lpc8 lp9|lpc9 m128 m256|mega stm|f1 f4 f7 s6 teensy|t31|t32 t35|t36 t40|t41
"
}

TESTPATH=buildroot/tests

STATE_FILE="./.pio/.mftestrc"
SED=$(which gsed sed | head -n1)

shopt -s extglob nocasematch

# Matching patterns
ISNUM='^[0-9]+$'
ISCMD='^(restore|opt|exec|use|pins|env)_'
ISEXEC='^exec_'
ISCONT='\\ *$'

# Get environment, test number, etc. from the command
TESTENV='-'
CHOICE=0
DEBUG=0

while getopts 'abhmrsuvyn:t:-:' OFLAG; do
  case "${OFLAG}" in
    a) AUTO_BUILD=1 ; bugout "Auto-Build target..." ;;
    h) EXIT_USAGE=1 ;;
    m) USE_MAKE=1 ; bugout "Using make with Docker..." ;;
    n) case "$OPTARG" in
         *[!0-9]*) perror "option requires a number" $OFLAG ; EXIT_USAGE=2 ;;
                *) CHOICE="$OPTARG" ; bugout "Got a number: $CHOICE" ;;
       esac
       ;;
    r) REBUILD=1         ; bugout "Rebuilding previous..." ;;
    s) SILENT_FLAG="-s" ;;
    t) TESTENV="$OPTARG" ; bugout "Got a target: $TESTENV" ;;
    u) AUTO_BUILD=2      ; bugout "Auto-Upload target..." ;;
    v) DEBUG=1           ; bugout "Debug ON" ;;
    y) BUILD_YES='Y'     ; bugout "Build will initiate..." ;;
    -) IFS="=" read -r ONAM OVAL <<< "$OPTARG"
       case "$ONAM" in
         help) [[ -z "$OVAL" ]] || perror "option can't take value $OVAL" $ONAM ; EXIT_USAGE=1 ;;
    autobuild) AUTO_BUILD=1 ; bugout "Auto-Build target..."  ;;
   autoupload) AUTO_BUILD=2 ; bugout "Auto-Upload target..." ;;
          env) case "$OVAL" in
                 '') perror "option requires a value" $ONAM ; EXIT_USAGE=2 ;;
                  *) TESTENV="$OVAL" ; bugout "Got a target: $TESTENV" ;;
               esac
               ;;
          num) case "$OVAL" in
                 [0-9]+) CHOICE="$OVAL" ; bugout "Got a number: $CHOICE" ;;
                      *) perror "option requires a value" $ONAM ; EXIT_USAGE=2 ;;
               esac
               ;;
      rebuild) REBUILD=1  ; bugout "Rebuilding previous..." ;;
       silent) SILENT_FLAG="-s" ;;
         make) USE_MAKE=1 ; bugout "Using make with Docker..." ;;
debug|verbose) DEBUG=1    ; bugout "Debug ON" ;;
        build) case "$OVAL" in
                 ''|y|yes) BUILD_YES='Y' ;;
                     n|no) BUILD_YES='N' ;;
                        *) perror "option value must be y, n, yes, or no" $ONAM ; EXIT_USAGE=2 ;;
               esac
               bugout "Build will initiate? ($BUILD_YES)"
               ;;
            *) perror "Unknown flag" "$OPTARG" ; EXIT_USAGE=2 ;;
       esac
       ;;
    *) EXIT_USAGE=2 ;;
  esac
done

((EXIT_USAGE)) && { usage ; let EXIT_USAGE-- ; exit $EXIT_USAGE ; }

if ((REBUILD)); then
  bugout "Rebuilding previous..."
  # Build with the last-built env
  [[ -f "$STATE_FILE" ]] || { errout "No previous (-r) build state found." ; exit 1 ; }
  read TESTENV <"$STATE_FILE"
  pio run $SILENT_FLAG -d . -e $TESTENV
  exit 0
fi

case $TESTENV in
    tree) pio run -d . -e include_tree ; exit 1 ;;
     due) TESTENV='DUE' ;;
     esp) TESTENV='esp32' ;;
    lin*) TESTENV='linux_native' ;;
lp8|lpc8) TESTENV='LPC1768' ;;
lp9|lpc9) TESTENV='LPC1769' ;;
    m128) TESTENV='mega1280' ;;
    m256) TESTENV='mega2560' ;;
    mega) TESTENV='mega2560' ;;
     stm) TESTENV='STM32F103RE' ;;
      f1) TESTENV='STM32F103RE' ;;
      f4) TESTENV='STM32F4' ;;
      f7) TESTENV='STM32F7' ;;
      s6) TESTENV='FYSETC_S6' ;;
  teensy) TESTENV='teensy31' ;;
     t31) TESTENV='teensy31' ;;
     t32) TESTENV='teensy31' ;;
     t35) TESTENV='teensy35' ;;
     t36) TESTENV='teensy35' ;;
     t40) TESTENV='teensy41' ;;
     t41) TESTENV='teensy41' ;;
[1-9]|[1-9][0-9]) TESTNUM=$TESTENV ; TESTENV=- ;;
esac

if ((AUTO_BUILD)); then
  #
  # List environments that apply to the current MOTHERBOARD.
  #
  case $(uname | tr '[:upper:]' '[:lower:]') in
    darwin) SYS='mac' ;;
    *linux) SYS='lin' ;;
      win*) SYS='win' ;;
     msys*) SYS='win' ;;
   cygwin*) SYS='win' ;;
    mingw*) SYS='win' ;;
         *) SYS='uni' ;;
  esac
  echo ; echo -n "Auto " ; ((AUTO_BUILD == 2)) && echo "Upload..." || echo "Build..."
  MB=$( grep -E "^\s*#define MOTHERBOARD" Marlin/Configuration.h | awk '{ print $3 }' | $SED 's/BOARD_//;s/\r//' )
  [[ -z $MB ]] && { echo "Error - Can't read MOTHERBOARD setting." ; exit 1 ; }
  BLINE=$( grep -E "define\s+BOARD_$MB\b" Marlin/src/core/boards.h )
  BNUM=$( $SED -E 's/^.+BOARD_[^ ]+ +([0-9]+).+$/\1/' <<<"$BLINE" )
  BDESC=$( $SED -E 's/^.+\/\/ *(.+)$/\1/' <<<"$BLINE" )
  [[ -z $BNUM ]] && { echo "Error - Can't find BOARD_$MB in core/boards.h." ; exit 1 ; }
  ENVS=( $( grep -EA1 "MB\(.*\b$MB\b.*\)" Marlin/src/pins/pins.h | grep -E "#include.+//.+(env|$SYS):[^ ]+" | grep -oE "(env|$SYS):[^ ]+" | $SED -E "s/(env|$SYS)://" ) )
  [[ -z $ENVS ]] && { errout "Error - Can't find target(s) for $MB ($BNUM)." ; exit 1 ; }
  ECOUNT=${#ENVS[*]}

  if [[ $ECOUNT == 1 ]]; then
    TARGET=$ENVS
  else
    if [[ $CHOICE == 0 ]]; then
      # List env names and numbers. Get selection.
      echo "Available targets for \"$BDESC\" | $MB ($BNUM):"

      IND=0 ; for ENV in "${ENVS[@]}"; do let IND++ ; echo " $IND) $ENV" ; done

      if [[ $ECOUNT > 1 ]]; then
        for (( ; ; ))
        do
          read -p "Select a target for '$MB' (1-$ECOUNT) : " CHOICE
          [[ -z "$CHOICE" ]] && { echo '(canceled)' ; exit 1 ; }
          [[ $CHOICE =~ $ISNUM ]] && ((CHOICE >= 1 && CHOICE <= ECOUNT)) && break
          errout ">>> Invalid environment choice '$CHOICE'."
        done
        echo
      fi
    else
      echo "Detected \"$BDESC\" | $MB ($BNUM)."
      [[ $CHOICE > $ECOUNT ]] && { echo "Environment selection out of range." ; exit 1 ; }
    fi
    TARGET="${ENVS[$CHOICE-1]}"
    echo "Selected $TARGET"
  fi

  echo "$TARGET" >"$STATE_FILE"

  if ((AUTO_BUILD == 2)); then
    echo "Uploading environment $TARGET for board $MB ($BNUM)..." ; echo
    pio run $SILENT_FLAG -t upload -e $TARGET
  else
    echo "Building environment $TARGET for board $MB ($BNUM)..." ; echo
    pio run $SILENT_FLAG -e $TARGET
  fi
  exit 0
fi

#
# List available tests and ask for selection
#

if [[ $TESTENV == '-' ]]; then
  IND=0
  NAMES=()
  MENU=()
  BIGLEN=0
  for FILE in $( ls -1 $TESTPATH/* | sort -f )
  do
    let IND++
    TNAME=${FILE/$TESTPATH\//}
    NAMES+=($TNAME)
    IFS=""
    ITEM=$( printf "%2i) %s" $IND $TNAME )
    MENU+=($ITEM)
    [[ ${#ITEM} -gt $BIGLEN ]] && BIGLEN=${#ITEM}
  done

  (( BIGLEN += 2 ))
  THIRD=$(( (${#MENU[@]} + 2) / 3 ))
  for ((i = 0; i < $THIRD; i++))
  do
    COL1=$i ; COL2=$(( $i + $THIRD )) ; COL3=$(( $i + 2 * $THIRD ))
    FMT="%-${BIGLEN}s"
    printf "${FMT}${FMT}${FMT}\n" ${MENU[$COL1]} ${MENU[$COL2]} ${MENU[$COL3]}
  done

  echo
  for (( ; ; ))
  do
    if [[ $TESTNUM -gt 0 ]]; then
      NAMEIND=$TESTNUM
    else
      read -p "Select a test to apply (1-$IND) : " NAMEIND
    fi
    [[ -z $NAMEIND ]] && { errout "(canceled)" ; exit 1 ; }
    TESTENV=${NAMES[$NAMEIND-1]}
    [[ $TESTNUM -gt 0 ]] && { echo "Preselected test $TESTNUM ... ($TESTENV)" ; TESTNUM='' ; }
    [[ $NAMEIND =~ $ISNUM ]] && ((NAMEIND >= 1 && NAMEIND <= IND)) && { TESTENV=${NAMES[$NAMEIND-1]} ; echo ; break ; }
    errout "Invalid selection."
  done
fi

# Get the contents of the test file
OUT=$( cat $TESTPATH/$TESTENV 2>/dev/null ) || { errout "Can't find test '$TESTENV'." ; exit 1 ; }

# Count up the number of tests
TESTCOUNT=$( awk "/$ISEXEC/{a++}END{print a}" <<<"$OUT" )

# User entered a number?
(( CHOICE && CHOICE > TESTCOUNT )) && { errout "Invalid test selection '$CHOICE' (1-$TESTCOUNT)." ; exit 1 ; }

if [[ $CHOICE == 0 ]]; then
  #
  # List test descriptions with numbers and get selection
  #
  echo "Available '$TESTENV' tests:" ; echo "$OUT" | {
    IND=0
    while IFS= read -r LINE
    do
      if [[ $LINE =~ $ISEXEC ]]; then
        DESC=$( "$SED" -E 's/^exec_test \$1 \$2 "([^"]+)".*$/\1/g' <<<"$LINE" )
        (( ++IND < 10 )) && echo -n " "
        echo " $IND) $DESC"
      fi
    done
  }
  CHOICE=1
  if [[ $TESTCOUNT > 1 ]]; then
    for (( ; ; ))
    do
      read -p "Select a '$TESTENV' test (1-$TESTCOUNT) : " CHOICE
      [[ -z "$CHOICE" ]] && { errout "(canceled)" ; exit 1 ; }
      [[ $CHOICE =~ $ISNUM ]] && ((CHOICE >= 1 && CHOICE <= TESTCOUNT)) && break
      errout ">>> Invalid test selection '$CHOICE'."
    done
  fi
fi

#
# Run the specified test lines
#
echo -ne "\033[0;33m"
echo "$OUT" | {
  IND=0
  GOTX=0
  CMD=""
  while IFS= read -r LINE
  do
    if [[ $LINE =~ $ISCMD || $GOTX == 1 ]]; then
      ((!IND)) && let IND++
      if [[ $LINE =~ $ISEXEC ]]; then
        ((IND++ > CHOICE)) && break
      else
        ((!HEADER)) && {
          HEADER=1
          echo -e "\n#\n# Test $TESTENV ($CHOICE) $DESC\n#"
        }
        ((IND == CHOICE)) && {
          GOTX=1
          [[ $CMD == "" ]] && CMD="$LINE" || CMD=$( echo -e "$CMD$LINE" | $SED -e 's/\\//g' | $SED -E 's/ +/ /g' )
          [[ $LINE =~ $ISCONT ]] || { echo "$CMD" ; eval "$CMD" ; CMD="" ; }
        }
      fi
    fi
  done
}
echo -ne "\033[0m"

# Make clear it's a TEST
opt_set CUSTOM_MACHINE_NAME "\"Test $TESTENV ($CHOICE)\""

# Build the test too?
if [[ -z "$BUILD_YES" ]]; then
  echo
  read -p "Build $TESTENV test #$CHOICE (y/N) ? " BUILD_YES
fi

[[ $BUILD_YES == 'Y' || $BUILD_YES == 'Yes' ]] && {
  ((USE_MAKE)) && make tests-single-local TEST_TARGET=$TESTENV ONLY_TEST=$CHOICE
  ((USE_MAKE)) || pio run $SILENT_FLAG -d . -e $TESTENV
  echo "$TESTENV" >"$STATE_FILE"
}
