#!/usr/bin/env bash
#
# qdb-comic <input.txt> <output.png>
#
# Renders a qdb snapshot as a Microsoft Comic Chat strip, via Spittoon
# (statico/spittoon --- an implementation of the Comic Chat SIGGRAPH '96
# layout algorithm). This shim owns all the Spittoon knowledge:
# qdbviewer.lhs just calls `qdb-comic <txt> <png>` and streams the PNG.
#
# Pipeline:
#   1. strip the snapshot header, keep the transcript
#   2. turn each `<nick> message` line into a one-panel Spittoon spec
#      line, mapping each nick to an alias letter (Spittoon assigns a
#      random artwork character to each alias)
#   3. run Spittoon --- via spittoon-compat.rb, a shim that lets the
#      2005-era code run on modern RMagick --- retrying on its random
#      "variation didn't fit" crashes (upstream's recommended coping)
#   4. write the PNG atomically (render to a temp, then rename)
#
# Zero-config in the standard layout: Spittoon lives at /opt/spittoon,
# the comic config sits beside this script, and the artwork is either a
# `comic-assets/` dir beside this script (drop in the .avb-extracted
# Comic Chat avatars there) or, failing that, Spittoon's bundled example
# artwork. Overridable via environment for non-standard installs:
#
#   QDB_SPITTOON_LIB      Spittoon's lib/ dir          (/opt/spittoon/lib)
#   QDB_SPITTOON_BIN      make_comic.rb path           (/opt/spittoon/bin/make_comic.rb)
#   QDB_COMIC_ASSETS      dir to cd into; holds        (./comic-assets if it
#                         artwork/ + backgrounds/       has artwork/, else
#                                                       /opt/spittoon/examples)
#   QDB_SPITTOON_CONFIG   the Spittoon config.yaml     (./comic-config.yaml)
#   QDB_COMIC_MAXPANELS   cap on panels (long quotes)  (24)
#   QDB_COMIC_RETRIES     Spittoon attempts before giving up (5)
#   QDB_COMIC_CHUNK       max chars per balloon; longer (140)
#                         messages wrap across panels
#
# comic-config.yaml's artwork/background paths are kept relative (so
# the file is portable); qdb-comic absolutises them against
# QDB_COMIC_ASSETS into a temp config at render time, because Spittoon
# resolves them against the config file's own directory, not CWD.
#
# Exit 0 on success (PNG written), non-zero otherwise. Nothing is
# written to stdout --- qdbviewer.lhs redirects it to /dev/null anyway,
# but keeping it clean means this is also safe to pipe.

set -u

if [ "$#" -ne 2 ]; then
  echo "usage: qdb-comic <input.txt> <output.png>" >&2
  exit 2
fi

IN="$1"
OUT="$2"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

SPITTOON_LIB="${QDB_SPITTOON_LIB:-/opt/spittoon/lib}"
SPITTOON_BIN="${QDB_SPITTOON_BIN:-/opt/spittoon/bin/make_comic.rb}"
CONFIG="${QDB_SPITTOON_CONFIG:-$SCRIPT_DIR/comic-config.yaml}"
MAXPANELS="${QDB_COMIC_MAXPANELS:-24}"
RETRIES="${QDB_COMIC_RETRIES:-5}"
CHUNK="${QDB_COMIC_CHUNK:-140}"

# Assets dir (cd'd into before running Spittoon; holds artwork/ +
# backgrounds/). Prefer an explicit override, then a comic-assets/ dir
# beside this script (where .avb-extracted avatars go), then Spittoon's
# bundled example artwork.
if [ -n "${QDB_COMIC_ASSETS:-}" ]; then
  COMIC_ASSETS="$QDB_COMIC_ASSETS"
elif [ -d "$SCRIPT_DIR/comic-assets/artwork" ]; then
  COMIC_ASSETS="$SCRIPT_DIR/comic-assets"
else
  COMIC_ASSETS="/opt/spittoon/examples"
fi

# spittoon-compat.rb: shim that lets Spittoon (written for RMagick 1.x/
# 2.x) run on modern RMagick. Loaded via `ruby -r` before make_comic.rb.
COMPAT_SHIM="$SCRIPT_DIR/spittoon-compat.rb"

# Spittoon assigns a random artwork character to each *alias* name in a
# spec. CHARS is that alias pool --- it must have exactly as many
# entries as comic-config.yaml's `characters:` list, because Spittoon
# raises once a spec uses more distinct aliases than there are
# characters. Each IRC nick maps to an alias, cycling the pool, so a
# quote with more speakers than characters just reuses avatars. (Note:
# Spittoon's own alias resolver has no branch for real character names,
# so aliases --- not character names --- are the only thing that works.)
CHARS="a b c d e"

[ -r "$IN" ]            || { echo "qdb-comic: cannot read $IN" >&2; exit 1; }
[ -r "$CONFIG" ]        || { echo "qdb-comic: cannot read config $CONFIG" >&2; exit 1; }
[ -r "$COMPAT_SHIM" ]   || { echo "qdb-comic: compat shim not at $COMPAT_SHIM" >&2; exit 1; }
[ -f "$SPITTOON_BIN" ]  || { echo "qdb-comic: make_comic.rb not at $SPITTOON_BIN" >&2; exit 1; }
[ -d "$COMIC_ASSETS" ]  || { echo "qdb-comic: assets dir not at $COMIC_ASSETS" >&2; exit 1; }

# Absolute paths --- make_comic.rb is run from inside $SPITTOON_DIR so
# that the relative artwork/background dirs in config.yaml resolve.
case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac
case "$IN"  in /*) ;; *) IN="$PWD/$IN"   ;; esac

tmpspec="$(mktemp /tmp/qdb-comic.spec.XXXXXX)"
tmpconfig="$(mktemp /tmp/qdb-comic.cfg.XXXXXX)"
# RMagick picks the output format from the filename extension, so the
# tmp PNG must actually end in `.png` --- a mktemp template like
# `qdb-comic.png.XXXXXX` produces `.XXXXXX` as the extension instead.
tmppng="$(mktemp --suffix=.png /tmp/qdb-comic.XXXXXX)"
trap 'rm -f "$tmpspec" "$tmpconfig" "$tmppng"' EXIT

# --- build the Spittoon spec -------------------------------------------------
# Body starts after the first blank line. Each transcript message
# becomes one or more `<alias>: text` panels. A message longer than
# CHUNK characters is word-wrapped across several consecutive panels
# for the same speaker --- this is authentic Comic Chat behaviour (a
# long turn spans panels) and, crucially, keeps every balloon small
# enough that Spittoon's layout can place it: much above ~150 chars a
# single balloon raises UnplacableBalloonError ("Text will never fit")
# and the whole strip render aborts. Parens are stripped from text ---
# they are face/pose syntax in a spec --- and face/pose are left
# unspecified for Spittoon to randomise (the authentic Comic Chat feel,
# and fewer ways to crash).
awk -v chars="$CHARS" -v maxp="$MAXPANELS" -v chunk="$CHUNK" '
  function emit(alias, s) {                      # one panel, respecting the cap
    if (panels >= maxp) return
    print alias ": " s; print ""; panels++
  }
  function wrap(alias, text,    n, w, i, line, word) {
    n = split(text, w, /[ \t]+/)
    line = ""
    for (i = 1; i <= n; i++) {
      word = w[i]
      while (length(word) > chunk) {             # hard-split an over-long word
        if (line != "") { emit(alias, line); line = "" }
        emit(alias, substr(word, 1, chunk))
        word = substr(word, chunk + 1)
      }
      if (word == "") continue
      if (line == "") line = word
      else if (length(line) + 1 + length(word) <= chunk) line = line " " word
      else { emit(alias, line); line = word }
    }
    if (line != "") emit(alias, line)
  }
  BEGIN { nc = split(chars, C, " "); inbody = 0; panels = 0; nuniq = 0 }
  !inbody { if ($0 ~ /^[ \t]*$/) inbody = 1; next }
  {
    if (!match($0, /<[^>]+> /)) next            # skip joins/parts/notices
    nick = substr($0, RSTART + 1, RLENGTH - 3)
    msg  = substr($0, RSTART + RLENGTH)
    gsub(/^[ \t]+/, "", msg); gsub(/[ \t]+$/, "", msg)
    gsub(/[()]/, " ", msg)                      # parens are spec syntax
    if (msg == "") next
    if (panels >= maxp) next
    if (!(nick in seen)) { seen[nick] = C[(nuniq % nc) + 1]; nuniq++ }
    wrap(seen[nick], msg)
  }
' "$IN" > "$tmpspec"

if ! [ -s "$tmpspec" ]; then
  echo "qdb-comic: no renderable transcript lines in $IN" >&2
  exit 1
fi

# --- absolutise the config's artwork paths -----------------------------------
# Spittoon resolves background_dir / face_artwork_dir / pose_artwork_dir
# against the config file's own directory. Emit a temp config with those
# three (relative) paths rewritten absolute under $COMIC_ASSETS; values
# that are already absolute are left untouched.
sed -E "s#^(background_dir|face_artwork_dir|pose_artwork_dir):[[:space:]]+([^/].*)#\1: $COMIC_ASSETS/\2#" \
  "$CONFIG" > "$tmpconfig"

# --- run Spittoon, retrying on its random crashes ----------------------------
ok=0
i=0
while [ "$i" -lt "$RETRIES" ]; do
  i=$((i + 1))
  if ( cd "$COMIC_ASSETS" \
        && ruby -I "$SPITTOON_LIB" -r "$COMPAT_SHIM" "$SPITTOON_BIN" \
                -c "$tmpconfig" -s "$tmpspec" -o "$tmppng" \
     ) >/dev/null 2>&1 && [ -s "$tmppng" ]; then
    ok=1
    break
  fi
done

if [ "$ok" -ne 1 ]; then
  echo "qdb-comic: Spittoon failed after $RETRIES attempts" >&2
  exit 1
fi

# Atomic publish: rename only succeeds whole, so qdbviewer.lhs never
# streams a half-written PNG.
mv "$tmppng" "$OUT"
trap 'rm -f "$tmpspec" "$tmpconfig"' EXIT
exit 0
