#!/bin/sh

# receive one package to be built from the build-list whose dependencies
# are already satisfied or which breaks a dependency cycle
# accepts a comma separated list of prefered packages as the first argument

# exit code shows state of success:
#  0: ok, I gave you an assignment
#  1: come back (shortly) later - I was running already
#  2: come back later - there are still packages to be built,
#     but currently none has all its dependencies ready
#  3: come back after the next run of get-package-updates - currently
#     there are no pending packages

# shellcheck source=conf/default.conf
. "${0%/*}/../conf/default.conf"

# TODO: honor manual build order of tool-chain:
# toolchain build order: linux-api-headers->glibc->binutils->gcc->binutils->glibc

mkdir -p "${work_dir}/package-states"

hand_out_assignment() {

  # we don't care anymore if an older version of this package was
  # "locked" or "broken"
  find "${work_dir}/package-states" -maxdepth 1 -regextype grep \
    -regex '.*/'"$(str_to_regex "$1")"'\(\.[^.]\+\)\{3\}\.\(locked\|broken\)' \
    -not -regex '.*/'"$(str_to_regex "$1.$2.$3.$4.")"'[^.]\+' \
    -delete

  # move that build order to the end of the build-list
  sed -i '
    /^'"$(str_to_regex "$1 $2 $3 $4")"'$/ {
      $ b
      d
    }
    $ a '"$1 $2 $3 $4" \
    "${work_dir}/build-list"

  # shellcheck disable=SC2016
  {
    printf 'SELECT '
    printf '`package_sources`.`%s`,' \
      'pkgbase' 'git_revision' 'mod_git_revision'
    printf '`upstream_repositories`.`name`,`binary_packages`.`sub_pkgrel`'
    printf ' FROM `upstream_repositories`'
    mysql_join_upstream_repositories_package_sources
    mysql_join_package_sources_build_assignments
    mysql_join_build_assignments_binary_packages
    mysql_join_binary_packages_repositories
    printf ' WHERE `repositories`.`name`="build-list"'
    printf ' AND `package_sources`.`%s`=from_base64("%s")' \
      'pkgbase' "$(printf '%s' "$1" | base64 -w0)" \
      'git_revision' "$(printf '%s' "$2" | base64 -w0)" \
      'mod_git_revision' "$(printf '%s' "$3" | base64 -w0)"
    printf ' AND `upstream_repositories`.`name`=from_base64("%s")' \
      "$(printf '%s' "$4" | base64 -w0)"
    printf ' LIMIT 1;\n'
  } | \
    mysql_run_query | \
    tr '\t' ' '

  {
    # shellcheck disable=SC2154
    echo "${slave}"
    if [ -r "${work_dir}/package-states/$1.$2.$3.$4.locked" ]; then
      cat "${work_dir}/package-states/$1.$2.$3.$4.locked"
    fi
  } | \
    sort -u | \
    sponge "${work_dir}/package-states/$1.$2.$3.$4.locked"
  # shellcheck disable=SC2016
  {
    printf 'UPDATE `build_slaves`'
    printf ' SET `currently_building` = ('
    printf ' SELECT `build_assignments`.`id`'
    printf ' FROM `build_assignments`'
    mysql_join_build_assignments_package_sources
    mysql_join_package_sources_upstream_repositories
    printf ' WHERE'
    printf ' `package_sources`.`%s` = from_base64("%s") AND' \
      'pkgbase'          "$(printf '%s' "$1" | base64 -w0)" \
      'git_revision'     "$(printf '%s' "$2" | base64 -w0)" \
      'mod_git_revision' "$(printf '%s' "$3" | base64 -w0)"
    printf ' `upstream_repositories`.`name` = from_base64("%s")' \
      "$(printf '%s' "$4" | base64 -w0)"
    printf ')'
    printf ' WHERE `build_slaves`.`name`=from_base64("%s");\n' \
      "$(printf '%s' "${slave}" | base64 -w0)"

    printf 'UPDATE `build_assignments`'
    mysql_join_build_assignments_package_sources
    mysql_join_package_sources_upstream_repositories
    printf ' SET `build_assignments`.`priority`=0'
    printf ' WHERE'
    printf ' `package_sources`.`%s` = from_base64("%s") AND' \
      'pkgbase'          "$(printf '%s' "$1" | base64 -w0)" \
      'git_revision'     "$(printf '%s' "$2" | base64 -w0)" \
      'mod_git_revision' "$(printf '%s' "$3" | base64 -w0)"
    printf ' `upstream_repositories`.`name` = from_base64("%s");\n' \
      "$(printf '%s' "$4" | base64 -w0)"
  } | \
    mysql_run_query

  # lock every loop this package breaks
  find "${work_dir}/build-list.loops" -maxdepth 1 -regextype grep \
    -regex '.*/loop_[0-9]\+' \
    -exec grep -qxF "$1" '{}' \; \
    -exec touch '{}.locked' \;

  exit 0

}

if [ -s "${work_dir}/build-master-sanity" ]; then
  >&2 echo 'Build master is not sane.'
  exit 1
fi

# Create a lock file and a trap.

exec 9> "${build_list_lock_file}"
if ! flock -n 9; then
  >&2 echo 'come back (shortly) later - I cannot lock build list.'
  exit 1
fi

exec 8> "${sanity_check_lock_file}"
if ! flock -s -n 8; then
  >&2 echo 'come back (shortly) later - sanity-check running.'
  exit 1
fi

clean_up() {
  rm -f "${build_list_lock_file}"
  rm -rf --one-file-system "${tmp_dir}"
}

tmp_dir=$(mktemp -d 'tmp.get-assignment.XXXXXXXXXX' --tmpdir)
trap clean_up EXIT

# if we're building something already, hand it out (again)
currently_building=$(
  # shellcheck disable=SC2016
  {
    printf 'SELECT '
    mysql_query_select_pkgbase_and_revision
    mysql_join_build_assignments_build_slaves
    mysql_join_build_assignments_binary_packages
    mysql_join_binary_packages_repositories
    printf ' WHERE `build_slaves`.`name`=from_base64("%s")' \
      "$(printf '%s' "${slave}" | base64 -w0)"
    printf ' AND `repositories`.`name`="build-list"'
    printf ' LIMIT 1;\n'
  } | \
    mysql_run_query
)

if [ -n "${currently_building}" ]; then
  # shellcheck disable=SC2086
  hand_out_assignment ${currently_building}
fi

# a package with [all dependencies met or which is part of a loop]
# and which is currently not being built, ordered by:
# 1: we requested it
# 2: its priority
# 3: is not yet built
# 4: was built the longest time ago
next_building=$(
  # shellcheck disable=SC2016
  {
    printf 'SELECT '
    printf '`package_sources`.`pkgbase`=from_base64("%s") AS `requested`,' \
      "$(
        printf '%s' "$1" | \
          base64 -w0
      )"
    printf '`build_assignments`.`priority`,'
    printf 'COALESCE('
    printf 'MAX(`failed_builds`.`date`),0'
    printf ') AS `last_trial`,'
    mysql_query_is_part_of_loop '`build_assignments`.`id`'
    printf ' AS `part_of_loop`,'
    mysql_query_select_pkgbase_and_revision
    mysql_join_build_assignments_binary_packages
    mysql_join_binary_packages_repositories
    printf ' LEFT'
    mysql_join_build_assignments_failed_builds
    printf ' WHERE `repositories`.`name`="build-list"'
    printf ' AND NOT EXISTS ('
      printf ' SELECT *'
      printf ' FROM `build_slaves`'
      printf ' WHERE `build_slaves`.`currently_building`=`build_assignments`.`id`'
    printf ') AND ('
      printf '`build_assignments`.`is_blocked` IS NULL'
      printf ' OR'
      printf ' `package_sources`.`pkgbase`=from_base64("%s")' \
        "$(
          printf '%s' "$1" | \
            base64 -w0
        )"
    printf ') AND ('
      mysql_query_is_part_of_loop '`build_assignments`.`id`'
      printf ' OR NOT '
      mysql_query_has_pending_dependencies '`build_assignments`.`id`'
    printf ')'
    printf ' GROUP BY `build_assignments`.`id`'
    printf ' ORDER BY `requested` DESC, `priority` DESC, `last_trial`, `part_of_loop`, `build_assignments`.`id`'
    printf ' LIMIT 1;\n'
  } | \
    mysql_run_query | \
    sed '
      y/\t/ /
      s/^.* \(\S\+\( \S\+\)\{3\}\)$/\1/
    '
)
if [ -n "${next_building}" ]; then
  # shellcheck disable=SC2086
  hand_out_assignment ${next_building}
fi

# Check if there are any pending packages at all
count_pending=$(
  # shellcheck disable=SC2016
  {
    printf 'SELECT count(*)'
    printf ' FROM `build_assignments`'
    mysql_join_build_assignments_binary_packages
    mysql_join_binary_packages_repositories
    printf ' WHERE `repositories`.`name`="build-list"'
    printf ' AND `build_assignments`.`is_blocked` IS NULL'
    printf ';\n'
  } | \
    mysql_run_query
)

if [ "${count_pending}" -eq 0 ]; then
  >&2 echo 'come back after the next run of get-package-updates - currently there are no pending packages'
  exit 3
else
  >&2 echo 'come back later - there are still packages to be built, but currently none has all its dependencies ready'
  exit 2
fi