#!/bin/sh
# vim: sw=2 sts=2 et fdm=marker cms=\ #\ %s

set -e
set -u

basex="${basex:-/usr/bin/basex}"

: ${FAKE_BACKEND_CHUNKSIZE:=255}

# need to protect this code from already-created fakes {{{
# this must be syntax-, not command-based,
# to prevent stumbling on a pre-existing fake.
[ "${FAKE_BINDIR-unset}" = unset ] || {
  opath="$PATH"
  npath=

  while [ -n "$opath" ]; do
    seg="${opath%%:*}"
    case "$opath" in
    *:*) opath="${opath#*:}" ;;
    *)   opath= ;;
    esac
    [ "$seg" = "$FAKE_BINDIR" ] || {
      npath="$npath:$seg"
    }
  done
  export PATH="${npath#:}"
}
# }}}

usage() # {{{
{
  local ex=${1-100}
  test $ex -ne 100 || printf >&2 'fake: error: missing operand\n'
  printf >&2 'fake: usage: %s -h|-hh\n' "${0##*/}"
  printf >&2 'fake: usage: %s [-b | -e | -o] [-c] [-v] [-x N] CMD [ARG...]\n' "${0##*/}"
  printf >&2 'fake: usage: %s -p [-c] CMD [ARG...]\n' "${0##*/}"
  printf >&2 'fake: usage: %s -w CMD [ARG...]\n' "${0##*/}"
  if test $ex -gt -2; then
    printf >&2 'fake: use `fake -hh` to display help\n'
  else
    cat >&2 <<-\EOF

	Options:

	  -h      Display short usage help.
	          Given twice, display full help.

	  -b      Body.  Created fake will consist of current stdin.
	  -c      Catchall.  Created fake will receive
	          any uses of CMD not covered by other,
	          more specific fakes.
	  -e      Error.  Created fake will emit current
	          stdin to its stderr.
	  -o      Output.  Created fake will emit current
	          stdin to its stdout.
	  -p      Pass-through.  Created fake will re-execute
	          its argv again after removing $FAKE_BINDIR
	          from $PATH.
	  -v      Verbose.  Reflect received argv on stderr.
	  -w      Which.  Print pathname of the fake that
	          would receive given CMD [ARG...].
	  -x N    Exit code.  Fake should exit with N.
EOF
  fi
  if test $ex -lt 0; then
    exit 0
  fi
  exit $ex
} # }}}

#### SHARED {{{

basex() # {{{
{
  command "$basex" "$@"
} # }}}

catchalls() # {{{
{
  local c=
  find "${1:?}" \
    -mindepth 1 \
    -maxdepth 1 \
    -name '*+*' \
  | while read c; do \
      find "$c" -type f; \
    done \
  | sort -rn
} # }}}

findimpl() # {{{
{
  local rv=$1 impldir="$2" argc="$3" hargv="$4"
  local be="$impldir/$argc-${hargv:+/$hargv}"
  eval $rv=
  if test -x "$be"; then
    eval $rv='"$be"'
    return
  fi
  local p=
  test -d "$impldir" || return 1
  hargv="$(printf -- "%s" "$hargv" | sed 's:/::g')"
  for p in $(catchalls "$impldir"); do
    local f="${p#$impldir/}"
    local fargc="${f%%+*}"
    local fargv="${f#*+}"
    fargv="$(printf -- "%s" "$fargv" | sed 's:/::g')"
    [ $argc -ge $fargc ] || continue
    if [ "$hargv" = "$fargv" ] || [ -z "$fargv" ]; then
      eval $rv='"$p"'
      return
    fi
    case "$hargv" in
    "$fargv-"*)
      eval $rv='"$p"'
      return
    ;;
    esac
  done
  return 1
} # }}}

expected() # {{{
{
  local name="${1#$impldir/}"
  set --
  if test "$name" = 0; then
    :
  else
    local argc="${name%%[-+]*}"
    local argv="${name#*[-+]/}"
    local arg=
    local i=1
    argv="$(printf -- "%s" "$argv" | sed 's:/::g')"
    while test $i -le $argc; do
      arg="${argv%%-*}"
      argv="${argv#*-}"
      set -- "$@" "$(printf -- "%s" "$arg" | basex -d base32hex)"
      i=$((i+1))
    done
  fi
  local fmt=
  case "${name#$argc}" in
  -*) fmt="fake: expected: %s\n" ;;
  +*) fmt="fake: expected: %s [...]\n" ;;
  esac
  printf -- "$fmt" "$ARGV0${*:+ $*}"
} # }}}

mismatch() # {{{
{
  local impldir="$1" fname=
  shift
  printf -- "fake: error: argv mismatch\n"
  find "$impldir" -mindepth 1 -type f -print | sort | while read fname; do
    expected "$fname"
  done
  printf -- "fake: received: %s" "$ARGV0"
  test $# -eq 0 || printf -- " %s" "$@"
  printf -- "\n"
  exit 100
} >&2 # }}}

hashargs() # {{{
{
  local rv=$1 lhargv= arg=; shift
  eval $rv=
  for arg in "$@"; do
    lhargv="$lhargv-$(printf -- "%s" "$arg" | basex -e base32hex)"
  done
  lhargv="${lhargv#-}"
  eval $rv='"$lhargv"'
} # }}}

#### SHARED }}}

catchall=0
copy_body=nope
passthrough=0
has_output=0
reflect=0
exitcode=
usage=0
which=0

while getopts hbceopvwx: OPTNAME; do
case "$OPTNAME" in
h) usage=$((usage - 1)) ;;

b) copy_body=asis ;;
c) catchall=1 ;;
e) copy_body=stderr ;;
o) copy_body=stdout ;;
p) passthrough=1 ;;
v) reflect=1 ;;
w) which=1 ;;
x) exitcode="$OPTARG" ;;
esac
done; shift $((OPTIND - 1))

test $usage -eq 0 || usage $usage

if test $# -eq 0; then
  usage 100
fi

cmd="$1"; shift
bindir=${FAKE_BINDIR:?}
frontend="$bindir/$cmd"
impldir="$bindir/.$cmd"

if test $catchall -eq 0; then
  name="$impldir/$#-"
else
  name="$impldir/$#+"
fi
hargv=
hashargs hargv "$@"

hargv="$(printf "%s" "$hargv" | sed 's:.\{'"$FAKE_BACKEND_CHUNKSIZE"'\}:&/:g')"
hargv="${hargv%/}"
name="$name${hargv:+/$hargv}"

impl=
if test $which -eq 1; then
  findimpl impl "$impldir" "$#" "$hargv" || exit 1
  printf "%s\n" "$impl"
  exit 0
fi

impl_has_body() # {{{
{
  cat > "${1:?}"
} # }}}
impl_passthrough() # {{{
{
  sed 's/^ *//' > "${1:?}" <<\EOF
  #!/bin/sh
  exec "$ARGV0" "$@"
EOF
} # }}}
impl_has_errout() # {{{
{
  {
    printf -- "%s\n" "#!/bin/sh"
    printf -- "%s\n" "cat >&${1:?} <<\\EOF"
    cat
    printf -- "%s\n" "EOF"
  } > "$name"
} # }}}
impl_reflect() # {{{
{
  sed 's/^ *//' > "${1:?}" <<\EOF
  #!/bin/sh
  exec >&2
  printf "%s" "${ARGV0##*/}"
  test $# -eq 0 || printf " %s" "$*"
  printf "\n"
EOF
} # }}}
impl_default() # {{{
{
  {
    printf -- "%s\n" "#!/bin/sh"
  } > "${1:?}"
} # }}}

mkdir -p "${name%/*}"

if test $copy_body = asis; then
  impl_has_body "$name"
elif test $copy_body = stderr; then
  impl_has_errout 2 "$name"
elif test $copy_body = stdout; then
  impl_has_errout 1 "$name"
elif test $reflect -ne 0; then
  impl_reflect "$name"
elif test $passthrough -ne 0; then
  impl_passthrough "$name"
else
  impl_default "$name"
fi
test -z "$exitcode" || printf -- >> "$name" "exit %s\n" "$exitcode"
chmod +x "$name"

{
  printf '#!/bin/sh\n\n'
  sed -n '/^#### SHARED {{{/,/### SHARED }}}/p' < "$0"
  sed -n '/^#### BEGIN FRONTEND$/,$p' < "$0" |
  sed \
    -e "s#PATH#$PATH" \
    -e "s#basex#$basex" \
    -e "s#impldir#$impldir"
} > "$frontend"
chmod a+x "$frontend"
exit

#### BEGIN FRONTEND

impldir="#impldir#"
basex="${basex:-#basex#}"

export PATH="#PATH#"

hashargs hargv "$@"

export ARGV0="${0##*/}"

impl=
if findimpl impl "$impldir" "$#" "$hargv"; then
  exec "$impl" "$@"
fi

mismatch "$impldir" "$@"
