#!/bin/bash

# version   v0.3.0
# executed  by Make (during CI or manually)
# task      checks files against linting targets

set -eEuo pipefail
shopt -s inherit_errexit

REPOSITORY_ROOT=$(realpath "$(dirname "$(readlink -f "${0}")")"/../../)
LOG_LEVEL=${LOG_LEVEL:-debug}
HADOLINT_VERSION='2.12.0'
ECLINT_VERSION='2.7.2'
SHELLCHECK_VERSION='0.9.0'

# shellcheck source=./../../target/scripts/helpers/log.sh
source "${REPOSITORY_ROOT}/target/scripts/helpers/log.sh"

function _eclint() {
  # `/check` is used instead of `/ci` as the mount path due to:
  # https://github.com/editorconfig-checker/editorconfig-checker/issues/268#issuecomment-1826200253
  # `.ecrc.json` continues to explicitly ignores the `.git/` path to avoid any potential confusion
  if docker run --rm --tty \
    --volume "${REPOSITORY_ROOT}:/check:ro" \
    --workdir "/check" \
    --name dms-test_eclint \
    "mstruebing/editorconfig-checker:${ECLINT_VERSION}" ec -config "/check/test/linting/.ecrc.json"
  then
    _log 'info' 'ECLint succeeded'
  else
    _log 'error' 'ECLint failed'
    return 1
  fi
}

function _hadolint() {
  if docker run --rm --tty \
    --volume "${REPOSITORY_ROOT}:/ci:ro" \
    --workdir "/ci" \
    --name dms-test_hadolint \
    "hadolint/hadolint:v${HADOLINT_VERSION}-alpine" hadolint --config "/ci/test/linting/.hadolint.yml" Dockerfile
  then
    _log 'info' 'Hadolint succeeded'
  else
    _log 'error' 'Hadolint failed'
    return 1
  fi
}

# Create three arrays (F_SH, F_BIN, F_BATS) containing our BASH scripts
function _getBashScripts() {
  readarray -d '' F_SH < <(find . -type f -iname '*.sh' \
    -not -path './test/bats/*' \
    -not -path './test/test_helper/*' \
    -not -path './.git/*' \
    -print0 \
  )

  # shellcheck disable=SC2248
  readarray -d '' F_BIN < <(find 'target/bin' -type f -not -name '*.py' -print0)
  readarray -d '' F_BATS < <(find 'test/tests/' -type f -iname '*.bats' -print0)
}

# Check BASH files for correct syntax
function _bashcheck() {
  local ERROR=0 SCRIPT
  # .bats files are excluded from the test below: Due to their custom syntax ( @test ), .bats files are not standard bash
  for SCRIPT in "${F_SH[@]}" "${F_BIN[@]}"; do
    bash -n "${SCRIPT}" || ERROR=1
  done

  if [[ ${ERROR} -eq 0 ]]; then
    _log 'info' 'BASH syntax check succeeded'
  else
    _log 'error' 'BASH syntax check failed'
    return 1
  fi
}

function _shellcheck() {
  # This command is a bit easier to grok as multi-line.
  # There is a `.shellcheckrc` file, but it's only supports half of the options below, thus kept as CLI:
  # `SCRIPTDIR` is a special value that represents the path of the script being linted,
  # all sourced scripts share the same SCRIPTDIR source-path of the original script being linted.
  local CMD_SHELLCHECK=(shellcheck
    --external-sources
    --check-sourced
    --severity=style
    --color=auto
    --wiki-link-count=50
    --enable=all
    --exclude=SC2154
    --exclude=SC2310
    --exclude=SC2311
    --exclude=SC2312
    --source-path=SCRIPTDIR
  )

  local BATS_EXTRA_ARGS=(
    --exclude=SC2030
    --exclude=SC2031
    --exclude=SC2034
    --exclude=SC2155
  )

  # The linter can reference additional source-path values declared in scripts,
  # which in our case rarely benefit from extending from `SCRIPTDIR` and instead
  # should use a relative path from the project root (mounted at `/ci`), eg `target/scripts/`.
  # Note that `SCRIPTDIR` will strip a prefix variable for a source path, which can be useful
  # if `SCRIPTDIR` would always be the same value, and combined with relative path via another
  # `source-path=SCRIPTDIR/relative/path/to/scripts` in the .sh file.
  # These source-path values can apply to the entire file (and sourced files) if not wrapped in a function scope.
  # Otherwise it only applies to the line below it. You can declare multiple source-paths, they don't override the previous.
  # `source=relative/path/to/file.sh` will check the source value in each source-path as well.
  # shellcheck disable=SC2068
  local ERROR=0

  docker run --rm --tty \
    --volume "${REPOSITORY_ROOT}:/ci:ro" \
    --workdir "/ci" \
    --name dms-test_shellcheck \
    "koalaman/shellcheck-alpine:v${SHELLCHECK_VERSION}" "${CMD_SHELLCHECK[@]}" "${F_SH[@]}" "${F_BIN[@]}" || ERROR=1

  docker run --rm --tty \
    --volume "${REPOSITORY_ROOT}:/ci:ro" \
    --workdir "/ci" \
    --name dms-test_shellcheck \
    "koalaman/shellcheck-alpine:v${SHELLCHECK_VERSION}" "${CMD_SHELLCHECK[@]}" \
    "${BATS_EXTRA_ARGS[@]}" "${F_BATS[@]}" || ERROR=1

  if [[ ${ERROR} -eq 0 ]]; then
    _log 'info' 'ShellCheck succeeded'
  else
    _log 'error' 'ShellCheck failed'
    return 1
  fi
}

function _main() {
  case "${1:-}" in
    ( 'eclint'     ) _eclint                      ;;
    ( 'hadolint'   ) _hadolint                    ;;
    ( 'bashcheck'  ) _getBashScripts; _bashcheck  ;;
    ( 'shellcheck' ) _getBashScripts; _shellcheck ;;
    ( * )
      _log 'error' "'${1:-}' is not a command nor an option"
      return 3
      ;;
  esac
}

_main "${@}" || exit ${?}
