#!/bin/sh

set -eu

usage() {
  cat <<EOF
usage: debci-batch [OPTIONS]

Options:

  -f, --force               Force test run on packages, even if no package in
                            its dependency chain changed. A package will still
                            not be tested if it was already tested today.
  -j N                      Test at most N packages in parallel
                            (default: 1)
  --offline                 Puts debci-batch offline. New test runs will not be
                            started.
  --online                  Puts debci-batch online. New test runs will be
                            started normally.

$@
EOF
}

short_options='j:f'
long_options='force,offline,online'

debci_base_dir=$(readlink -f $(dirname $(readlink -f $0))/..)
cd $debci_base_dir
. lib/environment.sh
. lib/functions.sh

tmp_dir=$(mktemp -d)
cleanup() {
  if [ -d "$tmp_dir" ]; then
    rm -rf "$tmp_dir"
  fi
}
trap cleanup INT TERM EXIT

one_month_ago="${tmp_dir}/one_month_ago"
touch -d '1 month ago' "${one_month_ago}"


run() {
  if which update-testbed >/dev/null 2>/dev/null; then
    log "I: Updating backend testbed"
    update-testbed
  else
    log "W: Backend $debci_backend does not provide a way to update testbed!"
  fi
  log "I: Finished update of backend testbed"

  log "I: building/updating chdist for $debci_suite"
  debci-setup-chdist

  log "I: start processing of all packages"

  process_all_packages

  log "I: finished processing of all packages"
}

all_packages_with_fastest_first() {
  # list of all packages, sorted by duration of their last test suite run
  #
  # new package have "unknown" as duration, and will sort first due to how
  # sort(1) works. We acknowledge that behavior and give new packages a chance
  # to be efficient. If they are not, they will be placed at the end of the
  # queue for the next run.
  debci-status --field duration_seconds --all | sort -k 2,2 -n | cut -d ' ' -f 1
}

process_all_packages() {
  local start=$(date +%s)

  # determine packages which need to be tested
  packages=''
  for pkg in $(all_packages_with_fastest_first); do
    if needs_processing $pkg; then
      packages="$packages $pkg"
    else
      report_status "$pkg" "skip"
    fi
  done

  if [ "$maxjobs" -eq 1 ]; then
    for pkg in $packages; do
      debci-test --index "$pkg"

      if [ -f "${debci_status_dir}/packages.json" ]; then
        local last_update=$(stat --format=%Y "${debci_status_dir}/packages.json")
        local now=$(date +%s)
        local update_interval=3600  # 1 hour
        if [ $(($now - $last_update)) -gt $update_interval ]; then
          debci-generate-index
        fi
      else
        debci-generate-index
      fi
    done
  else
    args=''
    for pkg in $packages; do
      args="$args --index $pkg"
    done
    parallel -n 2 -j "$maxjobs" debci-test -- $args
  fi

  local finish=$(date +%s)

  debci-generate-index --duration $(($finish - $start))
}

needs_processing() {
  status_dir=$(status_dir_for_package "$pkg")
  last_status=$(debci-status "$pkg")
  mkdir -p "${status_dir}"

  debci-list-dependencies "$pkg" > "$tmp_dir/${pkg}-deps.txt"
  reason="$status_dir/reason.txt"

  run=1

  if [ "$last_status" = 'tmpfail' ]; then
    run=0
    echo "∙ Retrying run since last attempt failed" >> $reason
  fi

  if [ -n "$force" ]; then
    run=0
    echo "∙ Forced test run for $pkg" >> $reason
  fi

  if [ -f "${status_dir}/latest.json" -a "${status_dir}/latest.json" -ot "${one_month_ago}" ]; then
    run=0
    echo '∙ Forcing test run after 1 month without one' >> $reason
  fi

  if [ -f "$status_dir/dependencies.txt" ]; then
    if diff -u --label last-run/dependencies.txt "$status_dir/dependencies.txt" --label current-run/dependencies.txt "$tmp_dir/${pkg}-deps.txt" > "$tmp_dir/${pkg}-changed-deps.diff"; then
      : # no need to run tests
    else
      run=0
      echo "∙ There were changes in the dependency chain since last test run:" >> $reason
      cat "$tmp_dir/${pkg}-changed-deps.diff" >> $reason
    fi
  else
    run=0
    echo "∙ First test run for $pkg" >> $reason
  fi

  if [ "$run" -eq 0 ]; then
    cp "$tmp_dir/${pkg}-deps.txt" "${status_dir}/dependencies.txt"
  fi

  return $run
}

# default configuration
maxjobs=1
force=''
offline_marker="$debci_data_basedir/offline"

while true; do
  arg="$1"
  shift
  case "$arg" in
    -j)
      maxjobs="$1"
      shift
      ;;
    -f|--force)
      force="$arg"
      ;;
    --offline)
      touch "${offline_marker}"
      exit 0
      ;;
    --online)
      rm -f "${offline_marker}"
      exit 0
      ;;
    --)
      break
      ;;
  esac
done

if [ -e "${offline_marker}" ]; then
  exit 0
fi

run_with_lock_or_exit \
  /var/lock/debci-batch-${debci_suite}-${debci_arch}.lock \
  run "$@"
