#lang racket/base

(require (for-syntax racket/base)
         racket/class
         racket/draw
         racket/match
         pict
         syntax/parse/define)

(define (s-frame color hatched? t)
  (define p (inset (text t) 10))
  (define-values [w h] (values (pict-width p) (pict-height p)))
  (define fr
    (dc (λ (dc dx dy)
          (define old-brush (send dc get-brush))
          (define old-pen (send dc get-pen))
          (define color-bg (make-object color%
                             (send color red)
                             (send color green)
                             (send color blue)
                             0.5))
          (send dc set-brush
                (new brush% [style (if hatched? 'fdiagonal-hatch 'transparent)]
                     [color color-bg]))
          (send dc set-pen
                (new pen% [width 1] [color color]))
          (define path (new dc-path%))
          (send path move-to 0 0)
          (send path line-to (- w 0.5) 0)
          (send path line-to (- w 0.5) (- h 0.5))
          (send path line-to 0 (- h 0.5))
          (send path close)
          (send dc draw-path path dx dy)
          (send dc set-brush old-brush)
          (send dc set-pen old-pen))
        w h))
  (cc-superimpose fr p))

(define (s-arrow label-text . blocks)
  (match blocks
    ['() (blank 0 0)]
    [(list pict-a) pict-a]
    [(cons pict-a pict-bs)
     (define pict-b (apply s-arrow label-text pict-bs))
     (pin-arrow-line
       10 (hc-append 100 pict-a pict-b)
       pict-a rc-find
       pict-b lc-find
       #:line-width 1
       #:style 'solid
       #:color "black"
       #:label (label-text))]))

(define (add-bottom-label p label-text [l-offset 0])
  (define-values [w h] (values (- (pict-width p) l-offset) (pict-height p)))
  (define bracket (hb-append (vline 0 5) (hline w 0) (vline 0 5)))
  (define label (text label-text))
  (vr-append p
             (vc-append (inset bracket 0 10 0 0)
                        (cc-superimpose (blank w (pict-height label)) label))))


(define (backgroundify-diagram p-raw)
  (define p (inset p-raw 20))
  (cc-superimpose (filled-rectangle (pict-width p) (pict-height p)
                                    #:draw-border? #f #:color "white")
                  p))

(define (save-to-svg p name)
  (define svg (new svg-dc%
                   [width (pict-width p)]
                   [height (pict-height p)]
                   [output (format "~a.svg" name)]
                   [exists 'replace]))
  (send svg start-doc "rendering SVG")
  (send svg start-page)
  (draw-pict p svg 0 0)
  (send svg end-page)
  (send svg end-doc))

(define-simple-macro (define-diagram name:id value:expr)
  #:with name-string (datum->syntax #'name (symbol->string (syntax->datum #'name)))
  (save-to-svg (backgroundify-diagram value) name-string))


(define c-reg (make-object color% 150 150 150))
(define c-pad (make-object color% 200 80 80))
(define c-len (make-object color% 200 80 80))

(define c-blue (make-object color% 150 150 200))
(define c-green (make-object color% 30 200 30))

(define-diagram sha-diagram
  (add-bottom-label
   (ht-append (s-frame c-reg #f "data")
              (s-frame c-pad #t "padding")
              (s-frame c-len #t "data len"))
   "hashed data"))

(define-diagram sha-diagram-extended
  (add-bottom-label
   (ht-append
    (add-bottom-label
     (ht-append
      (add-bottom-label
       (ht-append (s-frame c-reg #f "data")
                  (s-frame c-pad #f "padding")
                  (s-frame c-len #f "data len"))
       "originally hashed")
      (s-frame c-reg #f "extend data"))
     "append this"
     (pict-width (s-frame c-reg #f "data")))
    (s-frame c-pad #t "padding")
    (s-frame c-len #t "total data len"))
   "extended hash"))


(define-diagram sha-compression-diagram
  (vc-append
    (blank 1 60)
    (s-arrow (λ ()
                (define pict-a (s-frame c-reg #f "input block"))
                (define pict-b (text "SHA-2 round"))
                (pin-arrow-line
                  5 (vc-append 30 pict-a pict-b)
                  pict-a cb-find
                  pict-b ct-find
                  #:line-width 1
                  #:style 'solid
                  #:color "black"))
      (s-frame c-blue #t "SHA-2 state constants")
      (s-frame c-reg #t "state 1")
      (s-frame c-reg #t "state 2")
      (s-frame c-green #f "output hash"))))
