;;; http-static-server.lsp — serve lumbda.com's docroot from pure Scheme.
;;;
;;; Runs in any tier that has file->string + the tcp-* family: Python,
;;; C, and both asm builds. The asm-gc build is the target deployment
;;; (~27 KB binary, bounded memory via heap-snapshot per request).
;;;
;;;   ./asm/lumbda-gc < examples/http-static-server.lsp
;;;
;;; Serves GET requests under *docroot*. Default docroot is "www" —
;;; run from the repo root so the relative path resolves.

(define *port* 8080)
(define *docroot* "www")
(define *max-requests* 100000)

(define *crlf* "\r\n")
(define *crlf-crlf* "\r\n\r\n")

;;; ── HTTP helpers ────────────────────────────────────────────

(define (http-response status ctype body)
  (string-append
    "HTTP/1.0 " status *crlf*
    "Content-Type: " ctype *crlf*
    "Content-Length: " (number->string (string-length body)) *crlf*
    "Connection: close" *crlf-crlf*
    body))

(define SPACE 32)
(define DOT   46)
(define (char-at s i) (char->integer (string-ref s i)))

;;; Pull the URL path out of the request line "GET /path HTTP/1.0\r\n..."
(define (second-token s)
  (let ((len (string-length s)))
    (let loop1 ((i 0))
      (cond
        ((= i len) "/")
        ((= (char-at s i) SPACE)
         (let loop2 ((j (+ i 1)))
           (cond
             ((= j len) (substring s (+ i 1) len))
             ((= (char-at s j) SPACE) (substring s (+ i 1) j))
             (else (loop2 (+ j 1))))))
        (else (loop1 (+ i 1)))))))

;;; ── Path safety ─────────────────────────────────────────────

;;; Reject any path containing ".." — keeps requests under docroot.
(define (has-dotdot? s)
  (let ((n (string-length s)))
    (let loop ((i 0))
      (cond
        ((>= (+ i 1) n) #f)
        ((and (= (char-at s i) DOT) (= (char-at s (+ i 1)) DOT)) #t)
        (else (loop (+ i 1)))))))

;;; ── MIME dispatch ───────────────────────────────────────────

(define (ends-with? s suffix)
  (let ((ns (string-length s)) (nf (string-length suffix)))
    (if (< ns nf) #f
        (string=? suffix (substring s (- ns nf) ns)))))

(define (mime-of path)
  (cond
    ((ends-with? path ".html") "text/html; charset=utf-8")
    ((ends-with? path ".css")  "text/css; charset=utf-8")
    ((ends-with? path ".pdf")  "application/pdf")
    ((ends-with? path ".txt")  "text/plain; charset=utf-8")
    ((ends-with? path ".js")   "application/javascript")
    ((ends-with? path ".png")  "image/png")
    ((ends-with? path ".jpg")  "image/jpeg")
    ((ends-with? path ".svg")  "image/svg+xml")
    ((ends-with? path ".ico")  "image/x-icon")
    (else "application/octet-stream")))

;;; ── File resolution ─────────────────────────────────────────

(define (resolve-fs-path url-path)
  (cond
    ((string=? url-path "/") (string-append *docroot* "/index.html"))
    (else (string-append *docroot* url-path))))

(define (serve-file url-path)
  (cond
    ((has-dotdot? url-path)
     (http-response "403 Forbidden" "text/plain" "forbidden\n"))
    (else
      (let ((fs-path (resolve-fs-path url-path)))
        (let ((body (file->string fs-path)))
          (if body
              (http-response "200 OK" (mime-of fs-path) body)
              (let ((nf (file->string (string-append *docroot* "/404.html"))))
                (http-response "404 Not Found" "text/html; charset=utf-8"
                  (if nf nf "not found\n")))))))))

(define (handle-request req)
  (let ((path (second-token req)))
    (serve-file path)))

;;; ── Main loop ───────────────────────────────────────────────

(define server (tcp-listen *port*))

(define (server-loop n snap)
  (if (>= n *max-requests*)
      (begin
        (display "request cap reached, exiting\n")
        (tcp-close server))
      (begin
        (let ((client (tcp-accept server)))
          (let ((req (tcp-recv client 4096)))
            (if (and req (> (string-length req) 0))
                (tcp-send client (handle-request req))
                #f))
          (tcp-close client))
        ;; Rewind per-request allocations on asm; no-op on Python/C.
        (heap-restore snap)
        (server-loop (+ n 1) snap))))

(display "lumbda-www on :") (display *port*)
(display " serving ") (display *docroot*) (newline)
(server-loop 0 (heap-snapshot))
