#!/bin/bash
# adb, aimed at this checkout's own emulator, with screenshots scaled down
# before anything reads them.
#
# This is a wrapper, not a separate screenshot command, because a separate
# command is a rule to remember and this has to hold for every agent on
# this machine without anybody opting in. `adb exec-out screencap -p` is
# the normal way to take a screenshot here, so that is the thing that has
# to come back small.
#
# What it costs to read one is set by pixels, not bytes: an image is
# charged at about (width x height) / 750 tokens, after being scaled to fit
# 1568px on its long edge. So a 1080x2424 phone screenshot is ~1460 tokens
# however well the PNG is compressed, and the only lever that moves is the
# size. At 800px on the long edge it is ~380 -- and a 356x800 frame is
# still legible enough to check layout, contrast, alignment and small
# labels, which is what these are looked at for. Raise it for one call with
# ADB_SCREENCAP_MAX_EDGE if something genuinely needs the detail.
#
# The other half is which device a call means. Several agent sessions share
# this machine, each with an emulator named after its checkout, and a bare
# `adb shell` with two of them attached does not fail cleanly: `adb shell pm
# list packages` comes back *empty*, which reads as the app having been
# uninstalled rather than as the question being ambiguous. So when a call
# does not name a device and this checkout's own emulator is running, this
# aims at it. Nothing is guessed when it is not running: adb's own error is
# better than a wrapper picking somebody else's phone.
#
# Everything that is not a screencap is handed straight through, unchanged,
# including its exit status -- a wrapper that breaks `adb install` to save
# tokens on screenshots would not survive the week.
set -euo pipefail

. "$(dirname "$(readlink -f "$0")")/../lib/project-avd.sh"

# See `android_adb`: found without consulting PATH, which is how this script
# was called and would be how it called itself for ever.
real=$(android_adb) || {
    echo "adb: no platform-tools under any known SDK; set ANDROID_HOME" >&2
    exit 127
}

# Aim at this checkout's emulator, unless the caller has already said which
# device they mean or is asking something that is not about one.
#
# `-s` and friends are only valid before the subcommand, so the scan stops at
# the first word that is not one of adb's own options -- otherwise
# `adb shell echo -s` would look like a device selection.
target=()
if [ -z "${ANDROID_SERIAL:-}" ]; then
    subcommand=""
    named=false
    for arg in "$@"; do
        case "$arg" in
            -s | -d | -e | -t | -H | -P | -L)
                named=true
                break
                ;;
            -*) ;;
            *)
                subcommand="$arg"
                break
                ;;
        esac
    done
    case "$subcommand" in
        # Not about one device: `devices` lists them all, and the rest talk to
        # the adb server itself. Passing -s to these is at best ignored.
        devices | start-server | kill-server | version | help | connect | disconnect) ;;
        *)
            if [ "$named" = false ]; then
                avd=$(project_avd)
                serial=$(avd_serial "$real" "$avd" || true)
                if [ -n "$serial" ]; then
                    target=(-s "$serial")
                else
                    # This checkout's emulator is not running, and something
                    # else's is. Handing the call through would drive their
                    # device -- installing over the app they are looking at,
                    # or taking their foreground -- and it would look like it
                    # worked. A physical phone is not somebody's checkout and
                    # is left alone, which is why this asks for the AVD name
                    # rather than counting devices.
                    others=$(running_avds "$real" | awk -F'\t' -v mine="$avd" '$2 != mine')
                    if [ -n "$others" ]; then
                        {
                            echo "adb: '$avd' is not running, and these emulators belong to other checkouts:"
                            echo "$others" | while IFS=$'\t' read -r s n; do
                                printf '  %-16s %s\n' "$s" "$n"
                            done
                            echo
                            echo "  emu up                    start this checkout's own"
                            echo "  adb -s SERIAL ...         or say which you meant"
                            echo "  ANDROID_SERIAL=SERIAL     for a run of them"
                        } >&2
                        exit 1
                    fi
                fi
            fi
            ;;
    esac
fi

screencap=false
for arg in "$@"; do
    if [ "$arg" = screencap ]; then
        screencap=true
        break
    fi
done

# ffmpeg missing is a reason to hand over the full-size image, not to fail:
# the caller asked for a screenshot and is entitled to one.
if [ "$screencap" != true ] || ! command -v ffmpeg >/dev/null 2>&1; then
    exec "$real" "${target[@]}" "$@"
fi

max=${ADB_SCREENCAP_MAX_EDGE:-800}
tmp=$(mktemp -t adb-screencap.XXXXXX.png)
trap 'rm -f "$tmp"' EXIT
status=0
"$real" "${target[@]}" "$@" >"$tmp" || status=$?

# Only a PNG that actually arrived on stdout is ours to shrink. `adb shell
# screencap -p /sdcard/x.png` writes to the device and produces nothing
# here, and feeding that to ffmpeg would turn a working command into a
# failing one; so anything that is not a PNG goes back out byte for byte,
# with the real adb's exit status.
if [ "$status" -ne 0 ] || [ ! -s "$tmp" ] ||
    [ "$(head -c 8 "$tmp" | od -An -tx1 | tr -d ' \n')" != "89504e470d0a1a0a" ]; then
    cat "$tmp"
    exit "$status"
fi

# Never enlarge (min(1,...)), keep the aspect ratio, and keep both edges
# even, which some encoders insist on. If ffmpeg cannot make sense of it,
# the original still goes out: a smaller screenshot is worth having, a
# missing one is not.
scale="min(1,$max/max(iw,ih))"
if ! ffmpeg -y -loglevel error -i "$tmp" \
    -vf "scale='trunc(iw*$scale/2)*2':'trunc(ih*$scale/2)*2':flags=area" \
    -f image2pipe -c:v png -; then
    cat "$tmp"
fi
