#lang scheme/base
(require "../decode.rkt"
         "../scheme.rkt"
         "../struct.rkt"
         (only-in "../core.rkt" 
                  make-style style-name style-properties
                  nested-flow? nested-flow-blocks nested-flow-style
                  make-nested-flow)
         "../html-properties.rkt"
         racket/contract/base
         (for-syntax scheme/base
                     syntax/kerncase
                     syntax/boundmap)
         (for-label scheme/base
                    scheme/class))

(define-struct (box-splice splice) ())

(provide/contract
 [struct (box-splice splice) ([run list?])]) ; XXX ugly copying
(provide deftogether *deftogether
         with-racket-variables
         with-togetherable-racket-variables
         vertical-inset-style
         boxed-style
         add-background-label)

(define vertical-inset-style 
  (make-style 'vertical-inset null))

(define boxed-style 
  (make-style 'boxed (list (make-attributes (list (cons 'class "RBoxed"))))))

(define ((add-background-label what) l)
  (list
   (make-nested-flow
    (make-style "RBackgroundLabel" (list 'decorative 'command (alt-tag "div")
                                         (make-attributes '((class . "SIEHidden")))))
    (list
     (make-nested-flow
      (make-style "RBackgroundLabelInner" (list (alt-tag "div")))
      (list (make-omitable-paragraph what)))))
   (let* ([a (car l)]
          [remake (if (paragraph? a)
                      (lambda (sa)
                        (paragraph
                         (sa (paragraph-style a))
                         (paragraph-content a)))
                      (lambda (sa)
                        (table
                         (sa (table-style a))
                         (table-blockss a))))])
     (remake
      (lambda (s)
        (make-style (style-name s)
                    (let ([p (style-properties s)])
                      (if (ormap attributes? p)
                          (for/list ([i (in-list p)])
                            (if (attributes? i)
                                (let ([al (attributes-assoc i)])
                                  (if (assq 'class al)
                                      (for/list ([a (in-list al)])
                                        (if (eq? (car a) 'class)
                                            (cons 'class (string-append (cdr a) " RForeground"))
                                            a))
                                      (attributes (cons '(class . "RForeground")
                                                        al))))
                                i))
                          (cons (attributes '((class . "RForeground")))
                                p)))))))))

(begin-for-syntax (define-struct deftogether-tag () #:omit-define-syntaxes))

(define-syntax (with-togetherable-racket-variables stx)
  (syntax-case stx ()
    [(_ lits vars decl)
     (with-syntax ([vars (syntax-property #'vars 'taint-mode 'none)])
       (syntax-property
        #'(with-togetherable-racket-variables* lits vars decl)
        'taint-mode
        'transparent))]))

(define-syntax-rule (with-togetherable-racket-variables* . rest)
  (with-racket-variables . rest))

(define-syntax (with-racket-variables stx)
  (syntax-case stx ()
    [(_ lits ([kind s-exp] ...) body)
     (let ([ht (make-bound-identifier-mapping)]
           [lits (syntax->datum #'lits)])
       (for-each (lambda (kind s-exp)
                   (case (syntax-e kind)
                     [(proc)
                      (letrec ([do-proc
                                (lambda (s-exp)
                                  (let ([s-exp (syntax->list s-exp)])
                                    (for-each
                                     (lambda (arg)
                                       (if (identifier? arg)
                                           (unless (or (eq? (syntax-e arg) '...)
                                                       (eq? (syntax-e arg) '...+)
                                                       (eq? (syntax-e arg) '_...superclass-args...)
                                                       (memq (syntax-e arg) lits))
                                             (bound-identifier-mapping-put! ht arg #t))
                                           (syntax-case arg ()
                                             [(kw arg . rest)
                                              (and (keyword? (syntax-e #'kw))
                                                   (identifier? #'arg))
                                              (bound-identifier-mapping-put! ht #'arg #t)]
                                             [(arg . rest)
                                              (identifier? #'arg)
                                              (bound-identifier-mapping-put! ht #'arg #t)]
                                             [else (void)])))
                                     (cdr s-exp))
                                    (unless (identifier? (car s-exp))
                                      ;; Curried:
                                      (do-proc (car s-exp)))))])
                        (do-proc s-exp))]
                     [(form form/none form/maybe non-term)
                      (define skip-id (case (syntax-e kind)
                                         [(form) 
                                          (syntax-case s-exp ()
                                            [(defined-id actual-s-exp) (let ([id #'defined-id])
                                                                         (and (identifier? id)
                                                                              id))]
                                            [_ #f])]
                                         [else #f]))
                      (let loop ([form (case (syntax-e kind)
                                         [(form) 
                                          (syntax-case s-exp ()
                                            [(defined-id actual-s-exp) #'actual-s-exp])]
                                         [(form/none) s-exp]
                                         [(form/maybe)
                                          (syntax-case s-exp ()
                                            [(#f form) #'form]
                                            [(#t (id . form)) #'form])]
                                         [(non-term) s-exp])])
                        (if (identifier? form)
                            (unless (or (and skip-id
                                             (free-identifier=? skip-id form))
                                        (eq? (syntax-e form) '...)
                                        (eq? (syntax-e form) '...+)
                                        (eq? (syntax-e form) 'code:line)
                                        (eq? (syntax-e form) 'code:blank)
                                        (eq? (syntax-e form) 'code:comment)
                                        (eq? (syntax-e form) '?)
                                        (memq (syntax-e form) lits))
                              (bound-identifier-mapping-put! ht form #t))
                            (syntax-case form (unsyntax)
                              [(unsyntax _) (void)]
                              [(a . b) (loop #'a) (loop #'b)]
                              [#(a ...) (loop #'(a ...))]
                              [_ (void)])))]
                     [else
                      (raise-syntax-error
                       #f
                       "unknown variable mode"
                       stx
                       kind)]))
                 (syntax->list #'(kind ...))
                 (syntax->list #'(s-exp ...)))
       (with-syntax ([(id ...) (bound-identifier-mapping-map ht (lambda (k v) k))])
         #'(letrec-syntaxes ([(id) (make-variable-id 'id)] ...)
             body)))]))


(define (*deftogether boxes body-thunk)
  (make-box-splice
   (cons
    (make-blockquote 
     vertical-inset-style
     (list
      (make-table
       boxed-style
       (map
        (lambda (box)
          (unless (and (box-splice? box)
                       (= 1 (length (splice-run box)))
                       (nested-flow? (car (splice-run box)))
                       (eq? vertical-inset-style (nested-flow-style (car (splice-run box))))
                       (let ([l (nested-flow-blocks (car (splice-run box)))])
                         (= 1 (length l))
                         (table? (car l))
                         (eq? boxed-style (table-style (car l)))))
            (error 'deftogether
                   "element is not a boxing splice containing a single nested-flow with a single table: ~e"
                   box))
          (list (make-flow (list (make-table
                                  "together"
                                  (table-flowss (car (nested-flow-blocks (car (splice-run box))))))))))
        boxes))))
    (body-thunk))))

(define-syntax (deftogether stx)
  (syntax-case stx ()
    [(_ (def ...) . body)
     (with-syntax ([((_ (lit ...) (var ...) decl) ...)
                    (map (lambda (def)
                           (let ([exp-def (local-expand 
                                           def
                                           (list (make-deftogether-tag))
                                           (cons
                                            #'with-togetherable-racket-variables*
                                            (kernel-form-identifier-list)))])
                             (syntax-case exp-def (with-togetherable-racket-variables*)
                               [(with-togetherable-racket-variables* lits vars decl)
                                exp-def]
                               [_
                                (raise-syntax-error
                                 #f
                                 "sub-form is not a documentation form that can be combined"
                                 stx
                                 def)])))
                         (syntax->list #'(def ...)))])
       #'(with-togetherable-racket-variables
          (lit ... ...)
          (var ... ...)
          (*deftogether (list decl ...) (lambda () (list . body)))))]))
