;;; lumbda-call.lsp — calling shake256-fanout from any lumbda tier.
;;;
;;; This is the API SHAPE lumbda's core would expose as a primitive.
;;; Each tier already has spawn-process + portal I/O primitives; the
;;; cuda fan-out primitive is a thin Scheme wrapper that composes them.
;;;
;;; Once locked, each tier (Python / C / asm) registers the same name
;;; in its primitive dispatch table.  Asm tier uses fork + execve +
;;; portal-write syscalls — no libcudart linkage needed.  Python &
;;; C tiers can use the same shape via subprocess / fork respectively,
;;; or graduate to a true FFI later.
;;;
;;; Usage:
;;;
;;;   (cuda-shake-fanout
;;;     '("00" "01" "deadbeef")     ; list of hex-string inputs
;;;     32)                          ; output bytes per input
;;;   ⇒  '("0xhash0" "0xhash1" "0xhash2")

(define *cuda-shake-fanout-binary*
  ;; Override per host; reference points at the example dir.
  "/home/fox/git/lumbda/examples/cuda-fanout/shake256-fanout")

(define (with-temp-files names body)
  "Allocate gensym'd temp file paths, invoke body on them, cleanup
   regardless of exit path. Each tier should implement gensym +
   delete-file in its stdlib."
  (let* ((paths (map (lambda (n) (string-append "/tmp/cf-" n "-"
                                                (number->string (current-time-ms))))
                     names))
         (result (apply body paths)))
    (for-each delete-file paths)
    result))

(define (write-input-portal! path inputs output-bytes)
  (let ((port (open-output-file path)))
    (display "(cuda-shake-fanout\n" port)
    (display "  (output-bytes " port) (display output-bytes port) (display ")\n" port)
    (display "  (inputs\n" port)
    (for-each (lambda (hex)
                (display "    \"" port) (display hex port) (display "\"\n" port))
              inputs)
    (display "))\n" port)
    (close-port port)))

(define (read-hashes-portal path)
  ;; Use sim.lsp-style portal reader if available; fall back to text scan.
  (let* ((src (file->string path))
         ;; Crude hex-string extractor — production code should use a real
         ;; parser. The portal format is small & predictable so this works
         ;; for the reference example.
         (after-hashes (substring src
                                  (+ (string-search "(hashes" src) 7)
                                  (string-length src))))
    (let loop ((rest after-hashes) (acc '()))
      (let ((open (string-search "\"" rest)))
        (if (not open) (reverse acc)
            (let* ((tail (substring rest (+ open 1) (string-length rest)))
                   (close (string-search "\"" tail)))
              (if (not close) (reverse acc)
                  (loop (substring tail (+ close 1) (string-length tail))
                        (cons (substring tail 0 close) acc)))))))))

(define (cuda-shake-fanout inputs output-bytes)
  "Hash each input on its own CUDA thread via the reference fan-out
   binary. Inputs are hex strings; output is a list of hex hash strings."
  (with-temp-files '("in.portal" "out.portal")
    (lambda (in-path out-path)
      (write-input-portal! in-path inputs output-bytes)
      ;; spawn-process is each tier's process-spawn primitive name —
      ;; substitute when lumbda's core dispatches under a stable name.
      (spawn-process *cuda-shake-fanout-binary* (list in-path out-path))
      (read-hashes-portal out-path))))

;;; ── demo ──────────────────────────────────────────────────────

(define (demo)
  (let ((hashes (cuda-shake-fanout '("00" "01" "deadbeef") 32)))
    (display "got ") (display (length hashes)) (display " hashes:\n")
    (for-each (lambda (h)
                (display "  ") (display h) (newline))
              hashes)))

;; (demo)   ;; uncomment to run; needs spawn-process + file primitives
