;;; proof-netspace-server-lib.lsp — shared server body.
;;;
;;; Expected pre-bound top-level variables (set by the wrapper script
;;; that loads this file):
;;;
;;;   *port*            — TCP port to listen on
;;;   *max-requests*    — request cap before clean shutdown
;;;   *snapshot-path*   — filesystem path for the bulk-load snapshot
;;;   *proofs-dir*      — directory for per-proof cache artifacts
;;;
;;; Wrappers: proof-netspace-server.lsp (defaults), proof-netspace-node-a.lsp,
;;; proof-netspace-node-b.lsp.

;;; ─── Tiny content hash (djb2, 31-bit) ──────────────────────

(define (hash-string s)
  (define len (string-length s))
  (define (iter i h)
    (if (= i len)
        h
        (iter (+ i 1)
              (modulo (+ (* h 33) (char->integer (string-ref s i)))
                      2147483647))))
  (iter 0 5381))

;;; ─── Serialize a theorem to a canonical string ─────────────

(define (atom->string v)
  (cond
    ((number? v) (number->string v))
    ((symbol? v) (symbol->string v))
    ((null? v)   "()")
    ((pair? v)   (string-append "(" (list->str v) ")"))
    ((string? v) (string-append "\"" v "\""))
    (else        "?")))

(define (list->str lst)
  (cond
    ((null? lst) "")
    ((null? (cdr lst)) (atom->string (car lst)))
    (else (string-append (atom->string (car lst)) " "
                         (list->str (cdr lst))))))

(define (theorem->string lhs rhs)
  (string-append (atom->string lhs) "=" (atom->string rhs)))

;;; ─── Term-rewriting engine (same axioms as EML proof) ──────

(define (pattern-var? x)
  (if (symbol? x)
      (let ((s (symbol->string x)))
        (if (> (string-length s) 0)
            (= (char->integer (string-ref s 0)) 63)   ; '?' = 63
            #f))
      #f))

(define (sub-lookup var subs)
  (if (null? subs) #f
      (if (eqv? (car (car subs)) var) (car subs)
          (sub-lookup var (cdr subs)))))

(define (term-equal? a b)
  (cond
    ((and (pair? a) (pair? b))
     (if (term-equal? (car a) (car b)) (term-equal? (cdr a) (cdr b)) #f))
    ((and (null? a) (null? b)) #t)
    (else (equal? a b))))

(define (match-pat pat term subs)
  (cond
    ((eqv? subs 'no-match) 'no-match)
    ((pattern-var? pat)
     (let ((existing (sub-lookup pat subs)))
       (if existing
           (if (term-equal? (cdr existing) term) subs 'no-match)
           (cons (cons pat term) subs))))
    ((and (pair? pat) (pair? term))
     (match-pat (cdr pat) (cdr term)
                (match-pat (car pat) (car term) subs)))
    ((and (null? pat) (null? term)) subs)
    ((term-equal? pat term) subs)
    (else 'no-match)))

(define (subst template subs)
  (cond
    ((pattern-var? template)
     (let ((found (sub-lookup template subs)))
       (if found (cdr found) template)))
    ((pair? template)
     (cons (subst (car template) subs) (subst (cdr template) subs)))
    (else template)))

(define (try-rule rule term)
  (let ((pat (car rule)) (rhs (car (cdr (cdr rule)))))
    (let ((subs (match-pat pat term '())))
      (if (eqv? subs 'no-match) #f (subst rhs subs)))))

(define (step-root rules term)
  (cond
    ((null? rules) #f)
    ((try-rule (car rules) term) (try-rule (car rules) term))
    (else (step-root (cdr rules) term))))

(define (step-any rules term)
  (if (pair? term)
      (let ((car-step (step-any rules (car term))))
        (if car-step
            (cons car-step (cdr term))
            (let ((cdr-step (step-any rules (cdr term))))
              (if cdr-step (cons (car term) cdr-step)
                  (step-root rules term)))))
      (step-root rules term)))

(define (normalize rules term)
  (define (iter t n)
    (if (> n 500) t
        (let ((next (step-any rules t)))
          (if next (iter next (+ n 1)) t))))
  (iter term 0))

(define eml-rules
  (list
    '((eml ?x ?y) -> (- (exp ?x) (ln ?y)))
    '((exp (ln ?x)) -> ?x)
    '((ln (exp ?x)) -> ?x)
    '((ln 1) -> 0)
    '((- ?x 0) -> ?x)
    '((- 0 ?x) -> (neg ?x))
    '((neg (neg ?x)) -> ?x)
    '((- ?x ?x) -> 0)
    '((- ?a (- ?a ?b)) -> ?b)))

(define (verify lhs rhs)
  (term-equal? (normalize eml-rules lhs) (normalize eml-rules rhs)))

;;; ─── Hash-table library (pure Lumbda, portable) ───────────
;;;
;;; Asm has no native hash-table primitive, but exposes make-vector,
;;; vector-ref, and vector-set! — the substrate a hash-table needs.
;;; This library builds a hash-SET keyed by integer on top of vectors:
;;;
;;;   slot 0       = bucket count N
;;;   slots 1..N   = bucket list (chained alist of raw integer keys)
;;;
;;; Lookup and insert are O(1) expected. No resize: pick a bucket
;;; count large enough to keep load factor below ~10 at target scale.
;;; 1021 buckets keeps lookups fast through 10k entries.

(define *ht-buckets* 1021)

(define (ht-make n)
  (let ((v (make-vector (+ n 1) '())))
    (vector-set! v 0 n)
    v))

(define (ht-bucket t k)
  (+ 1 (modulo (if (< k 0) (- 0 k) k) (vector-ref t 0))))

(define (ht-in-bucket? bucket k)
  (cond
    ((null? bucket) #f)
    ((= (car bucket) k) #t)
    (else (ht-in-bucket? (cdr bucket) k))))

(define (ht-has? t k)
  (ht-in-bucket? (vector-ref t (ht-bucket t k)) k))

(define (ht-add! t k)
  (let ((idx (ht-bucket t k)))
    (let ((bucket (vector-ref t idx)))
      (if (ht-in-bucket? bucket k) #f
          (begin (vector-set! t idx (cons k bucket)) #t)))))

(define (ht-size t)
  (define n (vector-ref t 0))
  (define (blen lst)
    (if (null? lst) 0 (+ 1 (blen (cdr lst)))))
  (define (walk i acc)
    (if (> i n) acc
        (walk (+ i 1) (+ acc (blen (vector-ref t i))))))
  (walk 1 0))

(define (ht-fold-keys t acc fn)
  (define n (vector-ref t 0))
  (define (bfold lst acc)
    (if (null? lst) acc
        (bfold (cdr lst) (fn (car lst) acc))))
  (define (walk i acc)
    (if (> i n) acc
        (walk (+ i 1) (bfold (vector-ref t i) acc))))
  (walk 1 acc))

;;; ─── Proof DB (hash-set backed by ht-*) ────────────────────

(define *proof-db* (ht-make *ht-buckets*))

(define (db-has? h) (ht-has? *proof-db* h))
(define (db-add! h) (ht-add! *proof-db* h))
(define (db-size)  (ht-size *proof-db*))

(define (rebuild-db-from-list hashes)
  (let ((t (ht-make *ht-buckets*)))
    (define (walk lst)
      (if (null? lst) t
          (begin (ht-add! t (car lst)) (walk (cdr lst)))))
    (walk hashes)))

;;; ─── Per-proof file ────────────────────────────────────────

(define (proof-file-path h)
  (string-append *proofs-dir* (number->string h) ".proof"))

(define (file-has? h)
  (if (file->string (proof-file-path h)) #t #f))

(define (file-add! h canon)
  (write-file (proof-file-path h)
    (string-append ";; proof artifact for hash " (number->string h) "\n"
                   ";; " canon "\n" "PROVEN\n")))

;;; ─── Portal snapshot ───────────────────────────────────────
;;;
;;; Snapshot emits a `set!` form (not `define`). When the loader calls
;;; (load ...), the top-level *proof-db* binding gets mutated in place
;;; regardless of whether `load` executes in a nested scope.

(define (portal-snapshot!)
  (let ((hashes-str
         (ht-fold-keys *proof-db* ""
           (lambda (h acc)
             (string-append acc (number->string h) " ")))))
    (write-file *snapshot-path*
      (string-append ";; lumbda proof netspace snapshot\n"
                     "(set! *proof-db* (rebuild-db-from-list '("
                     hashes-str ")))\n"))))

(define (portal-restore!)
  (if (file->string *snapshot-path*)
      (load *snapshot-path*)
      #f))

;;; ─── Envelope teleport ─────────────────────────────────────
;;;
;;; Envelope = the full solution space as a list of hashes. Two nodes
;;; can exchange envelopes and each merges what it didn't know. After
;;; one round-trip, both hold the union. After N rounds across M nodes,
;;; the entire mesh converges on the same solution space.

(define (envelope-reply)
  (let ((hashes-str
         (ht-fold-keys *proof-db* ""
           (lambda (h acc)
             (string-append acc (number->string h) " ")))))
    (string-append "(envelope (" hashes-str "))\n")))

(define (merge-hashes lst added)
  (if (null? lst) added
      (if (ht-add! *proof-db* (car lst))
          (merge-hashes (cdr lst) (+ added 1))
          (merge-hashes (cdr lst) added))))

(define (merge-reply lst)
  (let ((added (merge-hashes lst 0)))
    (string-append "(merged " (number->string added) ")\n")))

;;; ─── Request handling ──────────────────────────────────────

(define (verify-reply lhs rhs)
  (let ((h (hash-string (theorem->string lhs rhs))))
    (cond
      ((db-has? h) "PROVEN\n")
      ((file-has? h)
       (db-add! h)
       "PROVEN\n")
      ((verify lhs rhs)
       (db-add! h)
       (file-add! h (theorem->string lhs rhs))
       "PROVEN\n")
      (else "UNKNOWN\n"))))

(define (handle-request raw)
  (let ((form (read-from-string raw)))
    (cond
      ((and (pair? form) (eqv? (car form) 'envelope) (null? (cdr form)))
       (envelope-reply))
      ((and (pair? form) (eqv? (car form) 'merge) (pair? (cdr form)))
       (merge-reply (car (cdr form))))
      ((and (pair? form) (pair? (cdr form)))
       (verify-reply (car form) (car (cdr form))))
      (else "ERROR\n"))))

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

(portal-restore!)

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

(define (serve-loop n)
  (if (>= n *max-requests*)
      (begin (portal-snapshot!)
             (display "request cap reached, snapshot saved\n")
             (tcp-close server))
      (begin
        (let ((client (tcp-accept server)))
          (let ((req (tcp-recv client 8192)))
            (if (and req (> (string-length req) 0))
                (tcp-send client (handle-request req))
                #f))
          (tcp-close client))
        (if (= (modulo n 20) 0) (portal-snapshot!) #f)
        (serve-loop (+ n 1)))))

(display "proof-netspace-server on :") (display *port*)
(display " (") (display (db-size)) (display " proofs preloaded)") (newline)
(serve-loop 0)
