#!/usr/bin/env zsh
# vim: fdm=marker cms=\ #\ %s

set -o errexit
set -o nounset
set -o nonullglob # glob patterns with no
set -o nonomatch  # matches are passed through

bsu_bs_apiurl=
bsu_bs_package=
bsu_bs_project=
bsu_dloadurl=

declare -a bsu_specfiles

bsu_bs_commit=1
bsu_dryrun=0
bsu_test_build=0
declare -a bsu_osc_build

bsu_orig_pwd="$PWD"

bsu_configuration_processing=1

if test -f .bs-update-hooks; then
  . $PWD/.bs-update-hooks
fi

if test -f .bs-update; then
  . $PWD/.bs-update
fi

usage() # {{{
{
  local self="$SELF" exit=${1?} fd=1
  shift
  test $exit -ne 0 && fd=2
  {
    if test $exit -eq 1; then
      printf "%s: missing <tag> argument\n" "$self"
    elif test $exit -eq 2; then
      printf "%s: option -%c requires an argument\n" "$self" "$1"
    elif test $exit -eq 3; then
      printf "%s: unknown option -%c\n" "$self" "$1"
    elif test $exit -eq 4; then
      printf "%s: no %s specified\n" "$self" "$*"
    fi
    printf "%s: Usage: %s {-h|[options] <tag> [<version>]}\n" "$self" "$self"
    if test $exit -ne 0; then
      printf "%s: Use \"%s -h\" to see the full option listing.\n" "$self" "$self"
    else
      printf "  Options:\n"
      printf "    %-16s  %s\n" \
        "-h"              "Display this message" \
        "-A BSAPI"        "Use Build Service at BSAPI (URL or alias)" \
        "-B BUILDARGS"    "Use BUILDARGS (after field splitting) as" \
        ""                "arguments to osc build" \
        "-P PROJECT"      "Update Build Service package of PROJECT" \
        "-b"              "Build the package locally before committing" \
        "-d URL"          "Download the source tarball from URL" \
        "-m COMMITMSG"    "Use COMMITMSG for the commit in Build Service" \
        "-n"              "Report values derived from configuration files" \
        ""                "and command line arguments, then exit" \
        "-p PACKAGE"      "Update Build Service package named PACKAGE" \
        "-s SPECFILE"     "Use SPECFILE.in from TARBALL" \
        "-t TARBALL"      "Use TARBALL for the name of the downloaded file"
    fi
  } >&$fd
  if test $exit -eq 0; then
    exit 0
  else
    exit 1
  fi
} # }}}

SELF=$(basename "$0")

declare -i seen_dash_B seen_dash_s

while getopts :A:B:CP:bd:hm:np:s:t: optname; do
  case $optname in
  A) bsu_bs_apiurl=$OPTARG ;;
  B)
    if (( seen_dash_B++ )); then
      bsu_osc_build+=($OPTARG)
    else
      bsu_osc_build=$OPTARG
    fi
  ;;
  C) bsu_bs_commit=0 ;;
  P) bsu_bs_project=$OPTARG ;;
  b) bsu_test_build=1 ;;
  d) bsu_dloadurl=$OPTARG ;;
  h) usage 0 ;;
  m) bsu_bs_commitmsg="$OPTARG" ;;
  n) bsu_dryrun=1 ;;
  p) bsu_bs_package=$OPTARG ;;
  s)
    if (( seen_dash_s++ )); then
      bsu_specfiles+=($OPTARG)
    else
      bsu_specfiles=$OPTARG
    fi
  ;;
  t) bsu_tarball=$OPTARG ;;
  :) usage 2 $OPTARG ;; # option-argument missing
  ?) usage 3 $OPTARG ;; # unknown option
  esac
done
shift $(($OPTIND - 1))

if test $# -lt 1; then
  usage 1
fi

deref() # {{{
{
  case $1 in
  -a) shift; _deref_array "$@";;
  *) _deref_one "$@" ;;
  esac
} # }}}
_deref_one() # {{{
{
  local var=${1?}
  local prev='' result=${(P)var}
  while [[ $result != $prev ]]; do
    prev=$result
    result=${(e)result}
  done
  : ${(P)var::=$result}
} # }}}
_deref_array() # {{{
{
  local var=${1?}
  local prev=''
  local -a result; result=(${(P)var})
  while [[ $result != $prev ]]; do
    prev=$result
    result=(${(e)result})
  done
  : ${(AP)var::=$result}
} # }}}

abort() # {{{
{ {
  for a in "$@"; do
    printf "%s: %s\n" "$SELF" "$a"
  done
  exit 1
} >&2; } # }}}

bsu_tag=$1

if test $# -gt 1; then
  bsu_version="$2"
elif type bsu_version_hook 1>/dev/null 2>&1; then
  if test -z "${bsu_version-}"; then
    bsu_version="$(bsu_version_hook "$bsu_tag")" || {
      abort "bsu_version_hook failed" \
            "returned value: '$bsu_version'"
    }
  fi
else
  : ${bsu_version:="${bsu_tag#v}"}
fi

deref bsu_version

deref bsu_bs_apiurl

deref bsu_bs_project
test -n "$bsu_bs_project" || usage 4 project

deref bsu_bs_package
test -n "$bsu_bs_package" || usage 4 package

: ${bsu_tarball:=$bsu_bs_package-$bsu_version.tar.gz}
deref bsu_tarball
test -n "$bsu_tarball" || usage 4 tarball

deref bsu_dloadurl
test -n "$bsu_dloadurl" || usage 4 download url

: ${bsu_bs_commitmsg:="Update to version $bsu_version"}
deref bsu_bs_commitmsg

deref -a bsu_specfiles
if (( $#bsu_specfiles == 0 )); then
  usage 4 specfiles
fi

deref bsu_test_build
deref -a bsu_osc_build

pkgdir=$bsu_bs_project/$bsu_bs_package

if test 0 -ne $bsu_dryrun; then
  printf '%16s = %s\n' \
    bsu_bs_apiurl "${(qqq)bsu_bs_apiurl}" \
    bsu_bs_project "${(qqq)bsu_bs_project}" \
    bsu_bs_package "${(qqq)bsu_bs_package}" \
    bsu_dloadurl "${(qqq)bsu_dloadurl}" \
    bsu_tag "${(qqq)bsu_tag}" \
    bsu_version "${(qqq)bsu_version}" \
    bsu_bs_commit "${(qqq)bsu_bs_commit}" \
    bsu_bs_commitmsg "${(qqq)bsu_bs_commitmsg}" \
    bsu_tarball "${(qqq)bsu_tarball}" \
    bsu_specfiles "(${(j: :)${(@qqq)bsu_specfiles}})" \
    bsu_test_build "${(qqq)bsu_test_build}" \
    bsu_osc_build "(${(j: :)${(@qqq)bsu_osc_build}})" \

  exit $?
fi

bsu_configuration_processing=0

osc() # {{{
{
  local bsapi=${bsu_bs_apiurl}
  command osc ${=bsapi:+-A "$bsapi"} "$@"
} # }}}
bsu_checkout() # {{{
{
  osc co "$@"
} # }}}
bsu_commit() # {{{
{
  if test $bsu_bs_commit -eq 1; then
    osc ci "$@"
  fi
} # }}}
fetch() # {{{
{
  local url=${1?} tarball=${2?}
  case "$url" in
  http://*|https://*|ftp://*|ftps://*)
    wget -qO "$tarball" "$url"
    ;;
  .)
    if test -d $bsu_orig_pwd/.git; then
      git archive \
        --format tar.gz \
        --prefix "${tarball%.tar.gz}/" \
        --output "$tarball" \
        --remote "file://$bsu_orig_pwd" \
        "$bsu_tag"
    else
      hg archive \
        --repository "$bsu_orig_pwd" \
        --prefix "${tarball%.tar.gz}/" \
        --rev "$bsu_tag" \
        "$tarball"
    fi
    ;;
  esac
} # }}}
run_tarball_hook() # {{{
(
  # `set -o errexit` (`ERR_EXIT`) and `trap exithook ZERR` from
  # the top-most scope are active here as well.
  # `bsu_tarball_hook` failing would trigger `exithook` twice:
  # once for this function body shell (errexit -> ZERR), then
  # for the overall script bailing out (again: errexit -> ZERR).
  # see zshbuiltins(1), zshmisc(1), zshoptions(1).
  trap - ZERR
  local tarball=${1:?}
  if ! type bsu_tarball_hook 1>/dev/null 2>&1; then
    return 0
  fi
  bsu_tarball_hook $tarball
) # }}}
run_specfile_hook() # {{{
(
  # see run_tarball_hook
  trap - ZERR
  bsu_specfile_hook "$@"
) # }}}
update_specfiles() # {{{
{
  local version=${1:?} tag=${2:?} tarball=${3:?}
  shift 3
  if type bsu_specfile_hook 1>/dev/null 2>&1; then
    run_specfile_hook $version $tag $tarball "$@"
  else
    extract $tarball "$@"
  fi
  do_update_specfiles $bsu_version "$@"
} # }}}
extract() # {{{
{
  local tarball=${1?}
  local prefix=${tarball%.tar.gz}/
  shift
  tar -xzf $tarball --strip-components=1 ${${@/#/$prefix}/%/.in}
} # }}}
do_update_specfiles() # {{{
{
  local version=${1?} infile= specfile=
  shift
  for infile in "$@"; do
    specfile=$infile:t
    cp $infile.in $specfile
    sed -ri '/^[ \t]*#/!s/__VERSION__/'$version'/' $specfile
    if osc st $specfile | grep -q '^?'; then
      osc add $specfile
    fi
  done
} # }}}
update_tarball() # {{{
{
  local tarball=${1?} rewrite=0
  if test -f $pkgdir/$tarball; then
    rewrite=1
  fi
  if test 0 -eq $rewrite && test -f $pkgdir/$bsu_bs_package-*.tar.gz; then
    osc rm $pkgdir/$bsu_bs_package-*.tar.gz
  fi
  mv $tarball $pkgdir
  if test 0 -eq $rewrite; then
    osc add $pkgdir/$tarball
  fi
} # }}}
bsu_test_build() # {{{
{
  if test "$bsu_test_build" -ne 1; then
    return
  fi
  if (( 0 == $#bsu_osc_build )); then
    osc build
    return
  fi
  local args
  for args in "${(@)bsu_osc_build}"; do
    osc build $=args
  done
} # }}}
exithook() # {{{
{
  local -i ex=$?
  rm -rf $root || :
  return $((ex > 0))
} # }}}

trap exithook EXIT ZERR
root=$(mktemp -d)
cd $root
bsu_checkout $pkgdir
fetch $bsu_dloadurl $bsu_tarball
run_tarball_hook $bsu_tarball
update_tarball $bsu_tarball
cd $pkgdir
update_specfiles $bsu_version $bsu_tag $bsu_tarball "${(@)bsu_specfiles}"
bsu_test_build
bsu_commit -m "$bsu_bs_commitmsg"
