#lang scheme/base
(require scheme/class
         (rename-in unstable/class-iop
                    [define/i define:]
                    [send/i send:])
         scheme/unit
         scheme/list
         scheme/file
         scheme/match
         scheme/gui
         framework/framework
         syntax/boundmap
         "interfaces.ss"
         "stepper.ss"
         "prefs.ss"
         "warning.ss"
         "hiding-panel.ss"
         (prefix-in sb: "../syntax-browser/embed.ss")
	 (prefix-in sb: "../syntax-browser/interfaces.ss")
         "../model/deriv.ss"
         "../model/deriv-util.ss"
         "../model/trace.ss"
         "../model/steps.ss"
         "cursor.ss"
         unstable/gui/notify)
(provide macro-stepper-frame-mixin)

(define-syntax override/return-false
  (syntax-rules ()
    [(override/return-false m ...)
     (begin (define/override (m) #f) ...)]))

(define (macro-stepper-frame-mixin base-frame%)
  (class* base-frame% (stepper-frame<%>)
    (init-field config)
    (init-field director)
    (init-field (filename #f))

    (define obsoleted? #f)

    (inherit get-area-container
             get-size
             set-label
             get-menu%
             get-menu-item%
             get-menu-bar
             get-file-menu
             get-edit-menu
             get-help-menu)

    (super-new (label (make-label))
               (width (send: config config<%> get-width))
               (height (send: config config<%> get-height)))

    (define/private (make-label)
      (if filename
          (string-append (path->string
                          (file-name-from-path filename))
                         (if obsoleted? " (old)" "")
                         " - Macro stepper")
          "Macro stepper"))

    ;; Grrr... we get a spurious on-size event sometime after the
    ;; frame is created, probably when the window-manager gets around
    ;; to doing something. Avoid unnecessary updates.
    (define-values (w0 h0) (get-size))
    (define/override (on-size w h)
      (send: config config<%> set-width w)
      (send: config config<%> set-height h)
      (unless (and (= w0 w) (= h0 h))
        (send: widget widget<%> update/preserve-view))
      (set!-values (w0 h0) (values w h)))

    (define warning-panel
      (new horizontal-panel%
           (parent (get-area-container))
           (stretchable-height #f)
           (style '(deleted))))

    (define/public (get-macro-stepper-widget%)
      macro-stepper-widget%)

    (define: widget widget<%>
      (new (get-macro-stepper-widget%)
           (parent (get-area-container))
           (director director)
           (config config)))
    (define: controller sb:controller<%>
      (send: widget widget<%> get-controller))

    (define/public (get-widget) widget)
    (define/public (get-controller) controller)

    (define/public (add-obsoleted-warning)
      (unless obsoleted?
        (set! obsoleted? #t)
        (new warning-canvas%
             (warning
              (string-append
               "Warning: This macro stepper session is obsolete. "
               "The program may have changed."))
             (parent warning-panel))
        (set-label (make-label))
        (send (get-area-container) change-children
              (lambda (children)
                (cons warning-panel
                      (remq warning-panel children))))))

    ;; Set up menus

    (override/return-false file-menu:create-new?
                           file-menu:create-open?
                           file-menu:create-open-recent?
                           file-menu:create-revert?
                           file-menu:create-save?
                           file-menu:create-save-as?
                           ;file-menu:create-print?
                           edit-menu:create-undo?
                           edit-menu:create-redo?
                           ;edit-menu:create-cut?
                           ;edit-menu:create-paste?
                           edit-menu:create-clear?)

    (define stepper-menu
      (new (get-menu%) (parent (get-menu-bar)) (label "Stepper")))

    (define/override (file-menu:between-new-and-open file-menu)
      (new (get-menu-item%)
           (label "Duplicate stepper")
           (parent file-menu)
           (callback (lambda _ (send: widget widget<%> duplicate-stepper))))
      (new (get-menu-item%)
           (label "Duplicate stepper (current term only)")
           (parent file-menu)
           (callback (lambda _ (send: widget widget<%> show-in-new-frame)))))

    (menu-option/notify-box stepper-menu
                            "View syntax properties"
                            (get-field props-shown? config))

    (let ([id-menu
           (new (get-menu%)
                (label "Identifier=?")
                (parent stepper-menu))])
      (for-each (lambda (p)
                  (let ([this-choice
                         (new checkable-menu-item%
                              (label (car p)) 
                              (parent id-menu)
                              (callback
                               (lambda _ 
                                 (send: controller sb:controller<%> set-identifier=? p))))])
                    (send: controller sb:controller<%> listen-identifier=?
                           (lambda (name+func)
                             (send this-choice check
                                   (eq? (car name+func) (car p)))))))
                (sb:identifier=-choices)))

    (let ([identifier=? (send: config config<%> get-identifier=?)])
      (when identifier=?
        (let ([p (assoc identifier=? (sb:identifier=-choices))])
          (send: controller sb:controller<%> set-identifier=? p))))

    (new (get-menu-item%)
         (label "Clear selection")
         (parent stepper-menu)
         (callback
          (lambda _ (send: controller sb:controller<%> 
			   set-selected-syntax #f))))

    (new separator-menu-item% (parent stepper-menu))

    (menu-option/notify-box stepper-menu
                            "Show macro hiding panel"
                            (get-field show-hiding-panel? config))

    (new (get-menu-item%)
         (label "Remove selected term")
         (parent stepper-menu)
         (callback (lambda _ (send: widget widget<%> remove-current-term))))
    (new (get-menu-item%)
         (label "Reset mark numbering")
         (parent stepper-menu)
         (callback (lambda _ (send: widget widget<%> reset-primary-partition))))
    (let ([extras-menu
           (new (get-menu%)
                (label "Extra options")
                (parent stepper-menu))])
      (new checkable-menu-item%
           (label "Always suffix marked identifiers")
           (parent extras-menu)
           (callback
            (lambda (i e)
              (send: config config<%> set-suffix-option
                     (if (send i is-checked?)
                         'always
                         'over-limit))
              (send: widget widget<%> update/preserve-view))))
      (menu-option/notify-box extras-menu
                              "Factor out common context?"
                              (get-field split-context? config))
      (menu-option/notify-box extras-menu
                              "Highlight redex/contractum"
                              (get-field highlight-foci? config))
      (menu-option/notify-box extras-menu
                              "Highlight frontier"
                              (get-field highlight-frontier? config))
      (menu-option/notify-box extras-menu
                              "Include renaming steps"
                              (get-field show-rename-steps? config))
      (menu-option/notify-box extras-menu
                              "One term at a time"
                              (get-field one-by-one? config))
      (menu-option/notify-box extras-menu
                              "Extra navigation"
                              (get-field extra-navigation? config))
      #|
      (menu-option/notify-box extras-menu
                              "Suppress warnings"
                              (get-field suppress-warnings? config))
      (menu-option/notify-box extras-menu
                              "(Debug) Catch internal errors?"
                              (get-field debug-catch-errors? config))
      |#)

    ;; fixup-menu : menu -> void
    ;; Delete separators at beginning/end and duplicates in middle
    (define/private (fixup-menu menu)
      (define items
        (filter (lambda (i) (not (send i is-deleted?)))
                (send menu get-items)))
      (define (delete-seps-loop items)
        (if (and (pair? items) (is-a? (car items) separator-menu-item%))
            (begin (send (car items) delete)
                   (delete-seps-loop (cdr items)))
            items))
      (define (middle-loop items)
        (cond
         [(and (pair? items) (is-a? (car items) separator-menu-item%))
          (middle-loop (delete-seps-loop (cdr items)))]
         [(pair? items)
          (middle-loop (cdr items))]
         [else null]))
      (middle-loop (delete-seps-loop items))
      (delete-seps-loop (reverse items))
      (void))

    (for ([menu (send (get-menu-bar) get-items)])
      (fixup-menu menu))
    (frame:remove-empty-menus this)
    (frame:reorder-menus this)))

;; Stolen from stepper

(define warning-color "yellow")
(define warning-font normal-control-font)

(define warning-canvas%
  (class canvas%
    (init-field warning)
    (inherit get-dc get-client-size)
    (define/override (on-paint)
      (let ([dc (get-dc)])
        (send dc set-font warning-font) 
        (let-values ([(cw ch) (get-client-size)]
                     [(tw th dont-care dont-care2) 
                      (send dc get-text-extent warning)])
          (send dc set-pen 
                (send the-pen-list find-or-create-pen warning-color 1 'solid))
          (send dc set-brush
                (send the-brush-list find-or-create-brush warning-color 'solid))
          (send dc draw-rectangle 0 0 cw ch)
          (send dc draw-text 
                warning
                (- (/ cw 2) (/ tw 2))
                (- (/ ch 2) (/ th 2))))))
    (super-new)
    (inherit min-width min-height stretchable-height)
    (let-values ([(tw th dc dc2)
                  (send (get-dc) get-text-extent warning warning-font)])
      (min-width (+ 2 (inexact->exact (ceiling tw))))
      (min-height (+ 2 (inexact->exact (ceiling th)))))
    (stretchable-height #f)))
