#!/usr/bin/env bash
# shellcheck disable=SC2001
# shellcheck disable=SC2068
# shellcheck disable=SC2086
# SC2001: GH Runners don't always have variable replacement.
# SC2068: The arrays we're expanding are intended to be re-split.
# SC2068: Prefer not to quote every integer.

LAST_DIR="$PWD"
cd "$(dirname "$0")/.." \
  || return $?
trap 'cd "$LAST_DIR"' EXIT

GLOARGS=$@

inargs() {
  # Nothing is in nothing?
  [ $# -lt 2    ] && return 1
  [ "$1" = "$2" ] && return 0
  inargs $1 ${@:3}
}
# "Flag in args"
fia() {
  inargs $1 $GLOARGS
}

#
#  Precedence goes highest -> lowest
#  'sh' stands for 'spreadsheet'
#  A row's 'in' expression -> 'out' result
#
# ---------------------------------------------------------------------------------------------------------------------------------------------
#                                                     in                                                                  #        out        #
# ----------------------------------------------------------------------------------------------------------------------- #                   #
#                     negative                    #                                   positive                            #                   #
# ----------------------------------------------- # --------------------------------------------------------------------- #                   #
#           item          #        group          #           item           #        group       #       default         #                   #
#                         #                       #                          #                    #                       #                   #
fia --no-platform-android || fia --no-platform || ! { fia --platform-android || fia --platform-all                     ; }; PLATFORM_ANDROID=$?
fia --no-platform-windows || fia --no-platform || ! { fia --platform-windows || fia --platform-all                     ; }; PLATFORM_WINDOWS=$?
fia   --no-platform-linux || fia --no-platform || ! { fia   --platform-linux || fia --platform-all                     ; };   PLATFORM_LINUX=$?
fia   --no-platform-macos || fia --no-platform || ! { fia   --platform-macos || fia --platform-all                     ; };   PLATFORM_MACOS=$?
fia    --no-platform-this || fia --no-platform || ! { fia    --platform-this || fia --platform-all || ! fia --platform ; };    PLATFORM_THIS=$?
fia     --no-platform-ios || fia --no-platform || ! { fia     --platform-ios || fia --platform-all                     ; };     PLATFORM_IOS=$?
fia    --no-build-release || fia    --no-build || ! { fia    --build-release || fia    --build-all || ! fia    --build ; };    BUILD_RELEASE=$?
fia      --no-build-debug || fia    --no-build || ! { fia      --build-debug || fia    --build-all || ! fia    --build ; };      BUILD_DEBUG=$?
fia       --no-build-test || fia    --no-build || ! { fia       --build-test || fia    --build-all || ! fia    --build ; };       BUILD_TEST=$?
fia         --no-run-unit || fia      --no-run || ! { fia         --run-unit || fia      --run-all || ! fia      --run ; };         RUN_UNIT=$?
fia         --no-run-perf || fia      --no-run || ! { fia         --run-perf || fia      --run-all || ! fia      --run ; };         RUN_PERF=$?
                                                  ! { fia          --install                                           ; };          INSTALL=$?
                                                  ! { fia            --clean                                           ; };            CLEAN=$?
                                                  ! { fia             --help                                           ; };             HELP=$?
                                                  ! { fia          --verbose                                           ; };          VERBOSE=$?
                                                  ! { fia   --show-artifacts                                           ; };   SHOW_ARTIFACTS=$?
# ---------------------------------------------------------------------------------------------------------------------------------------------

valid-flags() {
  echo -- --platform-android
  echo -- --platform-windows
  echo -- --platform-linux
  echo -- --platform-macos
  echo -- --platform-this
  echo -- --platform-ios
  echo -- --platform-all
  echo -- --no-platform-android
  echo -- --no-platform-windows
  echo -- --no-platform-linux
  echo -- --no-platform-macos
  echo -- --no-platform-this
  echo -- --no-platform-ios
  echo -- --no-platform
  echo -- --build-release
  echo -- --build-debug
  echo -- --build-test
  echo -- --build-all
  echo -- --no-build-release
  echo -- --no-build-debug
  echo -- --no-build-test
  echo -- --no-build
  echo -- --run-all
  echo -- --no-run-unit
  echo -- --no-run-perf
  echo -- --clean
  echo -- --help
  echo -- --verbose
  echo -- --show-artifacts
}

flags-are-valid() {
  echo "${GLOARGS[*]}" | tr ' ' '\n' | while read -r given
  do
    valid-flags | grep -cqo -- "$given" || {
      echo "Unexpected option: '$given'"
      return 1
    }
  done
}

inspect-all-options() {
  echo "PLATFORM_ANDROID=$PLATFORM_ANDROID"
  echo "PLATFORM_WINDOWS=$PLATFORM_WINDOWS"
  echo "  PLATFORM_LINUX=$PLATFORM_LINUX"
  echo "  PLATFORM_MACOS=$PLATFORM_MACOS"
  echo "   PLATFORM_THIS=$PLATFORM_THIS"
  echo "    PLATFORM_IOS=$PLATFORM_IOS"
  echo "   BUILD_RELEASE=$BUILD_RELEASE"
  echo "     BUILD_DEBUG=$BUILD_DEBUG"
  echo "      BUILD_TEST=$BUILD_TEST"
  echo "        RUN_UNIT=$RUN_UNIT"
  echo "        RUN_PERF=$RUN_PERF"
  echo "         INSTALL=$INSTALL"
  echo "           CLEAN=$CLEAN"
  echo "            HELP=$HELP"
}

inspect-options() {
  echo "[$(\
    to-lowercase \
    "$(inspect-all-options \
      | sed 's/.*=0$//' \
      | shellstrip \
      | sed 's/=1//g')")]"
}

help() {
  echo "
$0
  Build the Watcher.
  This file is a collection of shorthands for CMake invocations
  which eases over some platform-specific configuration, cross-
  compilation, and the many, many build targets:
    tests, sanitized builds, debug builds, release builds,
    installation targets, etc, etc, etc.

Usage
  $0 [--help | --clean | [PLATFORM_OPTIONS] [BUILD_OPTIONS] [RUN_OPTIONS]]

Default
  $0 --platform-this --build-all --run-unit

Platform Options
$(valid-flags | sed 's/ /\n  /g' | grep platform)

Build Options
$(valid-flags | sed 's/ /\n  /g' | grep build)

Run Options
$(valid-flags | sed 's/ /\n  /g' | grep run)

Examples
  $0 --build-all --no-run-perf --verbose
  $0 --no-run --build-all --platform-all
  $0 --build-release --install
  $0 --run-perf
  $0 --clean"
}
just-help() { help; exit 0; }

clean() {
  cmd="rm -rf '$PWD/out'"
  [ "$VERBOSE" = 1 ] && echo "$cmd"
  eval "$cmd"
}
just-clean() { clean; exit 0; }

# we need to use `tr` here because the bash on
# some github runners reject the ${var,,} syntax
to-uppercase() { echo "$1"   | tr '[:lower:]' '[:upper:]'          ; }
to-lowercase() { echo "$1"   | tr '[:upper:]' '[:lower:]'          ; }
shellstrip()   { tr '\n' ' ' | sed 's/\r\r/\r/g' | sed 's/  */ /g' ; }
guess-num-lcpu() {
  if [ "$(uname)" == Linux ]
  then
    if [ -f /proc/cpuinfo ] && grep -q 'cpu cores' /proc/cpuinfo
    then
      cat /proc/cpuinfo \
      | grep 'cpu cores' \
      | sed -E 's/.*: ([0-9]+)/\1/g' \
      | while read -r n
        do tot=$((tot + n))
        done
      echo $tot
    elif [ -f /proc/cpuinfo ] && grep -q 'processor' /proc/cpuinfo
    then
      cat /proc/cpuinfo \
      | grep 'processor' \
      | wc -l
    else nproc 2> /dev/null \
      || echo 1
    fi
  elif [ "$(uname)" == Darwin ]
  then sysctl -n hw.logicalcpu 2> /dev/null \
    || echo 1
  else echo 1
  fi
}
all-platforms() {
  echo android
  echo windows
  echo linux
  echo macos
  echo this
  echo ios
}
opt-platforms() {
  [ "$PLATFORM_ANDROID" = 1 ] && echo android
  [ "$PLATFORM_WINDOWS" = 1 ] && echo windows
  [ "$PLATFORM_LINUX"   = 1 ] && echo linux
  [ "$PLATFORM_MACOS"   = 1 ] && echo macos
  [ "$PLATFORM_THIS"    = 1 ] && echo this
  [ "$PLATFORM_IOS"     = 1 ] && echo ios
  return 0
}
out-path-of() {
  [ -n "$1" ] || return 1
  case "$1" in
    android) echo -n "$PWD/out/android" ;;
    windows) echo -n "$PWD/out/windows" ;;
    linux)   echo -n "$PWD/out/linux"   ;;
    macos)   echo -n "$PWD/out/macos"   ;;
    this)    echo -n "$PWD/out/this"    ;;
    ios)     echo -n "$PWD/out/ios"     ;;
    *)                              return 1  ;; esac
  if [ -n "$2" ]
  then
    case "$1" in
      windows) echo "/$2.exe" ;;
      *)       echo "/$2"     ;; esac
  else echo
  fi
}
infer-sysname-of() {
  [ -n "$1" ] || return 1
  case "$1" in
    darwin*) echo macos   ;;
    linux*)  echo linux   ;;
    mingw*)  echo windows ;;
    *)       echo "$1"    ;; esac
}
infer-sysname() {
  if [[ -n "$1" && "$1" != this ]]
  then infer-sysname-of "$1"
  else infer-sysname-of "$(to-lowercase "$(uname)")"
  fi
}
# Unused, but useful ...
# platform-out-paths() {
#   opt-platforms | while read -r platform
#   do path-of "$platform" || return 1
#   done
# }
# bestguess-cmake-generator-of-platform() {
#   case "$(infer-sysname "$(to-lowercase "$1")")" in
#     android) echo "-G 'Unix Makefiles'" ;;
#     windows) echo "-G 'Unix Makefiles'" ;;
#     linux)   echo "-G 'Unix Makefiles'" ;;
#     macos)   echo "-G 'Xcode'"          ;;
#     ios)     echo "-G 'Xcode'"          ;;
#     *)       echo "-G 'Unix Makefiles'" ;; esac ; }
cmake-targetsys-of-platform() {
  # Darwin isn't listed in:
  # https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html
  # So... Keep an eye out. The others should be fine, but WindowsStore
  # might need some tinkering -- Is it really universal?
  case "$(infer-sysname "$(to-lowercase "$1")")" in
    windows) echo WindowsStore ;;
    android) echo Android      ;;
    linux)   echo Linux        ;;
    macos)   echo Darwin       ;;
    ios)     echo iOS          ;;
    *)       return 1          ;; esac
}
all-buildtargets() {
  echo wtr.watcher
  echo wtr.watcher
  echo wtr.test_watcher
}
all-base-targets() {
  echo wtr.watcher
  echo wtr.test_watcher
}
all-sanitizers() {
  echo asan
  echo msan
  echo tsan
  echo ubsan
}
all-buildcfgs() {
  echo Debug
  echo Release
}
all-buildtargets() {
     all-base-targets | while read -r target
  do echo "$target"
  done
     all-base-targets | while read -r target
  do all-sanitizers   | while read -r sanitizer
  do echo "$target.$sanitizer"
  done
  done
}
all-buildtarget-paths() {
     all-platforms    | while read -r platform
  do all-buildcfgs    | while read -r buildcfg
  do all-buildtargets | while read -r target
  do echo "$(out-path-of "$platform" "$buildcfg")/$target"
  done
  done
  done
}
all-existing-buildtarget-paths() {
    all-buildtarget-paths \
  | while read -r tp
    do if [ -f "$tp" ]
       then echo "$tp"
       fi
    done
}
# Target configs and names from an argument to this program
opt-buildcfgs() {
  [ "$BUILD_DEBUG"   = 1 ] && echo Debug
  [ "$BUILD_RELEASE" = 1 ] && echo Release
}
opt-buildtargets() {
  [ "$BUILD_DEBUG"   = 1 ] && echo wtr.watcher
  [ "$BUILD_RELEASE" = 1 ] && echo wtr.watcher
  [ "$BUILD_TEST"    = 1 ] && echo wtr.test_watcher
}
existing-targets-of-platform() {
  [ -n "$1" ] || return 1 ; platform=$1
  opo() { out-path-of "$platform" "$1" ; }
  # find has no equivalent of -x
    all-buildcfgs \
  | while read -r buildcfg
    do find "$(opo "$buildcfg")" -maxdepth 1 -name 'wtr*' -or -name 'tw*' -or -name 'libw*' \
  | while read -r target
    do if [[ -x "$target" && -f "$target" ]]
       then echo "$target"
       fi
    done
    done
}
existing-matching-targets-in-pathptrn() {
    while read -r full_pathptrn
    do
    target_basepath=$(dirname "$full_pathptrn")
    target_pattern=$(basename "$full_pathptrn")
    find "$target_basepath" -maxdepth 1 -name "$target_pattern" \
  | while read -r target_binpath
    do if test -x "$target_binpath"
       then echo "$target_binpath"
       fi
    done
    done
}
show-buildcmd-of() {
  [ $# -eq 2 ] || return 1 ; platform=$1 ; buildcfg=$2
  echo "\
    [ ! -d '$(out-path-of "$platform" "$buildcfg")' ]
    && cmake
      -S '$PWD'
      -B '$(out-path-of "$platform" "$buildcfg")'
      -G 'Unix Makefiles'
      -DBUILD_LIB=ON
      -DBUILD_BIN=ON
      -DBUILD_HDR=ON
      -DBUILD_TESTING=ON
      -DBUILD_SAN=ON
      -DCMAKE_BUILD_TYPE='$buildcfg' $(
      [ "$platform" = android ] \
        && echo "-DCMAKE_ANDROID_NDK='$ANDROID_NDK_HOME'")
      $(targetsysname=$(cmake-targetsys-of-platform "$platform") \
        && echo "-DCMAKE_SYSTEM_NAME='$targetsysname'")
    ;
    cmake
      --build '$(out-path-of "$platform" "$buildcfg")'
      --config '$buildcfg'
      --parallel $(guess-num-lcpu)
    ;" \
    | sed -E 's/^  *//g'
}
opt-show-each-buildcmd() {
     opt-platforms | while read -r platform
  do opt-buildcfgs | while read -r buildcfg
  do show-buildcmd-of "$platform" "$buildcfg"
  done
  done
}
opt-build-each() {
  [ "$VERBOSE" = 1 ] && opt-show-each-buildcmd
  opt-show-each-buildcmd | shellstrip | bash -e
}
# Accessing variables outside of a
# subshell or a pipeline is evidently
# not portable. Instead, we'll echo
# a unique message and grep for it.
# The match count is the return value.
uniq_msg=b5c02513073841751ea8628d2b45e1e1
run-unit() {
  nfailed=$(
    all-existing-buildtarget-paths \
    | grep -i this/debug/wtr.test_watcher \
    | existing-matching-targets-in-pathptrn \
    | while read -r target
      do
        echo "$(basename "$target")" 1>&2
        "$target" '[not-perf]' 1>&2 || echo "$uniq_msg"
      done \
    | grep -c "$uniq_msg"
  )
  if [ "$nfailed" -eq 0 ]
  then echo "All test suites passed"
       return 0
  elif [ "$nfailed" -eq 1 ]
  then echo "$nfailed test suite failed"
       return "$nfailed"
  else echo "$nfailed test suites failed"
       return "$nfailed"
  fi
}
run-perf() {
  nfailed=$(
    all-existing-buildtarget-paths \
    | grep -i this/debug/wtr.test_watcher \
    | existing-matching-targets-in-pathptrn \
    | while read -r target
      do
        echo "$(basename "$target")" 1>&2
        "$target" '[perf]' 1>&2 || echo "$uniq_msg"
      done \
    | grep -c "$uniq_msg"
  )
  if [ "$nfailed" -eq 0 ]
  then echo "All test suites passed"
       return 0
  elif [ "$nfailed" -eq 1 ]
  then echo "$nfailed test suite failed"
       return "$nfailed"
  else echo "$nfailed test suites failed"
       return "$nfailed"
  fi
}
show-artifacts() {
  opt-platforms | while read -r platform
  do existing-targets-of-platform "$platform"
  done
  echo "$PWD/include"
  echo "$PWD/src"
}

#
#  Actual work
#

flags-are-valid     || { help ; exit 1 ; }
[ 1 = "$VERBOSE"  ] && { inspect-options ; }
[ 1 = "$HELP"     ] && { help ; exit 0 ; }
[ 1 = "$CLEAN"    ] && { clean || exit 1 ; }
[ 1 = "$SHOW_ARTIFACTS"  ] && { show-artifacts ; exit 0 ; }
opt-build-each      || { exit 1 ; }
[ 1 = "$RUN_UNIT" ] && { run-unit || exit 1 ; }
[ 1 = "$RUN_PERF" ] && { run-perf || exit 1 ; }
[ 1 = "$VERBOSE"  ] && { show-artifacts ; }
exit 0
