(module seqcontract mzscheme
  (require mzlib/class)
  (provide (protect es-contract-mixin lock-contract-mixin))
  
  (require-for-syntax syntax/stx
                      syntax/boundmap)

  (define-syntax (sequence-contract-mixin stx)
    (syntax-case stx (state-machine)
      [(_ (state-machine
           [name exp (method-name aritiess states ...) ...] ...)
          clauses ...)
       (and (andmap identifier? (syntax->list (syntax (name ...))))
            (andmap (lambda (x) (andmap identifier? (syntax->list x)))
                    (syntax->list (syntax ((method-name ...) ...))))
            (andmap (lambda (xs) 
                      (andmap (lambda (x) (andmap identifier? (syntax->list x)))
                              (syntax->list xs)))
                    (syntax->list (syntax (((states ...) ...) ...)))))
       (let ()
         (define state-names (syntax->list (syntax (name ...))))
         (define predicate-names (generate-temporaries (syntax (name ...))))
         
         (define state-name->predicate-name
           (let ([mapping (make-bound-identifier-mapping)])
             (for-each (lambda (state-name predicate-name)
                         (bound-identifier-mapping-put! mapping state-name predicate-name))
                       state-names
                       predicate-names)
             (lambda (state-name)
               (bound-identifier-mapping-get mapping state-name))))
         
         (define-struct state-desc (method-name arities predicate-name state-name result-predicates) (make-inspector))
         
         ;; -> mapping[state-name-symbol -o> state-desc]
         (define (build-table)
           (let ([mapping (new-mapping)])
             (for-each 
              (lambda (state-name-stx predicate-name-stx method-names aritiess state-namess)
                (for-each
                 (lambda (method-name arities state-names-stx)
                   (extend-mapping/at-end
                    mapping
                    method-name
                    (make-state-desc method-name
                                     arities
                                     predicate-name-stx
                                     state-name-stx
                                     (syntax->list state-names-stx))))
                 (syntax->list method-names)
                 (syntax->list aritiess)
                 (syntax->list state-namess)))
              (syntax->list (syntax (name ...)))
              predicate-names
              (syntax->list (syntax ((method-name ...) ...)))
              (syntax->list (syntax ((aritiess ...) ...)))
              (syntax->list (syntax (((states ...) ...) ...))))
             mapping))
         
         (define (build-overriding-method mapping state-descs)
           (with-syntax ([method-name (state-desc-method-name (car state-descs))]
                         [super-method-name (build-super-name (state-desc-method-name (car state-descs)))]
                         [(predicate-name ...) (map state-desc-predicate-name state-descs)]
                         [(predicate-result-name ...)
                          (generate-temporaries
                           (map state-desc-predicate-name state-descs))]
                         [(state-name ...) (map state-desc-state-name state-descs)]
                         [((result-predicate-state ...) ...)
                          (map state-desc-result-predicates state-descs)]
                         [((result-predicate-name ...) ...)
                          (map 
                           (lambda (state-desc)
                             (map state-name->predicate-name
                                  (state-desc-result-predicates state-desc)))
                           state-descs)])
             (with-syntax ([(cases ...)
                            (map (lambda (arity)
                                   (with-syntax ([formals arity])
                                     (with-syntax ([call (if (identifier? arity)
                                                             (syntax (super-method-name . formals))
                                                             (with-syntax ([(x ...) arity])
                                                               (syntax (super-method-name x ...))))]
                                                   [args-as-list
                                                    (if (identifier? arity)
                                                        arity
                                                        (with-syntax ([(x ...) arity])
                                                          (syntax (list x ...))))])
                                       (syntax
                                        [formals
                                         (let ([predicate-result-name (predicate-name)] ...)
                                           (cond
                                             [predicate-result-name
                                              call
                                              
                                              ;; Doesn't do post-condition checking,
                                              ;; since it isn't thread safe
                                              #;
                                              (begin0
                                                call
                                                (unless (or (result-predicate-name) ...)
                                                  (sequence-contract-violation 
                                                   'positive 
                                                   "expected one of states ~s after calling ~s in state ~s"
                                                   '(result-predicate-state ...)
                                                   'method-name
                                                   'state-name)))
                                              ]
                                             ...
                                             [else
                                              (sequence-contract-violation 
                                               'negative
                                               "method ~s cannot be called, except in states ~s~a"
                                               'method-name
                                               '(state-name ...)
                                               (format-args args-as-list))]))]))))
                                 (syntax->list (state-desc-arities (car state-descs))))])
               (syntax
                (begin
                  (rename-super [super-method-name method-name])
                  (define/override method-name
                    (case-lambda cases ...)))))))
         
         ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
         ;;
         ;; finite mapping code
         ;;
         
         (define (new-mapping) (make-hash-table))
         (define (set-mapping mapping key-stx val)
           (hash-table-put! mapping (syntax-e key-stx) val))
         (define get-mapping
           (case-lambda
             [(mapping key-stx) (get-mapping mapping key-stx (lambda () (error 'get-mapping "ack!")))]
             [(mapping key-stx fail)
              (hash-table-get mapping (syntax-e key-stx) fail)]))
         (define (extend-mapping/at-end mapping key-stx ele)
           (set-mapping mapping key-stx 
                        (append
                         (get-mapping mapping key-stx (lambda () null))
                         (list ele))))
         (define (mapping-map f mapping)
           (hash-table-map mapping f))
         
         ;;
         ;;
         ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
         
         (define (build-super-name name)
           (datum->syntax-object
            name
            (string->symbol
             (format 
              "super-~a"
              (syntax-object->datum name)))))
         
         (define table (build-table))
         (with-syntax ([(predicate-names ...) predicate-names]
                       [(overriding-method ...) (mapping-map
                                                 (lambda (k vs) (build-overriding-method table vs))
                                                 table)])
           
           (syntax
            (lambda (%)
              (class* % ()
                (define/private predicate-names (lambda () exp)) ...
                overriding-method ... 
                clauses ...)))))]))
  
  (define (format-args l)
    (cond
      [(null? l) ""]
      [else
       (string-append
        ", args "
        (let loop ([fst (car l)]
                   [rst (cdr l)])
          (cond
            [(null? rst) (format "~e" fst)]
            [else (string-append
                   (format "~e" fst)
                   " "
                   (loop (car rst) (cdr rst)))])))]))

  (define (sequence-contract-violation dir fmt . args)
    (apply error
           'sequence-contract-violation 
           (string-append (format "~a: " dir) fmt)
           args))

  (define es-contract-mixin 
    (sequence-contract-mixin
     (state-machine
      [in-edit-sequence
       (in-edit-sequence?)
       (begin-edit-sequence [() (x) (x y)] in-edit-sequence)
       (end-edit-sequence [()] in-edit-sequence out-of-edit-sequence)]
      [out-of-edit-sequence
       (not (in-edit-sequence?))
       (begin-edit-sequence [() (x) (x y)] in-edit-sequence)])
     
     (inherit in-edit-sequence?)
     (super-new)))

#|

  (define (test t)
    (send t begin-edit-sequence)
    (send t end-edit-sequence)
    (send t end-edit-sequence))
  
  (test (new text%))
  (test (new (es-contract-mixin text%)))
  
Matthew writes:

> Underscore tends to mean "internal". Many variants of
> Insert(), for example, call the main _Insert() method.

So, double check the methods to make sure that a flag check
in an underscore method means the flag is checked in the
non-underscore methods.

At Sun, 29 Jun 2003 09:26:02 -0500, Robby Findler wrote:
> Is there some kind of invariant or ordering on these
> flags? That is, if a method only checks the flowLocked flag,
> is that effectively the same as checking the flowLocked flag
> or the writeLocked flag or something like that?

Yes: readLocked => flowLocked, and flowLocked => writeLocked.

Matthew

  |#
  
  ;; need to figure out
  ;; line-start-position and friends 
  ;; (line-start-position not valid in readlock)
  
  (define lock-contract-mixin
    (sequence-contract-mixin
     (state-machine
      [unlocked
       (and (not (locked-for-write?))
            (not (locked-for-flow?))
            (not (locked-for-read?)))
       (set-position [(x) (x y) (x y z) (x y z p) (x y z p q) (x y z p q r)] unlocked)
       (set-autowrap-bitmap [(bitmap)] unlocked)
       (print-to-dc [(dc) (dc page)] unlocked)
       (move-position [(code?) (code? extend) (code? extend kind)] unlocked)
       (split-snip [(pos)] unlocked)
       (set-line-spacing [(space)] unlocked)
       (set-max-width [(width)] unlocked)
       (set-min-width [(width)] unlocked)
       (set-min-height [(width)] unlocked)
       (set-max-height [(width)] unlocked)
       (set-tabs [(tabs) (tabs tab-width) (tabs tab-width units?)] unlocked)
       (print [()
               (interactive?)
               (interactive? fit-on-page?)
               (interactive? fit-on-page? output-mode)
               (interactive? fit-on-page? output-mode parent)
               (interactive? fit-on-page? output-mode parent force-ps-page-bbox?)
               (interactive? fit-on-page? output-mode parent force-ps-page-bbox? as-eps?)]
              unlocked)
       
       (get-text [() (x) (x y) (x y z) (x y z p)] unlocked)
       (get-flattened-text [()] unlocked)
       (get-character [(start)] unlocked)
       (find-wordbreak [(start end reason)] unlocked)
       (save-file [() (filename) (filename format) (filename format show-errors?)] unlocked)
       (write-to-file [(stream) (stream start end)]  unlocked)
       (find-position [(x y) (x y at-eol?) (x y at-eol? on-it?) (x y at-eol? on-it? edge-close?)] unlocked)
       (scroll-line-location [(pos)] unlocked)
       (num-scroll-lines [()] unlocked)
       (find-scroll-line [(location)] unlocked)
       (style-has-changed [(style)] unlocked)

       (set-paragraph-margins [(para fl l r)] unlocked)
       (set-paragraph-alignment [(para align)] unlocked)

       (change-style [(x) (x y) (x y z) (x y z w)] unlocked)
       (insert [(x) (x y) (x y z) (x y z p) (x y z p q)] unlocked)
       (delete [() (start) (start end) (start end scroll-ok?)] unlocked)
       (insert-port [(port) (port format) (port format show-errors?)] unlocked)
       (read-from-file [(x) (x y) (x y z)] unlocked)
       (set-style-list [(style-list)] unlocked)]
      
      [write-lock
       (and (locked-for-write?)
            (not (locked-for-flow?))
            (not (locked-for-read?)))

       (set-position [(x) (x y) (x y z) (x y z p) (x y z p q)] write-lock)
       (set-autowrap-bitmap [(bitmap)] write-lock)
       (print-to-dc [(dc)] write-lock)
       (move-position [(code?) (code? extend) (code? extend kind)] write-lock)
       (split-snip [(pos)] write-lock)
       (set-line-spacing [(space)] write-lock)
       (set-max-width [(width)] write-lock)
       (set-min-width [(width)] write-lock)
       (set-min-height [(width)] write-lock)
       (set-max-height [(width)] write-lock)
       (set-tabs [(tabs) (tabs tab-width) (tabs tab-width units?)] write-lock)
       (print [()
               (interactive?)
               (interactive? fit-on-page?)
               (interactive? fit-on-page? output-mode)
               (interactive? fit-on-page? output-mode parent)
               (interactive? fit-on-page? output-mode parent force-ps-page-bbox?)]
              write-lock)
       
       (get-text [() (x) (x y) (x y z) (x y z p)] write-lock)
       (get-flattened-text [()] write-lock)
       (get-character [(start)] write-lock)
       (find-wordbreak [(start end reason)] write-lock)
       (save-file [() (filename) (filename format) (filename format show-errors?)] write-lock)
       (write-to-file [(stream) (stream start end)]  write-lock)
       (find-position [(x y) (x y at-eol? on-it? edge-close?)] write-lock)
       (scroll-line-location [(pos)] write-lock)
       (num-scroll-lines [()] write-lock)
       (find-scroll-line [(location)] write-lock)
       (style-has-changed [(style)] write-lock)]
      
      [flow-lock
       (and (locked-for-flow?)
            (not (locked-for-read?)))

       (get-text [() (x) (x y) (x y z) (x y z p)] flow-lock)
       (get-flattened-text [()] flow-lock)
       (get-character [(start)] flow-lock)
       (find-wordbreak [(start end reason)] flow-lock)
       (save-file [() (filename) (filename format) (filename format show-errors?)] flow-lock)
       (write-to-file [(stream) (stream start end)]  flow-lock)
       (find-position [(x y) (x y at-eol? on-it? edge-close?)] flow-lock)
       (scroll-line-location [(pos)] flow-lock)
       (num-scroll-lines [()] flow-lock)
       (find-scroll-line [(location)] flow-lock)
       (style-has-changed [(style)] flow-lock)]

      [read-lock
       (locked-for-read?)])

     (inherit locked-for-flow?
              locked-for-write?
              locked-for-read?)
     (super-new))))

  #|
     ;; flowLocked in wx_mpriv
     set-position ; _SetPosition
     CheckRecalc  (only if graphicMaybeInvalid aka locations-computed?)
     set-autowrap-bitmap ; SetAutowrapBitmap
     Redraw 
     BeginPrint
     EndPrint
     HasPrintPage
     print-to-dc ; PrintToDC
     
     ;; flowlocked in wx_media.cxx
     move-position ; MovePosition
     split-snip ; SplitSnip
     set-line-spacing ; SetLineSpacing
     set-max-width ; SetMaxWidth
     set-min-width ; SetMinWidth
     set-min-height ; SetMinHeight
     set-max-height ; SetMaxHeight
     set-tabs ; SetTabs
     resized ; Resized ;; uses the flag, but not to abort
     
     ;; methods that consider
     ;; the readLocked variable,
     ;; at the C level; they just
     ;; return if it is set.
     get-text ; GetText
     get-character ; GetCharacter
     find-wordbreak ; FindWorkbreak
     save-file ; SaveFile
     write-to-file ; WriteToFile
     _FindPositionInSnip
     find-position ; FindPosition
     scroll-line-location ; ScrollLineLocation
     num-scroll-lines ; NumScrollLines
     find-scroll-line ; FindScrollLine
     style-has-changed ; StyleHasChanged ;; maybe need to expand this to include style lists?

     FindFirstVisiblePosition ;; LineStartPosition?
     FindLastVisiblePosition
     CheckRecalc
     
     ;; methods that consider the writeLocked variable,
     ;; at the C level
     _ChangeStyle
     _Insert
     _Delete
     insert-port ; InsertPort
     read-from-file ; ReadFromFile
     set-style-list ; SetStyleList
     ; Recounted
     ReallyCanEdit -- only when op != wxEDIT_COPY
     
     ;; in wx_mpbrd.cxx
     insert ; Insert
     delete ; Delete
     erase ; Erase
     delete ; Delete ;; -- with arg
     remove ; Remove
     move-to ; MoveTo
     move ; Move, also with arg
     change-style ; _ChangeStyle
     set-before ;SetBefore
     set-after ;SetAfter
     ;ReallyCanEdit -- only when op != wxEDIT_COPY
     ;Refresh has weird code checking writeLocked -- what does < 0 mean?
     do-paste ; DoPaste
     paste ; Paste
     insert-port ; InsertPort
     insert-file ; InsertFile
     read-from-file ; ReadFromFile
     ; BeginEditSequence ;; -- weird flag check
     ; EndEditSequence ;; -- weird flag check, like BeginEditSequence
  
  |#
