#!/bin/sh
# =====================================================================
#  How a Computer Is Made - single-file setup for Linux (Education
#  Series, Title 1). Run it:  ./HowAComputerIsMade-Linux-Setup
#
#  What happens: the setup downloads the course parts once (internet
#  required, each part checksum-verified, the whole package verified
#  again after assembly), installs them under
#  ~/.local/share/hacm/, leaves a "How a Computer Is Made" entry in
#  your applications menu (and on the desktop when one exists), then
#  OPENS the course in your browser by itself.
#
#  Other actions:
#    ./HowAComputerIsMade-Linux-Setup --selftest   prove install +
#         learner path (quiz, saved progress) in a throwaway home
#    ./HowAComputerIsMade-Linux-Setup --no-launch  install only
# =====================================================================
set -u
HACM_VER="0.1.0"
HACM_BASE="${HACM_BASE_URL:-https://education.48hourrfprescue.com/downloads}"
HACM_ZIP_SHA="b8fa9656967699b01f77c31adab6312ff2d21c00363ae15a5afe6a7ed2dc0683"
HACM_PARTS="hacm-payload.part01:197516ca25450ffebebc53cfc8ee461e38e5fa0f36454161c318369746dd7162 hacm-payload.part02:bafb9f8ecbe27737f5c470d4af9c969c2bc8a37e952c0a2a181c61a16780fe8a"
HACM_PORT="${HACMPORT:-8791}"
BASE="${XDG_DATA_HOME:-$HOME/.local/share}/hacm"
CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/hacm-setup"

say() { printf '%s\n' "$*"; }
fail() { printf 'Setup stopped: %s\n' "$*" >&2; exit 1; }

have() { command -v "$1" >/dev/null 2>&1; }

download() {  # url dest
    if have curl; then curl -fL --retry 2 -o "$2" "$1" && return 0; fi
    if have wget; then wget -q -O "$2" "$1" && return 0; fi
    python3 -c 'import sys,urllib.request; urllib.request.urlretrieve(sys.argv[1], sys.argv[2])' "$1" "$2"
}

sha_of() {  # file -> sha256 hex
    if have sha256sum; then sha256sum "$1" | awk '{print $1}';
    else python3 -c 'import sys,hashlib; print(hashlib.sha256(open(sys.argv[1],"rb").read()).hexdigest())' "$1"; fi
}

fetch_payload() {
    mkdir -p "$CACHE"
    ZIP="$CACHE/hacm-payload.zip"
    if [ -f "$ZIP" ] && [ "$(sha_of "$ZIP")" = "$HACM_ZIP_SHA" ]; then
        say "Course package already downloaded and verified."
        return 0
    fi
    : > "$CACHE/assembled.zip"
    for pair in $HACM_PARTS; do
        name=${pair%%:*}; want=${pair##*:}
        part="$CACHE/$name"
        if [ ! -f "$part" ] || [ "$(sha_of "$part")" != "$want" ]; then
            say "Downloading $name ..."
            download "$HACM_BASE/$name" "$part" || fail "could not download $name - check your internet connection and run the setup again."
            [ "$(sha_of "$part")" = "$want" ] || fail "$name failed its checksum - the download was damaged. Run the setup again."
        fi
        cat "$part" >> "$CACHE/assembled.zip"
    done
    [ "$(sha_of "$CACHE/assembled.zip")" = "$HACM_ZIP_SHA" ] || fail "the assembled course package failed its checksum. Run the setup again."
    mv "$CACHE/assembled.zip" "$ZIP"
    say "Course package downloaded and verified."
}

install_app() {
    fetch_payload
    mkdir -p "$BASE"
    rm -rf "$BASE/app.new"
    mkdir -p "$BASE/app.new"
    python3 - "$CACHE/hacm-payload.zip" "$BASE/app.new" <<'PYEOF'
import sys, zipfile
zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])
PYEOF
    [ -f "$BASE/app.new/server.py" ] || fail "the course package did not unpack correctly."
    rm -rf "$BASE/app"
    mv "$BASE/app.new" "$BASE/app"
    cat > "$BASE/launch" <<'LAUNCHEOF'
#!/bin/sh
# Opens How a Computer Is Made: starts the local course server if it
# is not already running, then opens the course in the browser.
PORT="${HACMPORT:-8791}"
APP="$HOME/.local/share/hacm/app"
LOG="$HOME/.local/share/hacm/server.log"
healthy() {
    python3 - "$PORT" <<'PYEOF' >/dev/null 2>&1
import sys, urllib.request
try:
    r = urllib.request.urlopen("http://127.0.0.1:%s/api/health" % sys.argv[1], timeout=1)
    sys.exit(0 if r.status == 200 else 1)
except Exception:
    sys.exit(1)
PYEOF
}
if ! healthy; then
    HACMPORT="$PORT" nohup python3 "$APP/server.py" >>"$LOG" 2>&1 &
    i=0
    while [ $i -lt 50 ]; do healthy && break; i=$((i+1)); sleep 0.2; done
fi
if [ -z "${HACM_NOOPEN:-}" ]; then
    xdg-open "http://127.0.0.1:$PORT/" >/dev/null 2>&1 || \
        printf 'Open http://127.0.0.1:%s/ in your browser.\n' "$PORT"
fi
LAUNCHEOF
    chmod +x "$BASE/launch"
    APPS="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
    mkdir -p "$APPS"
    cat > "$APPS/hacm.desktop" <<DESKEOF
[Desktop Entry]
Type=Application
Name=How a Computer Is Made
Comment=Education Series - how every part of a computer is made
Exec=$BASE/launch
Icon=applications-education
Categories=Education;
DESKEOF
    if [ -d "$HOME/Desktop" ]; then cp "$APPS/hacm.desktop" "$HOME/Desktop/" 2>/dev/null || true; fi
    say "Installed under $BASE/app (version $HACM_VER)."
}

selftest() {
    TMHOME=$(mktemp -d)
    export HOME="$TMHOME" HACMPORT=8899 HACM_NOOPEN=1
    BASE="${XDG_DATA_HOME:-$HOME/.local/share}/hacm"
    CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/hacm-setup"
    say "selftest: installing into throwaway home $TMHOME"
    install_app
    HACMPORT=8899 nohup python3 "$BASE/app/server.py" >"$TMHOME/server.log" 2>&1 &
    SVPID=$!
    python3 - "$BASE/app/lessons.json" <<'PYEOF'
import json, sys, time, urllib.request
BASE = "http://127.0.0.1:8899"
def call(path, data=None):
    req = urllib.request.Request(BASE + path,
        data=json.dumps(data).encode() if data is not None else None,
        headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=5) as r:
        return json.loads(r.read().decode())
for _ in range(50):
    try:
        h = call("/api/health"); break
    except Exception:
        time.sleep(0.2)
else:
    print("FAIL health: server never answered"); sys.exit(1)
print("PASS health:", h["app"], h["version"])
lst = call("/api/lessons")
assert len(lst["lessons"]) == 10, lst
print("PASS lessons list: 10 lessons")
one = call("/api/lesson/cpu")
assert "answer" not in json.dumps(one["quiz"]), "answer key leaked"
print("PASS answer key not shipped to browser")
full = {l["id"]: l for l in json.load(open(sys.argv[1]))}
answers = [q["answer"] for q in full["cpu"]["quiz"]]
res = call("/api/quiz", {"lesson": "cpu", "answers": answers})
assert res["score"] == 3, res
print("PASS quiz grading: 3/3 correct answers score 3")
res2 = call("/api/quiz", {"lesson": "cpu", "answers": [9, 9, 9]})
assert res2["score"] == 0, res2
print("PASS quiz grading: wrong answers score 0")
call("/api/progress", {"lesson": "cpu", "watched": True})
prog = call("/api/progress")
assert prog["lessons"]["cpu"]["watched"] and prog["lessons"]["cpu"]["quizPassed"]
print("PASS progress recorded in memory: watched + quizPassed")
# range request on the video (browser-style seek)
req = urllib.request.Request(BASE + "/media/cpu.mp4", headers={"Range": "bytes=0-1023"})
with urllib.request.urlopen(req, timeout=5) as r:
    assert r.status == 206 and len(r.read()) == 1024
print("PASS video range request: HTTP 206, 1024 bytes")
PYEOF
    RC=$?
    kill $SVPID 2>/dev/null; sleep 1
    if [ $RC -ne 0 ]; then say "selftest FAILED (phase 1)"; rm -rf "$TMHOME"; return 1; fi
    HACMPORT=8899 nohup python3 "$BASE/app/server.py" >"$TMHOME/server2.log" 2>&1 &
    SVPID=$!
    python3 - <<'PYEOF'
import json, time, urllib.request
for _ in range(50):
    try:
        with urllib.request.urlopen("http://127.0.0.1:8899/api/progress", timeout=3) as r:
            prog = json.loads(r.read().decode())
        break
    except Exception:
        time.sleep(0.2)
else:
    print("FAIL persistence: server never answered after restart"); raise SystemExit(1)
cpu = prog["lessons"]["cpu"]
assert cpu.get("watched") and cpu.get("quizPassed"), cpu
print("PASS progress survives a server restart (saved on disk)")
PYEOF
    RC=$?
    kill $SVPID 2>/dev/null
    if [ $RC -ne 0 ]; then say "selftest FAILED (phase 2)"; rm -rf "$TMHOME"; return 1; fi
    rm -rf "$TMHOME"
    say "selftest: ALL CHECKS PASSED"
    return 0
}

case "${1:-}" in
    --selftest) selftest ;;
    --no-launch) install_app ;;
    *) install_app; "$BASE/launch"; say "How a Computer Is Made is open in your browser." ;;
esac
