#!/usr/bin/env bash
# vim: ft=bash
# —————————————————————————————————————————————————————————————————————————————————————
# NOTE: These setup scripts rely on an open source BASH framework BashMatic.
#       https://github.com/kigster/bashmatic
#
# The framework is pretty light-weight, and is installed in your $HOME/.bashmatic folder.
# You can safely remove that folder after the setup if you wish, although re-running the
# setup will re-install it.
# —————————————————————————————————————————————————————————————————————————————————————

set -e

# shellcheck disable=SC1091
source "bin/deps" 

export SHELL_INIT="${HOME}/.bashrc"
export BUNDLE_PATH="${BUNDLE_PATH:-${HOME}/.bundle/gems}"
export PATH="${HOME}/.rbenv/bin:${HOME}/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin:/opt/local/bin"

export BAZEL_OPTS="--host_jvm_args=-Xmx500m --host_jvm_args=-Xms500m"
export BAZEL_BUILD_OPTS="--curses=no --verbose_failures -j 30 --progress_report_interval=2"
export BAZEL_TEST_OPTS="--verbose_failures --test_verbose_timeout_warnings --verbose_explanations"
export RUBY_VERSION="$(__version.detect .ruby-version)"
export BUNDLER_VERSION=$(gem.gemfile.bundler-version)
export RULES_VERSION=$(__version.detect .rules_version)

h3bg "RUBY_VERSION=${RUBY_VERSION}"
 
export BashMatic__Expr="
  [[ -f ${SHELL_INIT} ]] && source ${SHELL_INIT};
  [[ -f ${HOME}/.bashmatic/init.sh ]] && source ${HOME}/.bashmatic/init.sh;
  set -e
"
deps.start-clock

run.set-all abort-on-error show-output-on

#—————————————————————————————————————————————————————————————————————————————————————————————————————————————
# Private Functions
#—————————————————————————————————————————————————————————————————————————————————————————————————————————————

__test-suite.setup.environment() {
  # shellcheck disable=SC2016
  grep -q 'bashmatic/init.sh' "${SHELL_INIT}" || echo '[[ -d ${HOME}/.bashmatic ]] && source ${HOME}/.bashmatic/init.sh' >>"${SHELL_INIT}"

  [[ -n $(command -v rbenv) ]] && {
    export PATH="${HOME}/.rbenv/shims:${HOME}/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
    # shellcheck disable=SC2016
    grep -q 'rbenv/shims' "${SHELL_INIT}" ||
      run "echo 'export PATH="$HOME/.rbenv/bin:${HOME}/.rbenv/shims:$PATH"' >> ${SHELL_INIT}"

    eval "$(rbenv init -)"

    # shellcheck disable=SC2016
    grep -q 'rbenv init' "${SHELL_INIT}" ||
      run "echo 'eval \"$(rbenv init -)\"' >>${SHELL_INIT}"
  }
}

# Setup rbenv if needed, and install Ruby but only if needed
__test-suite.setup.rbenv-ruby() {
  [[ -d "${HOME}/.rbenv" ]] || run "git clone https://github.com/rbenv/rbenv.git ${HOME}/.rbenv"

  export PATH="${HOME}/.rbenv/bin:${HOME}/.rbenv/shims:${PATH}"

  local rbenv_plugins="${HOME}/.rbenv/plugins/ruby-build"
  if [[ ! -d "${rbenv_plugins}" ]]; then
    run "git clone https://github.com/rbenv/ruby-build.git ${rbenv_plugins}"
  fi

  __test-suite.setup.environment

  set +e
  source "${SHELL_INIT}" 2>&1 1>/dev/null
  set -e

  run "rbenv install -s ${RUBY_VERSION}"
  run "rbenv global ${RUBY_VERSION}"
  run "rbenv rehash"
  run "ruby --version"
  run "gem update --system --no-doc"
  run "gem install bundler --version ${BUNDLER_VERSION} --no-doc"
  run "bundle install --jobs=4 --retry=3 --path ${BUNDLE_PATH}"
}

__test.actions() {
  local sep=', '
  printf "${bldylw}$(array.join "${sep}" $(util.functions-matching "test."))"
}

__test.exec() {
  local target
  target="$1"
  shift
  set -e
  /usr/bin/env bash -c "
    ${BashMatic__Expr}
    h2 \"Testing Target: ${bldcyn}${target}\"
    $*
  "
}

__test.header() {
  box.green-in-cyan "RulesRuby Test Suite Runner" "RubyRules Version ${bldylw}${RULES_VERSION}"
  test-suite.setup
}

[[ -n ${DEBUG} ]] && set -x

#—————————————————————————————————————————————————————————————————————————————————————————————————————————————
# Public Functions
#—————————————————————————————————————————————————————————————————————————————————————————————————————————————

test.help() {
  printf "
$(help-header USAGE)
  ${bldblk}# without any arguments runs a complete test suite.${clr}
$(help-usage "bin/test-suite")

  ${bldblk}# alternatively, a partial test name can be passed:${clr}
$(help-usage "bin/test-suite [ $(__test.actions " | ") ]")

$(help-header DESCRIPTION:)
  Shell script that runs all or partial tests. Can be used locally and on CI.

$(help-header EXAMPLES:)
$(help-example "bin/test-suite")

  Or, to run only one individual test group, pass it as an argument:

$(help-example bin/test-suite help)
$(help-example bin/test-suite workspace)
$(help-example bin/test-suite simple-example)

  etc.
"
  exit 0
}

# Runs Rubocop Tests
test.rubocop() {
  __test.exec rubocop "
    bundle install --path=${BUNDLE_PATH}
    bundle exec rubocop -E -D .rubocop.yml --force-exclusion
  "
}

# Runs Buildifier
test.buildifier() {
  __test.exec buildifier "
    bazel run :buildifier-check
  "
}

# Builds and runs workspace inside examples/simple_script
test.simple-script() {
  # This workspace requires a version specification
  local RUBY_VERSION="--@rules_ruby//ruby/runtime:version=ruby-3.0 "
  __test.exec simple-script "
    cd examples/simple_script
    bazel ${BAZEL_OPTS} info;                                                  echo; echo
    bazel ${BAZEL_OPTS} build ${BAZEL_BASE_BUILD_OPTS} ${RUBY_VERSION} -- //... ;                   echo; echo
    bazel ${BAZEL_OPTS} test ${BAZEL_BASE_BUILD_OPTS} ${BAZEL_TEST_OPTS}  ${RUBY_VERSION} -- //... ; echo; echo
    echo run :bin;        bazel ${BAZEL_OPTS} run ${BAZEL_BASE_BUILD_OPTS} ${RUBY_VERSION} :bin
    echo run :rubocop;    bazel ${BAZEL_OPTS} run ${BAZEL_BASE_BUILD_OPTS} ${RUBY_VERSION} :rubocop
  "
}

# Builds and runs workspace inside examples/simple_script
test.example-gem() {
  __test.exec example-gem "
    cd examples/example_gem
    echo bazel ${BAZEL_OPTS} build ...:all
    bazel ${BAZEL_OPTS} build --@rules_ruby//ruby/runtime:version=ruby-3.0 ...:all
  "
}

test.workspace() {
  __test.exec workspace "
    bazel ${BAZEL_OPTS} info;                                                  echo; echo
    bazel ${BAZEL_OPTS} build ${BAZEL_BUILD_OPTS} -- //... ;                   echo; echo
    bazel ${BAZEL_OPTS} test ${BAZEL_BUILD_OPTS} ${BAZEL_TEST_OPTS} -- //... ; echo; echo
    bazel ${BAZEL_OPTS} build ${BAZEL_BUILD_OPTS} --@rules_ruby//ruby/runtime:version=ruby-3.0 -- //... ;                   echo; echo
    bazel ${BAZEL_OPTS} test ${BAZEL_BUILD_OPTS} --@rules_ruby//ruby/runtime:version=ruby-3.0 ${BAZEL_TEST_OPTS} -- //... ; echo; echo
    bazel ${BAZEL_OPTS} build ${BAZEL_BUILD_OPTS} --@rules_ruby//ruby/runtime:version=jruby-9.4 -- //... ;                   echo; echo
    bazel ${BAZEL_OPTS} test ${BAZEL_BUILD_OPTS} --@rules_ruby//ruby/runtime:version=jruby-9.4 ${BAZEL_TEST_OPTS} -- //... ; echo; echo
  "
}

# Private
test.bazel-info() {
  __test.exec bazel-info "
    bazel ${BAZEL_OPTS} version; echo; echo
    bazel ${BAZEL_OPTS} info; echo; echo
    bazel ${BAZEL_OPTS} fetch --curses=no -- '//ruby/...'
  "
}

# Run rspecs once we have something in the spec folder.
test.rspec() {
  [[ -d "spec" ]] || return 0
  __test.exec rspec "
    bundle check || bundle install
    bundle exec rspec
  "
}

test.all() {
  test.bazel-info
  test.workspace
  test.simple-script
  test.example-gem
  test.rubocop
  test.rspec
  test.buildifier
}

test-suite.setup() {
  # shellcheck disable=SC1090
  [[ -f ${SHELL_INIT} ]] && source "${SHELL_INIT}"

  if [[ -n $(command -v ruby) ]]; then
    CURRENT_VERSION=$(ruby -e 'puts RUBY_VERSION')
    if [[ ${CURRENT_VERSION} != "${RUBY_VERSION}" ]]; then
      __test-suite.setup.rbenv-ruby
    else
      info "Ruby already exists and matches the version:"
      info "$(command -v ruby), version: ${bldylw}$(ruby --version)"
    fi
  else
    hl.subtle "Ruby wasn't found, installing via rbenv..."
    __test-suite.setup.rbenv-ruby
  fi
}

test-suite.main() {
  local action="${1}"

  [[ "${action}" == "-h" || ${action} == "--help" ]] && action="help"
  [[ -z ${action} ]] && action=all
  local func="test.${action}"

  if [[ -n ${action} && ${action} != "all" ]]; then
    shift
    if util.is-a-function "${func}"; then
      [[ "${action}" != "help" ]] && {
        __test.header
        h1 "Executing partial tests for action ${bldylw}${action}"
      }
      ${func} "$@"
      local code=$?
      if [[ ${code} -eq 0 ]]; then
        success "Test Suite for ${action} was successful"
      else
        error "Error running tests for ${action}"
      fi
      deps.print-duration
      exit ${code}
    else
      info "Invalid action ${action}."
      info "List of valid actions are: $(__test.actions)"
      exit 1
    fi
  else
    __test.header
    run.set-all abort-on-error
    set -e
    test.all
    success "Test suite was successful!"
    deps.print-duration
  fi
}

test-suite.main "$@"

set +x
