; lib/artdag/serialize.sx — portable wire form for whole DAGs, so a peer can
; receive and run a graph it did not author. The form is a topo-ordered list of
; node records (id op inputs params commutative) — plain lists with keyword-keyed
; param dicts, which survive write/read (unlike string-keyed node dicts). The id
; is the content-id, so the form is self-verifying. Depends on dag.sx.

(define
  artdag/node->record
  (fn
    (dag id)
    (let
      ((n (artdag/dag-get dag id)))
      (list
        id
        (artdag/node-op n)
        (artdag/node-inputs n)
        (artdag/node-params n)
        (get n :commutative)))))

; dag -> list of records, in topological order.
(define
  artdag/dag->wire
  (fn
    (dag)
    (map (fn (id) (artdag/node->record dag id)) (artdag/dag-order dag))))

; an empty input list reads back as nil; normalize it.
(define
  artdag/-rec-inputs
  (fn (rec) (let ((i (nth rec 2))) (if (nil? i) (list) i))))

(define artdag/-rec->node (fn (rec) {:inputs (artdag/-rec-inputs rec) :commutative (nth rec 4) :op (nth rec 1) :params (nth rec 3)}))

; records -> dag. Local author names are not part of the wire form; the receiver
; works by content-id. :names is left empty.
(define
  artdag/wire->dag
  (fn
    (records)
    (reduce
      (fn (dag rec) (let ((id (nth rec 0))) {:names (get dag :names) :order (concat (get dag :order) (list id)) :ok true :nodes (assoc (get dag :nodes) id (artdag/-rec->node rec))}))
      {:names {} :order (list) :ok true :nodes {}}
      records)))

; integrity: each record's id must equal the content-id recomputed from its spec.
(define
  artdag/wire-verify
  (fn
    (records)
    (every?
      (fn
        (rec)
        (= (nth rec 0) (artdag/content-id (artdag/-rec->node rec))))
      records)))

; string transport.
(define
  artdag/dag->string
  (fn (dag) (write-to-string (artdag/dag->wire dag))))
(define
  artdag/string->dag
  (fn (s) (artdag/wire->dag (read (open-input-string s)))))
