#!/usr/bin/env python3
"""
Offline server for the COG surveillance-art app.

Why not `python3 -m http.server`?
  1. It sends the wrong Content-Type for .mjs / .wasm, which breaks the
     ES-module import (MediaPipe) and WebAssembly streaming (ONNX Runtime).
  2. It sends no COOP/COEP headers, so the browser can't use SharedArrayBuffer
     and the WASM models run single-threaded (slow).

This server fixes both, so face/body/hand/badge detection all run fast, offline,
on Windows and macOS alike.

Usage:
    python serve.py            # serves this folder at http://localhost:8000
    python serve.py 9000       # ...on a different port

Then open the printed URL in Chrome or Edge and allow the camera.
"""
import http.server
import os
import socketserver
import sys
import webbrowser

START_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8000

# serve alongside this script no matter where it's launched from
os.chdir(os.path.dirname(os.path.abspath(__file__)))

# Force correct MIME types ourselves. On Windows, Python reads MIME types from
# the registry, which frequently mislabels .js / .mjs / .wasm — and the exact
# lookup order changed across Python versions. This map is the single source of
# truth so the app loads identically on every machine.
FORCE_TYPES = {
    ".html": "text/html",
    ".js": "text/javascript",
    ".mjs": "text/javascript",
    ".wasm": "application/wasm",
    ".css": "text/css",
    ".json": "application/json",
    ".woff2": "font/woff2",
    ".woff": "font/woff",
    ".png": "image/png",
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".svg": "image/svg+xml",
    ".task": "application/octet-stream",
    ".onnx": "application/octet-stream",
}


class Handler(http.server.SimpleHTTPRequestHandler):
    def guess_type(self, path):
        ext = os.path.splitext(path)[1].lower()
        if ext in FORCE_TYPES:
            return FORCE_TYPES[ext]
        return super().guess_type(path)

    def end_headers(self):
        # cross-origin isolation -> enables multi-threaded WASM (fast inference)
        self.send_header("Cross-Origin-Opener-Policy", "same-origin")
        self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
        # never cache, so refreshing the flash drive's files always takes effect
        self.send_header("Cache-Control", "no-store")
        super().end_headers()

    def handle_one_request(self):
        # browsers routinely cancel range requests on big .wasm files; swallow the
        # resulting connection errors so the console stays clean (common on Windows)
        try:
            super().handle_one_request()
        except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
            self.close_connection = True

    def log_message(self, fmt, *args):
        pass  # quiet console; comment out to see each request


socketserver.TCPServer.allow_reuse_address = True


def main():
    last_err = None
    for port in range(START_PORT, START_PORT + 15):
        try:
            httpd = socketserver.TCPServer(("127.0.0.1", port), Handler)
        except OSError as e:  # port in use — try the next one
            last_err = e
            continue
        url = f"http://localhost:{port}"
        print("=" * 44)
        print(f"  COG is running at:  {url}")
        print("  (leave this window open; close it to stop)")
        print("=" * 44)
        try:
            webbrowser.open(url)
        except Exception:
            pass
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            print("\nstopped.")
        return
    print(f"Could not open a port in range {START_PORT}-{START_PORT + 14}: {last_err}")
    sys.exit(1)


if __name__ == "__main__":
    main()
