
(module unit scheme/base
  (require mzlib/unit
           scheme/contract
           (for-syntax scheme/base
                       syntax/struct))
  (provide (except-out (all-from-out mzlib/unit)
                       struct struct/ctc)
           (rename-out [struct* struct]
                       [struct/ctc* struct/ctc]))

  ;; Replacement `struct' signature form:
  (define-signature-form (struct* stx)
    (syntax-case stx ()
      ((_ name (field ...) opt ...)
       (begin
         (unless (identifier? #'name)
           (raise-syntax-error #f
                               "expected an identifier to name the structure type"
                               stx
                               #'name))
         (for-each (lambda (field)
                     (unless (identifier? field)
                       (syntax-case field ()
                         [(id #:mutable)
                          (identifier? #'id)
                          'ok]
                         [_
                          (raise-syntax-error #f
                                              "bad field specification"
                                              stx
                                              field)])))
                   (syntax->list #'(field ...)))
         (let-values ([(no-ctr? mutable? no-stx? no-rt?)
                       (let loop ([opts (syntax->list #'(opt ...))]
                                  [no-ctr? #f]
                                  [mutable? #f]
                                  [no-stx? #f]
                                  [no-rt? #f])
                         (if (null? opts)
                             (values no-ctr? mutable? no-stx? no-rt?)
                             (let ([opt (car opts)])
                               (case (syntax-e opt)
                                 [(#:omit-constructor)
                                  (if no-ctr?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) #t mutable? no-stx? no-rt?))]
                                 [(#:mutable)
                                  (if mutable?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) no-ctr? #t no-stx? no-rt?))]
                                 [(#:omit-define-syntaxes)
                                  (if no-stx?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) no-ctr? mutable? #t no-rt?))]
                                 [(#:omit-define-values)
                                  (if no-rt?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) no-ctr? mutable? no-stx? #t))]
                                 [else
                                  (raise-syntax-error #f
                                                      (string-append
                                                       "expected a keyword to specify option: "
                                                       "#:mutable, #:omit-constructor, #:omit-define-syntaxes, or #:omit-define-values")
                                                      stx
                                                      opt)]))))])
           (cons
            #`(define-syntaxes (name)
                #,(build-struct-expand-info
                   #'name (syntax->list #'(field ...))
                   #f (not mutable?)
                   #f '(#f) '(#f)
                   #:omit-constructor? no-ctr?))
            (let ([names (build-struct-names #'name (syntax->list #'(field ...))
                                             #f (not mutable?))])
              (if no-ctr?
                  (cons (car names) (cddr names))
                  names))))))
      ((_ name fields opt ...)
       (raise-syntax-error #f
                           "bad syntax; expected a parenthesized sequence of fields"
                           stx
                           #'fields))
      ((_ name)
       (raise-syntax-error #f
                           "bad syntax; missing fields"
                           stx))
      ((_)
       (raise-syntax-error #f
                           "missing name and fields"
                           stx))))
  
  ;; Replacement struct/ctc form
  (define-signature-form (struct/ctc* stx)
    (syntax-case stx ()
      ((_ name ([field ctc] ...) opt ...)
       (begin
         (unless (identifier? #'name)
           (raise-syntax-error #f
                               "expected an identifier to name the structure type"
                               stx
                               #'name))
         (for-each (lambda (field)
                     (unless (identifier? field)
                       (syntax-case field ()
                         [(id #:mutable)
                          (identifier? #'id)
                          'ok]
                         [_
                          (raise-syntax-error #f
                                              "bad field specification"
                                              stx
                                              field)])))
                   (syntax->list #'(field ...)))
         (let-values ([(no-ctr? mutable? no-stx? no-rt?)
                       (let loop ([opts (syntax->list #'(opt ...))]
                                  [no-ctr? #f]
                                  [mutable? #f]
                                  [no-stx? #f]
                                  [no-rt? #f])
                         (if (null? opts)
                             (values no-ctr? mutable? no-stx? no-rt?)
                             (let ([opt (car opts)])
                               (case (syntax-e opt)
                                 [(#:omit-constructor)
                                  (if no-ctr?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) #t mutable? no-stx? no-rt?))]
                                 [(#:mutable)
                                  (if mutable?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) no-ctr? #t no-stx? no-rt?))]
                                 [(#:omit-define-syntaxes)
                                  (if no-stx?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) no-ctr? mutable? #t no-rt?))]
                                 [(#:omit-define-values)
                                  (if no-rt?
                                      (raise-syntax-error #f
                                                          "redundant option"
                                                          stx
                                                          opt)
                                      (loop (cdr opts) no-ctr? mutable? no-stx? #t))]
                                 [else
                                  (raise-syntax-error #f
                                                      (string-append
                                                       "expected a keyword to specify option: "
                                                       "#:mutable, #:omit-constructor, #:omit-define-syntaxes, or #:omit-define-values")
                                                      stx
                                                      opt)]))))])
           (define (add-contracts l)
             (let* ([pred (caddr l)]
                    [ctor-ctc #`(-> ctc ... #,pred)]
                    [pred-ctc #'(-> any/c boolean?)]
                    [field-ctcs
                     (apply append
                            (map (λ (f c)
                                   (cons #`(-> #,pred #,c)
                                         (if (and (not mutable?)
                                                  (not (pair? (syntax-e f))))
                                             null
                                             #`(-> #,pred #,c void?))))
                                 (syntax->list #'(field ...))
                                 (syntax->list #'(ctc ...))))])
               (list* (car l)
                      (list (cadr l) ctor-ctc)
                      (list pred pred-ctc)
                      (map list (cdddr l) field-ctcs))))
           (cons
            #`(define-syntaxes (name)
                #,(build-struct-expand-info
                   #'name (syntax->list #'(field ...))
                   #f (not mutable?)
                   #f '(#f) '(#f)
                   #:omit-constructor? no-ctr?))
            (let* ([names (add-contracts
                           (build-struct-names #'name (syntax->list #'(field ...))
                                               #f (not mutable?)))]
                   [cpairs (cons 'contracted
                                 (if no-ctr? (cddr names) (cdr names)))])
              (list (car names) cpairs))))))
      ((_ name fields opt ...)
       (raise-syntax-error #f
                           "bad syntax; expected a parenthesized sequence of fields"
                           stx
                           #'fields))
      ((_ name)
       (raise-syntax-error #f
                           "bad syntax; missing fields"
                           stx))
      ((_)
       (raise-syntax-error #f
                           "missing name and fields"
                           stx)))))
