#!/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.
  --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='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

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

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


run() {
  log "I: debci-batch started $(date)"

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

  log "I: start processing of all packages"

  process_all_packages

  log "I: debci-batch finished $(date)"
}

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)

  # TODO: we need something more flexible than $debci_backend here -- look at
  # tests' isolation restrictions
  amqp-declare-queue --url ${debci_amqp_server} --durable -q $debci_amqp_queue >/dev/null

  # determine packages which need to be tested and request tests
  for pkg in $(all_packages_with_fastest_first); do
    if (! already_enqueued $pkg) && needs_processing $pkg; then
      record_enqueued "$pkg"
      debci-enqueue "$pkg"
    else
      report_status "$pkg" "skip"
    fi
  done
}

needs_processing() {
  local tmp_dir="$base_tmp_dir/${debci_suite}/${debci_arch}/${pkg}"
  mkdir -p "$tmp_dir"
  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
}

already_enqueued() {
  local pkg="$1"
  # XXX if you change the line below also change in record_enqueued()
  local queue_marker="$(status_dir_for_package "$pkg")/queue.txt"
  local last_result="$(status_dir_for_package "$pkg")/latest.json"
  if [ -e "$queue_marker" ]; then
    if [ -e "$last_result" ]; then
      # already enqueued if last result is older than last request
      test "$last_result" -ot "$queue_marker"
    else
      # already enqueued, just not finished yet
      return 0
    fi
  else
    # never enqueued before
    return 1
  fi
}

record_enqueued() {
  local pkg="$1"
  # XXX if you change the line below also change in already_enqueued()
  local queue_marker="$(status_dir_for_package "$pkg")/queue.txt"
  mkdir -p "$(dirname "$queue_marker")"
  echo "Enqueued at $(date)" > "$queue_marker"
}

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

while true; do
  arg="$1"
  shift
  case "$arg" in
    -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 "$debci_batch_lock" run "$@"
