#lang racket/base
(require racket/class
         (for-syntax racket/base)
         racket/file
         racket/port
         "../syntax.rkt"
         "private.rkt"
         racket/snip/private/snip
         racket/snip/private/private
         racket/snip/private/style
         racket/snip/private/snip-flags
         racket/snip/private/prefs
         "editor-admin.rkt"
         "stream.rkt"
         "undo.rkt"
         "keymap.rkt"
         "editor-data.rkt"
         (only-in "cycle.rkt"
                  printer-dc%
                  text%
                  pasteboard%
                  editor-snip%
                  editor-snip-editor-admin%
                  editor-get-file
                  editor-put-file)
         "wx.rkt")

(provide editor%
         editor<%>
         add-editor-keymap-functions
         ALLOW-X-STYLE-SELECTION?
         copy-style-list
         set-common-copy-region-data!
         cons-common-copy-buffer!
         cons-common-copy-buffer2!
         editor-set-x-selection-mode
         editor-x-selection-allowed
         editor-x-selection-mode?
         editor-x-selection-owner
         detect-wxme-file
         read-editor-version
         read-editor-global-header
         read-editor-global-footer
         write-editor-version
         write-editor-global-header
         write-editor-global-footer
         write-snips-to-file
         get-default-print-size)

;; ----------------------------------------

(define RIDICULOUS-SIZE 2000)
(define ALLOW-X-STYLE-SELECTION? (eq? 'unix (system-type)))

(defclass offscreen% object%
  (define bitmap #f)
  (define dc (make-object bitmap-dc%))
  (define bm-width 0)
  (define bm-height 0)
  (define in-use? #f)
  (define last-used #f)

  (define/public (is-in-use?) in-use?)
  (define/public (set-in-use v) (set! in-use? (and v #t)))
  (define/public (get-bitmap) bitmap)
  (define/public (get-dc) dc)
  (define/public (get-last-used) last-used)
  (define/public (set-last-used v) (set! last-used v))

  (define/public (ready-offscreen width height)
    (if (or #t ; disable on all  platforms
            (width . > . RIDICULOUS-SIZE)
            (height . > . RIDICULOUS-SIZE)
            (eq? (system-type) 'macosx))
        #f
        (if (and (not in-use?)
                 (or (height . > . bm-height)
                     (width . > . bm-width)))
            (let ([oldbm bitmap])
              (set! bm-height (max (add1 (->long height)) bm-height))
              (set! bm-width (max (add1 (->long width)) bm-width))
              (set! bitmap (make-object bitmap% bm-width bm-height))
              (send dc set-bitmap #f)
              (when (send bitmap ok?)
                (send dc set-bitmap bitmap))
              #t)
            #f)))

  (super-new))

(define the-offscreen (new offscreen%))

;; ----------------------------------------

;; 8.5" x 11" paper, 0.5" margin; usually not used
(define PAGE-WIDTH 612)
(define PAGE-HEIGHT 792)

(define (get-printer-orientation)
  (send (current-ps-setup)  get-orientation))

(define (get-default-print-size w h)
  (set-box! w PAGE-WIDTH)
  (set-box! h PAGE-HEIGHT)
  (when (eq? (get-printer-orientation) 'landscape)
    (let ([tmp (unbox h)])
      (set-box! h (unbox w))
      (set-box! w tmp))))

;; ----------------------------------------

(define default-emacs-style-undo? (and (get-preference* 'GRacket:emacs-undo) #t))
(define (max-undo-value? v) (or (exact-nonnegative-integer? v)
                                (eq? v 'forever)))

(define global-lock (make-semaphore 1))

(defclass editor% object%

  (field [s-offscreen the-offscreen]
         [s-admin #f]
         [s-keymap (new keymap%)]
         [s-own-caret? #f]
         [s-temp-filename? #f]
         [s-user-locked? #f]
         [s-modified? #f]
         [s-noundomode 0])
  (def/public (is-modified?) s-modified?)
  
  (define undomode? #f)
  (define redomode? #f)
  (define interceptmode? #f)
  (define loadoverwritesstyles? #t)
  (define emacs-style-undo? default-emacs-style-undo?)

  (field [s-custom-cursor-overrides? #f]
         [s-need-on-display-size? #f])
  (define paste-text-only? #f)

  (define num-parts-modified 0)

  (field [s-caret-snip #f]
         [s-style-list (new style-list%)])
  (define/public (get-focus-snip) s-caret-snip)
  (define/public (get-s-style-list) s-style-list)

  (send s-style-list new-named-style "Standard" (send s-style-list basic-style))
  (define notify-id
    (send s-style-list notify-on-change (lambda (which) (style-has-changed which))))
  
  (field [s-filename #f]) ; last loaded file

  (define max-undos 0)

  (define changes #())
  (define changes-start 0)
  (define changes-end 0)
  (define changes-size 0)

  (define redochanges #())
  (define redochanges-start 0)
  (define redochanges-end 0)
  (define redochanges-size 0)
  
  (define savedchanges #f) ;; for emacs-style undo
  (define intercepted null)

  (field [s-custom-cursor #f]
         [s-inactive-caret-threshold 'show-inactive-caret])

  (define printing #f)
  (define/public (get-printing) printing)

  (define num-extra-headers 0)
  (define seq-lock #f)

  (super-new)

  (define/public (is-printing?) (and printing #t))

  ;; ----------------------------------------

  (def/public (blink-caret) (void))

  (def/public (size-cache-invalid) (void))
  (def/public (locked-for-read?) #f)
  (def/public (locked-for-write?) #f)
  (def/public (locked-for-flow?) #f)

  (def/public (resized) (void))
  (def/public (recounted) (void))
  (define/public (invalidate-bitmap-cache) (void))
  (def/public (needs-update) (void))
  (def/public (release-snip) (void))

  (def/public (scroll-line-location) (void))
  (def/public (num-scroll-lines) (void))
  (def/public (find-scroll-line) (void))

  ;; ----------------------------------------

  (define/public (on-event event) (void))
  (define/public (on-char event) (void))

  (def/public (on-local-event [mouse-event% event])
    (unless (and s-keymap
                 (or (send s-keymap handle-mouse-event this event)
                     (begin
                       (when (not (send event moving?))
                         (send s-keymap break-sequence))
                       #f)))
      (on-default-event event)))
  
  (def/public (on-local-char [key-event% event])
    (unless (and s-keymap
                 (or (send s-keymap handle-key-event this event)
                     (begin
                       (send s-keymap break-sequence)
                       #f)))
      (on-default-char event)))

  (define/public (on-default-event event) (void))
  (define/public (on-default-char event) (void))
  
  (def/public (on-focus [any? on?]) (void))

  ;; ----------------------------------------

  (def/public (set-admin [(make-or-false editor-admin%) administrator])
    (setting-admin administrator)

    (set! s-admin administrator)
    (when (not s-admin)
      (set! s-own-caret? #f))
    (when s-admin
      (init-new-admin)))

  (def/public (setting-admin [(make-or-false editor-admin%) a]) (void))

  (def/public (init-new-admin) (void))

  (def/public (get-admin) s-admin)

  ;; ----------------------------------------

  (def/public (own-caret [any? ownit?]) (void))

  (def/public (do-own-caret [any? ownit?])
    (let ([ownint? (and ownit? #t)])
      (let ([refresh? (and (not s-caret-snip)
                           (not (eq? s-own-caret? ownit?)))])
        (set! s-own-caret? ownit?)
        (when s-caret-snip
          (send s-caret-snip own-caret ownit?))
        (when (and s-keymap (not ownint?) refresh?)
          (send s-keymap break-sequence))

        (when ALLOW-X-STYLE-SELECTION?
          (cond
           [(and ownit? (not s-caret-snip))
            (set! editor-x-selection-allowed this)]
           [(eq? editor-x-selection-allowed this)
            (set! editor-x-selection-allowed #f)]))

        (when s-admin
          (send s-admin update-cursor))

        refresh?)))

  (def/public (get-dc)
    ;; this can be called by snips to get a DC appropriate for
    ;; sizing text, etc., outside of draws. it isn't the destination 
    ;; for draws, though
    (if s-admin
        (send s-admin get-dc #f #f)
        #f))

  (def/public (get-view-size [(make-or-false box?) w][(make-or-false box?) h])
    (if s-admin
        (send s-admin get-view #f #f w h)
        (begin
          (when w (set-box! w 0.0))
          (when h (set-box! h 0.0)))))

  (define/public (get-snip-location snip x y)
    (when x (set-box! x 0.0))
    (when y (set-box! y 0.0))
    #t)

  (def/public (do-set-caret-owner [(make-or-false snip%) snip] [symbol? dist])
    (let ([same? (eq? snip s-caret-snip)])
      (if (and same?
               (or (not s-admin) (eq? dist 'immediate)))
          #f
          (begin
            (when same?
              (send s-admin grab-caret dist))

            (let ([vis-caret? s-own-caret?])
              (cond
               [(or (not snip)
                    (not (has-flag? (snip->flags snip) HANDLES-EVENTS)))
                
                (let ([old-caret s-caret-snip]
                      [refresh? #f])
                  (set! s-caret-snip #f)
                  (when old-caret
                    (send old-caret own-caret #f)
                    (when vis-caret?
                      (set! refresh? #t)))
                  (when ALLOW-X-STYLE-SELECTION?
                    (set! editor-x-selection-allowed this))
                  (when s-admin
                    (send s-admin update-cursor))
                  refresh?)]
               [(not (get-snip-location snip #f #f)) #f]
               [else
                (let ([had-caret? (and s-own-caret?
                                       (not s-caret-snip))]
                      [old-caret s-caret-snip]
                      [refresh? #f])

                  (set! s-caret-snip snip)

                  (begin-edit-sequence)
                  (cond
                   [old-caret (send old-caret own-caret #f)]
                   [vis-caret? (set! refresh? #t)])
                  (send snip own-caret s-own-caret?)
                  (end-edit-sequence)

                  (when (and s-admin
                             (not (eq? dist 'immediate)))
                    (send s-admin grab-caret dist))

                  (when s-admin
                    (send s-admin update-cursor))

                  refresh?)]))))))

  (define/private (convert-coords admin x y to-local?)
    (let-values ([(lx ly)
                  (if admin
                      (if (admin . is-a? . editor-snip-editor-admin%)
                          (let* ([snip (send admin get-snip)]
                                 [sa (send snip get-admin)])
                            (if sa
                                (let ([mbuf (send sa get-editor)])
                                  (if mbuf 
                                      (let-boxes ([bx 0.0][by 0.0]
                                                  [lx 0.0][ly 0.0]
                                                  [l 0.0][t 0.0][r 0.0][b 0.0])
                                          (begin
                                            (send mbuf local-to-global bx by)
                                            (send mbuf get-snip-location snip lx ly #f)
                                            (send snip get-margin l t r b))
                                        (values (+ lx bx l)
                                                (+ ly by t)))
                                      (values 0.0 0.0)))
                                (values 0.0 0.0)))
                          (let-boxes ([lx 0.0][ly 0.0])
                              (send admin get-dc lx ly)
                            (values (- lx) (- ly))))
                      (values 0.0 0.0))])
      (when x (set-box! x (+ (unbox x) (if to-local? (- lx) lx))))
      (when y (set-box! y (+ (unbox y) (if to-local? (- ly) ly))))))

  (def/public (editor-location-to-dc-location [real? x] [real? y])
    (let-boxes ([x x] [y y])
        (local-to-global x y)
      (values x y)))

  (def/public (dc-location-to-editor-location [real? x] [real? y])
    (let-boxes ([x x] [y y])
        (global-to-local x y)
      (values x y)))

  (def/public (global-to-local [maybe-box? x] [maybe-box? y])
    (convert-coords s-admin x y #t))

  (def/public (local-to-global [maybe-box? x] [maybe-box? y])
    (convert-coords s-admin x y #f))

  (def/public (set-cursor [(make-or-false cursor%) c] [any? [override? #t]])
    (set! s-custom-cursor c)
    (set! s-custom-cursor-overrides? override?)
    (when s-admin
      (send s-admin update-cursor)))

  (def/public (adjust-cursor [mouse-event% event]) (void))

  ;; ----------------------------------------

  (def/public (set-keymap [(make-or-false keymap%) [k #f]])
    (set! s-keymap k))
  (def/public (get-keymap) s-keymap)
  (def/public (get-style-list) s-style-list)

  (def/public (set-style-list [style-list% new-list])
    (send s-style-list forget-notification notify-id)
    (set! notify-id
          (send new-list notify-on-change (lambda (which) (style-has-changed which))))
    (set! s-style-list new-list)
    ;; create "Standard" if it's not there:
    (send s-style-list new-named-style "Standard" (send s-style-list basic-style))
    (void))

  (define/public (style-has-changed which) (void))
  
  (def/public (default-style-name) "Standard")

  (def/public (get-default-style)
    (send s-style-list find-named-style (default-style-name)))

  ;; ----------------------------------------

  (define/public (set-max-width w) (void))
  (define/public (set-min-width v) (void))
  (define/public (get-max-width) 0.0)
  (define/public (get-min-width) 0.0)
  (define/public (set-min-height w) (void))
  (define/public (set-max-height w) (void))
  (define/public (get-min-height) 0.0)
  (define/public (get-max-height) 0.0)

  (define/public (find-first-snip) #f)

  (define/public (get-extent) (void))
  (define/public (get-descent) (void))
  (define/public (get-space) (void))

  (define/public (get-flattened-text) (void))

  ;; ----------------------------------------

  (define/public (clear) (void))
  (define/public (cut ? time) (void))
  (define/public (copy ? time) (void))
  (define/public (paste time) (void))
  (define/public (paste-x-selection time) (void))
  (define/public (kill time) (void))
  (define/public (select-all) (void))
  (define/public (insert snip) (void))
  (define/public (insert-paste-snip snip) (void))
  (define/public (insert-paste-string str) (void))
  (define/public (do-read-insert snip) (void))
  (define/public (set-caret-owner snip focus) (void))
  (define/public (read-from-file mf) #f)

  (def/public (do-edit-operation [symbol? op] [any? [recursive? #t]] [exact-integer? [time 0]])
    (if (and recursive?
             s-caret-snip)
        (send s-caret-snip do-edit-operation op #t time)
        (case op
          [(undo) (undo)]
          [(redo) (redo)]
          [(clear) (clear)]
          [(cut) (cut #f time)]
          [(copy) (copy #f time)]
          [(paste) (paste time)]
          [(kill) (kill time)]
          [(insert-text-box) (insert-box 'text)]
          [(insert-pasteboard-box) (insert-box 'pasteboard)]
          [(insert-image) (insert-image)]
          [(select-all) (select-all)])))

  (def/public (can-do-edit-operation? [symbol? op] [any? [recursive? #t]])
    (if (and recursive?
             s-caret-snip)
        (send s-caret-snip can-do-edit-operation? op #t)
        (cond
         [(and (is-locked?)
               (not (or (eq? op 'copy) (eq? op 'select-all))))
          #f]
         [(and (eq? op 'undo)
               (= changes-start changes-end))
          #f]
         [(and (eq? op 'redo)
               (= redochanges-start redochanges-end))
          #f]
         [else (really-can-edit? op)])))

  (define/public (really-can-edit?) #f)

  (def/public (insert-box [symbol? [type 'text]])
    (let ([snip (on-new-box type)])
      (when snip
        (let ([sname (default-style-name)])

          (begin-edit-sequence)
          (send snip set-s-style (or (send s-style-list find-named-style sname)
                                     (send s-style-list basic-style)))
          (insert snip)
          (set-caret-owner snip)
          (end-edit-sequence)))))

  (def/public (on-new-box [symbol? type])
    (let* ([media (if (eq? type 'text)
                      (new text%)
                      (new pasteboard%))]
           [snip (make-object editor-snip% media)])
      (send media set-keymap s-keymap)  
      (send media set-style-list s-style-list)
      snip))

  (def/public (insert-image [(make-or-false path-string?) [filename #f]]
                            [image-type? [type 'unknown/alpha]]
                            [any? [relative? #f]]
                            [any? [inline-img? #t]])
    (let ([filename (or filename
                        (get-file #f))])
      (when filename
        (let ([snip (on-new-image-snip filename type 
                                       (and relative? #t) 
                                       (and inline-img? #t))])
          (insert snip)))))

  (def/public (on-new-image-snip [path-string? filename]
                                 [image-type? type]
                                 [any? relative?]
                                 [any? inline-img?])
    (make-object image-snip% filename type relative? inline-img?))

  ;; ----------------------------------------

  (def/public (get-snip-data [snip% s]) #f)
  (def/public (set-snip-data [snip% s] [editor-data% v]) (void))
  
  ;; ----------------------------------------

  (def/public (read-header-from-file [editor-stream-in% f] [string? header-name])
    (error 'read-header-from-file "unknown header data: ~s" header-name))
  (def/public (read-footer-from-file [editor-stream-in% f] [string? header-name])
    (error 'read-header-from-file "unknown footer data: ~s" header-name))
  (def/public (write-headers-to-file [editor-stream-out% f]) #t)
  (def/public (write-footers-to-file [editor-stream-out% f]) #t)

  (def/public (begin-write-header-footer-to-file [editor-stream-out% f]
                                                 [string? header-name]
                                                 [box? data-buffer])
    (set-box! data-buffer (send f tell))
    (send f put-fixed 0)
    (send f put-unterminated (string->bytes/utf-8 header-name))
    #t)

  (def/public (end-write-header-footer-to-file [editor-stream-out% f]
                                               [exact-integer? data])
    (let ([end (send f tell)])
      (send f jump-to data)
      (send f put-fixed 0)
      (let ([pos (send f tell)])
        (send f jump-to data)
        (send f put-fixed (- end pos))
        (send f jump-to end)
        (set! num-extra-headers (add1 num-extra-headers))
        #t)))

  (def/public (read-headers-footers [editor-stream-in% f] [any? headers?])
    (let-boxes ([num-headers 0])
        (send f get-fixed num-headers)
      (for/fold ([ok? #t]) ([i (in-range num-headers)] #:when ok?)
        (let-boxes ([len 0])
            (send f get-fixed len)
          (and (send f ok?)
               (if (positive? len)
                   (let ([pos (send f tell)])
                     (send f set-boundary len)
                     (let ([header-name (bytes->string/utf-8 (send f get-unterminated-bytes) #\?)])
                       (and (if headers?
                                (read-header-from-file f header-name)
                                (read-footer-from-file f header-name))
                            (send f ok?)
                            (begin
                              (send f remove-boundary)
                              (let ([len (- len (- (send f tell) pos))])
                                (when (positive? len)
                                  (send f skip len))
                                (send f ok?))))))
                   #t))))))

  (define/public (do-write-headers-footers f headers?)
    (let ([all-start (send f tell)])
      (send f put-fixed 0)
      (set! num-extra-headers 0)

      (and
       (if headers?
           (write-headers-to-file f)
           (write-footers-to-file f))
       (begin
         (when (positive? num-extra-headers)
           (let ([all-end (send f tell)])
             (send f jump-to all-start)
             (send f put-fixed num-extra-headers)
             (send f jump-to all-end))
           #t)))))

  ;; ----------------------------------------

  (def/public (read-snips-from-file [editor-stream-in% f]
                                    [any? overwritestylename?])
    (and (read-headers-footers f #t)
         (let* ([list-id (box 0)]
                [new-list (read-styles-from-file s-style-list f overwritestylename? list-id)])
           (and new-list 
                (begin
                  (unless (eq? new-list s-style-list)
                    (set-style-list new-list))
                  (let-boxes ([num-headers 0])
                      (send f get-fixed num-headers)
                    (and
                     ;; Read headers
                     (for/and ([i (in-range num-headers)])
                       (let ([n (send f get-exact)]
                             [len (send f get-fixed-exact)])
                         (and (send f ok?)
                              (or (zero? len)
                                  (let ([sclass (send (send f get-s-scl) find-by-map-position f n)])
                                    (and
                                     (if sclass
                                         (let ([start (send f tell)])
                                           (send f set-boundary len)
                                           (and (send sclass read-header f)
                                                (send f ok?)
                                                (begin
                                                  (send f do-set-header-flag sclass)
                                                  (let ([rcount (- (send f tell) start)])
                                                    (when (rcount . < . len)
                                                      (error 'read-snips-from-file "underread (caused by file corruption?)"))
                                                    (send f skip (- len rcount)))
                                                  (send f remove-boundary)
                                                  #t)))
                                         (begin (send f skip len) #t))
                                     (send f ok?)))))))
                     ;; Read snips
                     (let-boxes ([num-snips 0])
                         (send f get num-snips)
                       (let ([accum? (this . is-a? . text%)])
                         (let ([accum
                                (for/fold ([accum null]) ([i (in-range num-snips)] #:when accum)
                                  (let ([n (send f get-exact)])
                                    (let ([sclass (if (n . >= . 0)
                                                      (send (send f get-s-scl) find-by-map-position f n)
                                                      #f)]) ; -1 => unknown
                                      (let ([len (if (or (not sclass)
                                                         (not (send sclass get-s-required?)))
                                                     (send f get-fixed-exact)
                                                     -1)])
                                        (and (send f ok?)
                                             (or (and (zero? len) accum)
                                                 (and
                                                  (if sclass
                                                      (let ([start (send f tell)])
                                                        (when (len . >= . 0)
                                                          (send f set-boundary len))
                                                        (let ([style-index (send f get-exact)])
                                                          (let ([snip (send sclass read f)])
                                                            (and 
                                                             snip
                                                             (begin
                                                               (when (has-flag? (snip->flags snip) OWNED)
                                                                 (send snip set-s-flags (remove-flag (snip->flags snip) OWNED)))
                                                               (send snip set-s-style
                                                                     (or
                                                                      (send s-style-list map-index-to-style f style-index (unbox list-id))
                                                                      (send s-style-list basic-style)))
                                                               (let* ([zero-length? (zero? (snip->count snip))]
                                                                      [accum
                                                                       (if zero-length?
                                                                           ;; A 0-length snip is a bug in the input, but 
                                                                           ;;  we continue anyway to recover from bad
                                                                           ;;  files generated by version 4.2.
                                                                           accum
                                                                           (if accum?
                                                                               (cons snip accum)
                                                                               (do-read-insert snip)))])
                                                                 (and
                                                                  accum
                                                                  (let ([data (read-buffer-data f)])
                                                                    (and
                                                                     (send f ok?)
                                                                     (let ([accum
                                                                            (if zero-length?
                                                                                accum
                                                                                (if accum?
                                                                                    (cons (cons (car accum) data) (cdr accum))
                                                                                    (when data
                                                                                      (set-snip-data snip data))))])
                                                                       (and
                                                                        accum
                                                                        (begin
                                                                          (when (len . >= . 0)
                                                                            (let ([rcount (- (send f tell) start)])
                                                                              (when (rcount . < . len)
                                                                                (error 'read-snips-from-file
                                                                                       "underread (caused by file corruption?)"))
                                                                              (send f skip (- len rcount))
                                                                              (send f remove-boundary)))
                                                                          accum))))))))))))
                                                      (begin
                                                        (send f skip len)
                                                        (and (send f ok?)
                                                             accum))))))))))])
                           (and accum
                                (begin
                                  (when accum?
                                    (let ([accum (reverse accum)])
                                      (send this do-read-insert (map car accum))
                                      (for ([p (in-list accum)])
                                        (when (cdr p)
                                          (set-snip-data (car p) (cdr p))))))
                                  
                                  (read-headers-footers f #f)))))))))))))

  ;; ----------------------------------------

  (define/public (insert-port) (void))
  (define/public (insert-file) (void))
  (define/public (save-port) (void))
  (define/public (load-file) (void))
  (define/public (set-filename) (void))
  (define/public (write-to-file) (void))

  (def/public (get-filename [(make-or-false box?) [temp #f]])
    (when temp (set-box! temp s-temp-filename?))
    s-filename)

  (define/private (extract-parent)
    (and s-admin
         ((send s-admin get-s-standard) . > . 0)
         (let ([w (send s-admin do-get-canvas)])
           (send w get-top-level))))

  (define/public (do-begin-print) (void))
  (define/public (print-to-dc) (void))
  (define/public (do-end-print) (void))
  (define/public (do-has-print-page?) (void))

  (define/private (run-printout
                   parent
                   interactive? ; currently ignored
                   fit-to-page? ; ignored
                   begin-doc-proc
                   has-page?-proc
                   print-page-proc
                   end-doc-proc)
    (let ([dc (make-object printer-dc% parent)])
      (send dc start-doc "printing")
      (begin-doc-proc dc)
      (let loop ([i 1])
        (when (has-page?-proc dc i)
          (begin
            (send dc start-page)
            (print-page-proc dc i)
            (send dc end-page)
            (loop (add1 i)))))
      (end-doc-proc)
      (send dc end-doc)))

  (def/public (print [bool? [interactive? #t]]
                     [bool? [fit-to-page? #t]]
                     [(symbol-in standard postscript pdf) [output-mode 'standard]]
                     [any? [parent #f]] ; checked in ../editor.rkt
                     [bool? [force-page-bbox? #t]]
                     [bool? [as-eps? #f]])
    (let ([ps? (or (eq? output-mode 'postscript)
                   (eq? output-mode 'pdf))]
          [parent (or parent
                      (extract-parent))])
      (cond
       [ps?
        (let* ([ps-dc% (if (eq? output-mode 'postscript) post-script-dc% pdf-dc%)]
               [dc (if as-eps?
                       ;; just for size:
                       (new ps-dc% [interactive #f] [output (open-output-nowhere)])
                       ;; actual target:
                       (make-object ps-dc% interactive? parent force-page-bbox? #f))])
          (when (send dc ok?)
            (send dc start-doc "printing buffer")
            (set! printing dc)
            (let ([data (do-begin-print dc fit-to-page?)])
              (let ([new-dc 
                     (if as-eps?
                         ;; now that we know the size, create the actual target:
                         (let ([w (box 0)]
                               [h (box 0)]
                               [sx (box 0)]
                               [sy (box 0)])
                           (get-extent w h)
                           (send (current-ps-setup) get-scaling sx sy)
                           (let ([dc (make-object ps-dc% interactive? parent force-page-bbox?
                                                  #t 
                                                  (* (unbox w) (unbox sx))
                                                  (* (unbox h) (unbox sy)))])
                             (and (send dc ok?)
                                  (send dc start-doc "printing buffer")
                                  (set! printing dc)
                                  dc)))
                         dc)])
                (when new-dc
                  (print-to-dc new-dc (if as-eps? 0 -1))
                  (when as-eps?
                    (send new-dc end-doc)))
                (set! printing #f)
                (do-end-print dc data)
                (send dc end-doc)
                (invalidate-bitmap-cache 0.0 0.0 'end 'end)))))]
       [else
        (let ([data #f])
          (run-printout ;; from wx
           parent
           interactive?
           fit-to-page?
           ;; begin-doc:
           (lambda (dc)
             (set! printing dc)
             (set! data (do-begin-print printing fit-to-page?)))
           ;; has page?:
           (lambda (dc n) (do-has-print-page? dc n))
           ;; print-page:
           (lambda (dc n) (print-to-dc dc n))
           ;; end-doc
           (lambda ()
             (let ([pr printing])
               (set! printing #f)
               (do-end-print printing data))
             (invalidate-bitmap-cache 0.0 0.0 'end 'end))))])))

  (def/public (undo)
    (when (and (not undomode?)
               (not redomode?))
      (set! undomode? #t)
      (perform-undos #f)
      (set! undomode? #f)))

  (def/public (redo)
    (when (and (not undomode?)
               (not redomode?))
      (set! redomode? #t)
      (perform-undos #t)
      (set! redomode? #f)))

  (define/private (do-clear-undos changes start end size)
    (let loop ([i start])
      (unless (= i end)
        (send (vector-ref changes i) cancel)
        (vector-set! changes i #f)
        (loop (modulo (+ i 1) size)))))

  (define/public (add-undo-rec rec)
    (cond
     [interceptmode?
      (send intercepted append rec)]
     [undomode?
      (append-undo rec #t)]
     [(zero? s-noundomode)
      (when (not redomode?)
        (cond
         [emacs-style-undo?
          (when (not (= redochanges-start redochanges-end))
            (let loop ([e redochanges-end])
              (unless (= redochanges-start e)
                (let ([e (modulo (+ e -1 redochanges-size) redochanges-size)])
                  (append-undo (send (vector-ref redochanges e) inverse) #f)
                  (loop e))))
            (let loop ()
              (unless (= redochanges-start redochanges-end)
                (append-undo (vector-ref redochanges redochanges-start) #f)
                (vector-set! redochanges redochanges-start #f)
                (set! redochanges-start (modulo (add1 redochanges-start) redochanges-size))
                (loop)))
            (set! redochanges-start 0)
            (set! redochanges-end 0))]
         [else
          (do-clear-undos redochanges redochanges-start redochanges-end redochanges-size)
          (set! redochanges-start 0)
          (set! redochanges-end 0)]))
      (append-undo rec #f)]
     [else (send rec cancel)]))

  (def/public (add-undo [(make-procedure 0) proc])
    (add-undo-rec (new proc-record% [proc proc])))

  (define/private (append-undo rec redos?)
    (if (or (eq? max-undos 'forever) (positive? max-undos))
        (let-values ([(start end size c) (get-undos redos?)])
          (let-values ([(size c) (if (zero? size)
                                     (let ([size (min 128 (if (eq? max-undos 'forever) 128 max-undos))])
                                       (values size
                                               (make-vector size #f)))
                                     (values size c))])
            (vector-set! c end rec)
            (let ([end (modulo (add1 end) size)])
              (let-values ([(start end size c)
                            (if (= end start)
                                (if (or (eq? max-undos 'forever)
                                        (size . < . max-undos)
                                        emacs-style-undo?)
                                    ;; make more room
                                    (let* ([s (min (* size 2) (if (or (eq? max-undos 'forever)
                                                                      emacs-style-undo?)
                                                                  (* size 2)
                                                                  max-undos))]
                                           [naya (make-vector s #f)])
                                      (for ([j (in-range size)])
                                        (vector-set! naya j (vector-ref c (modulo (+ start j) size))))
                                      (values 0 size s naya))
                                    ;; no room to grow, so drop an undo record
                                    (begin
                                      (send (vector-ref c start) cancel)
                                      (vector-set! c start #f)
                                      (values (modulo (add1 start) size)
                                              end
                                              size
                                              c)))
                                (values start end size c))])
                (put-undos-back redos? start end size c)))))
        (send rec cancel)))

  (define/private (get-undos redos?)
    (if redos?
        (values redochanges-start redochanges-end redochanges-size redochanges)
        (values changes-start changes-end changes-size changes)))

  (define/private (put-undos-back redos? start end size c)
    (if redos?
        (begin
          (set! redochanges-start start)
          (set! redochanges-end end)
          (set! redochanges-size size)
          (set! redochanges c))
        (begin
          (set! changes-start start)
          (set! changes-end end)
          (set! changes-size size)
          (set! changes c))))

  (def/public (begin-edit-sequence) (void))
  (def/public (end-edit-sequence) (void))
  (def/public (in-edit-sequence?) #f)
  (def/public (refresh-delayed?) #f)
  (def/public (locations-computed?) #f)

  (define/private (perform-undos redos?)
    (let ([id #f] [parity #f])
      (let-values ([(start end size c) (get-undos redos?)])
        (begin-edit-sequence)
        (let loop ([end end])
          (unless (= start end)
            (let ([end (modulo (+ end -1 size) size)])
              (let ([rec (vector-ref c end)])
                (vector-set! c end #f)
                (put-undos-back redos? start end size c)
                (when emacs-style-undo?
                  (set! id (send rec get-id))
                  (set! parity (send rec get-parity)))
                (when (send rec undo this)
                  (loop end))))))
        (end-edit-sequence)
        (when (and emacs-style-undo?
                   (not redos?))
          ;; combine all new steps into one undo record, and
          ;; set/generate id
          (let-values ([(start end size c) (get-undos #t)])
            (unless (= start end)
              (let ([cnt (let loop ([e end][cnt 0])
                           (if (= start e)
                               cnt
                               (let ([e (modulo (+ e -1 size) size)])
                                 (if (send (vector-ref c e) is-composite?)
                                     cnt
                                     (loop e (add1 cnt))))))])
                (when (positive? cnt)
                  (let ([cu (new composite-record% [count cnt] [id id] [parity? (not parity)])])
                    (for ([i (in-range cnt)])
                      (let ([e (modulo (+ (- end cnt) i size) size)])
                        (send cu add-undo i (vector-ref c e))
                        (vector-set! c e #f)))
                    (let ([e (modulo (+ (- end cnt) size) size)])
                      (vector-set! c e cu)
                      (set! redochanges-end (modulo (add1 e) size))))))))))))

  (define/public (perform-undo-list changes)
    (begin-edit-sequence)
    (let loop ([changes changes])
      (unless (null? changes)
        (when (send (car changes) undo this)
          (loop (cdr changes)))))
    (end-edit-sequence))

  (define/public (clear-undos)
    (do-clear-undos changes changes-start changes-end changes-size)
    (set! changes-start 0)
    (set! changes-end 0)
    (do-clear-undos redochanges redochanges-start redochanges-end redochanges-size)
    (set! redochanges-start 0)
    (set! redochanges-end 0))

  (def/public (set-max-undo-history [max-undo-value? v])
    (unless (or undomode?
                redomode?
                (eq? v max-undos))
      (when (equal? 0 v)
        (clear-undos)
        (set! changes #f)
        (set! redochanges #f)
        (set! changes-size 0)
        (set! redochanges-size 0))
      ;;  should we bother downsizing if max-undos gets smaller but stays
      ;; non-0?
      (set! max-undos v)))

  (def/public (get-max-undo-history) max-undos)

  (def/public (s-start-intercept)
    (set! interceptmode? #t)
    (set! intercepted null))
  
  (def/public (s-end-intercept)
    (begin0
     intercepted
     (set! interceptmode? #f)
     (set! intercepted null)))

  (define/public (undo-preserves-all-history?)
    emacs-style-undo?)
  (define/public (set-undo-preserves-all-history on?)
    (set! emacs-style-undo? (and on? #t)))
  
  ;; ----------------------------------------

  ;; see top-level functions below, at "copy ring"

  (define/public (copy-ring-next)
    (vector-set! copy-ring-buffer1 copy-ring-pos common-copy-buffer)
    (vector-set! copy-ring-buffer2 copy-ring-pos common-copy-buffer2)
    (vector-set! copy-ring-data copy-ring-pos common-copy-region-data)
    (vector-set! copy-ring-style copy-ring-pos copy-style-list)

    (set! copy-ring-pos (sub1 copy-ring-pos))
    (when (copy-ring-pos . < . 0)
      (set! copy-ring-pos (sub1 copy-ring-max)))

    (set! common-copy-buffer (vector-ref copy-ring-buffer1 copy-ring-pos))
    (set! common-copy-buffer2 (vector-ref copy-ring-buffer2 copy-ring-pos))
    (set! common-copy-region-data (vector-ref copy-ring-data copy-ring-pos))
    (set! copy-style-list (vector-ref copy-ring-style copy-ring-pos)))

  (define/public (begin-copy-buffer)
    (set! copy-depth (add1 copy-depth)))
  (define/public (end-copy-buffer)
    (set! copy-depth (sub1 copy-depth)))

  (define/public (free-old-copies)
    (when copy-style-list
      (if (copy-depth . > . 1)
          ;; delete current "ring" occupant:
          (begin
            (set! common-copy-buffer null)
            (set! common-copy-buffer2 null)
            (set! common-copy-region-data #f)
            (set! copy-style-list #f))

          (begin
            (vector-set! copy-ring-buffer1 copy-ring-pos common-copy-buffer)
            (vector-set! copy-ring-buffer2 copy-ring-pos common-copy-buffer2)
            (vector-set! copy-ring-data copy-ring-pos common-copy-region-data)
            (vector-set! copy-ring-style copy-ring-pos copy-style-list)
            
            (when (copy-ring-max . > . copy-ring-dest)
              ;; no more space: delete current ring occupant:
              (vector-set! copy-ring-buffer1 copy-ring-dest #f)
              (vector-set! copy-ring-buffer2 copy-ring-dest #f)
              (vector-set! copy-ring-data copy-ring-dest #f))

            (set! common-copy-buffer null)
            (set! common-copy-buffer2 null)
            (set! common-copy-region-data #f)
            (set! copy-style-list #f)
            (set! copy-ring-pos copy-ring-dest)

            (set! copy-ring-dest (add1 copy-ring-dest))
            (when (copy-ring-max . < . copy-ring-dest)
              (set! copy-ring-max copy-ring-dest))
            (when (copy-ring-dest . >= . copy-ring-size)
              (set! copy-ring-dest 0))))))
  
  (define/public (install-copy-buffer time sl)
    (set! copy-style-list sl)

    (when (not (= copying-self copy-depth))
      (when (or (not ALLOW-X-STYLE-SELECTION?)
                (not x-clipboard-hack?))
        (send the-clipboard set-clipboard-client the-editor-clipboard-client time))))

  (define/public (do-buffer-paste cb time local?)
    ;; cut and paste to ourself? (same eventspace?)
    (if (or local?
            (and (not paste-text-only?)
                 (send cb same-clipboard-client? the-editor-clipboard-client)
                 (send the-editor-clipboard-client same-eventspace? (current-eventspace))))
        ;; local direct copy:
        (begin
          (set! copy-depth (add1 copy-depth))
          (map (lambda (snip bd)
                 (insert-paste-snip (send snip copy) bd))
               (reverse common-copy-buffer)
               (reverse common-copy-buffer2))
          (set! copy-depth (sub1 copy-depth))
          (when (and common-copy-region-data
                     (this . is-a? . text%))
            (send this paste-region-data common-copy-region-data)))
        ;; general paste:
        (or
         (and (not paste-text-only?)
              (let ([str (send cb get-clipboard-data "WXME" time)])
                (and str
                     (let* ([b (make-object editor-stream-in-bytes-base% str)]
                            [mf (make-object editor-stream-in% b)])
                       (and (read-editor-version mf b #t #f)
                            (begin
                              (when (read-editor-global-header mf)
                                (when (send mf ok?)
                                  (when (read-from-file mf)
                                    (let ([data (read-buffer-data mf)])
                                      (and data
                                           (this . is-a? . text%)
                                           (send this paste-region-data data))))))
                              (read-editor-global-footer mf)
                              #t))))))
         (and (not paste-text-only?)
              (let ([bm (send cb get-clipboard-bitmap time)])
                (and bm
                     (begin
                       (insert-paste-snip (make-object image-snip% bm) #f)
                       #t))))
         (let ([str (send cb get-clipboard-string time)])
           ;; no data => empty string
           (insert-paste-string str)))))

  (def/public (copy-self) (void))

  (def/public (copy-self-to [editor<%> m])
    ;; copy style list
    (send (send m get-s-style-list) copy s-style-list)
    ;; copy all the snips:
    (let ([save-buffer common-copy-buffer]
          [save-buffer2 common-copy-buffer2]
          [save-styles copy-style-list]
          [save-data common-copy-region-data]
          [save-cs copying-self])
      
      (send m begin-edit-sequence)

      (set! common-copy-buffer null)
      (set! common-copy-buffer2 null)
      (set! copy-style-list #f)
      (set! common-copy-region-data #f)
      (set! copying-self (add1 copy-depth))
      
      (cond
       [(this . is-a? . text%)
        (send this copy #t 0 0 (send this last-position))]
       [(this . is-a? . pasteboard%)
        (begin-edit-sequence)
        (let ([unselect
               (let loop ([s (send this find-first-snip)])
                 (if s
                     (if (send this is-selected? s)
                         (loop (snip->next s))
                         (begin
                           (send this add-selected s)
                           (cons s (loop (snip->next s)))))
                     null))])
          (send this copy #t 0)
          (for-each (lambda (s)
                      (send this remove-selected s))
                    unselect))
        (end-edit-sequence)])

      (let ([copy-snips (reverse common-copy-buffer)]
            [copy-snips2 (reverse common-copy-buffer2)])
      
        (set! common-copy-buffer save-buffer)
        (set! common-copy-buffer2 save-buffer2)
        (set! copy-style-list save-styles)
        (set! common-copy-region-data save-data)
        (set! copying-self save-cs)

        (when (this . is-a? . text%)
          (send m do-insert-snips copy-snips 0))

        (for-each (lambda (s bfd)
                    (unless (this . is-a? . text%)
                      (send m insert s #f))
                    (when bfd
                      (send m set-snip-data s bfd)))
                  copy-snips
                  copy-snips2)

        (send m size-cache-invalid)

        (send m set-min-width (get-min-width))
        (send m set-max-width (get-max-width))
        (send m set-min-height (get-min-height))
        (send m set-max-height (get-max-height))

        (let-boxes ([temp? (box #f)]
                    [f (box #f)])
            (set-box! f (get-filename temp?))
          (send m set-filename f temp?))

        (send m set-max-undo-history (get-max-undo-history))
        
        (send m set-keymap (get-keymap))

        (send m set-inactive-caret-threshold (get-inactive-caret-threshold))
        (send m set-load-overwrites-styles (get-load-overwrites-styles))

        (send m end-edit-sequence))))
  
  ;; ----------------------------------------

  (define/public (own-x-selection) (void))

  (define/public (do-own-x-selection on? force?)
    (if on?
        (if (and (not force?)
                 (not (eq? editor-x-selection-allowed this)))
            #f
            (begin
              (when editor-x-selection-owner
                (send editor-x-selection-owner own-x-selection #f #t #f)
                (set! editor-x-selection-owner #f))
              (set! x-selection-copied? #f)
              (send the-x-selection-clipboard set-clipboard-client the-editor-x-clipboard-client 0)
              (set! editor-x-selection-owner this)
              #t))
        (begin
          (when (eq? this editor-x-selection-owner)
            (set! editor-x-selection-owner #f)
            (when (and (not x-selection-copied?)
                       (send the-x-selection-clipboard same-clipboard-client?
                             the-editor-x-clipboard-client))
              (send the-x-selection-clipboard set-clipboard-string "" 0)))
          #t)))

  (define/public (copy-out-x-selection)
    (when (eq? this editor-x-selection-owner)
      (copy-into-selection)
      (set! x-selection-copied? #t)))

  (def/public (get-paste-text-only)
    paste-text-only?)

  (def/public (set-paste-text-only [any? pto?])
    (set! paste-text-only? (and pto? #t)))

  ;; ----------------------------------------

  (def/public (lock [any? lock?])
    (set! s-user-locked? (and lock? #t)))

  (def/public (is-locked?) s-user-locked?)

  (def/public (modified?) s-modified?)

  (def/public (set-modified [any? mod?])
    (let ([mod? (and mod? #t)])
      (unless (eq? mod? s-modified?)
        (set! s-modified? mod?)
        (when mod?
          (set! num-parts-modified 1))

        (when (and (not mod?)
                   (not undomode?))
          ;; get rid of undos that reset the modification state
          (set! num-parts-modified 0)
          (let loop ([i changes-end])
            (unless (= i changes-start)
              (let ([i (modulo (+ i -1 changes-size) changes-size)])
                (send (vector-ref changes i) drop-set-unmodified)
                (loop i))))
          (let loop ([i redochanges-end])
            (unless (= i redochanges-start)
              (let ([i (modulo (+ i -1 redochanges-size) redochanges-size)])
                (send (vector-ref redochanges i) drop-set-unmodified)
                (loop i)))))

        (when s-admin
          (send s-admin modified s-modified?))
        
        (when (and (not mod?) (not undomode?))
          ;; tell all snips that they should now consider themselves unmodified:
          (let loop ([snip (find-first-snip)])
            (when snip
              (send snip set-unmodified)
              (loop (snip->next snip))))))))

  (def/public (on-snip-modified [snip% s] [any? mod?])
    (if (not mod?)
        (when (= num-parts-modified 1)
          (set! num-parts-modified 0)
          (when s-modified?
            (set-modified #f)))
        (if s-modified?
            (set! num-parts-modified (add1 num-parts-modified))
            (set-modified #t))))

  (def/public (get-inactive-caret-threshold)
    s-inactive-caret-threshold)

  (def/public (set-inactive-caret-threshold [(symbol-in no-caret show-inactive-caret show-caret) v])
    (set! s-inactive-caret-threshold v))

  (define/public (scroll-editor-to localx localy w h refresh? bias)
    (if s-admin
        (send s-admin scroll-to localx localy w h refresh? bias)
        #f))

  (def/public (refresh [real? left] [real? top] [nonnegative-real? width] [nonnegative-real? height]
                       [caret-status? show-caret]
                       [(make-or-false color%) bg-color])
    (void))

  (def/public (on-paint [any? pre?] [dc<%> dc]
                        [real? l] [real? t] [real? r] [real? b]
                        [real? dx] [real? dy]
                        [caret-status? show-caret])
    (void))

  (def/public (can-save-file? [path-string? filename]
                              [symbol? format])
    #t)

  (def/public (on-save-file [path-string? filename]
                            [symbol? format])
    (void))

  (def/public (after-save-file [any? ok?])
    (void))

  (def/public (can-load-file? [path-string? filename]
                              [symbol? format])
    #t)

  (def/public (on-load-file [path-string? filename]
                            [symbol? format])
    (void))

  (def/public (after-load-file [any? ok?])
    (void))

  (def/public (on-edit-sequence) (void))

  (def/public (after-edit-sequence) (void))

  (def/public (on-display-size) (void))

  (def/public (on-change) (void))

  (def/public (on-display-size-when-ready)
    (cond
     [(in-edit-sequence?)
      (set! s-need-on-display-size? #t)]
     [(or (not seq-lock)
          (semaphore-try-wait? seq-lock))
      (when seq-lock
        (semaphore-post seq-lock))
      (on-display-size)]
     [else (set! s-need-on-display-size? #t)]))

  (def/public (begin-sequence-lock)
    (call-with-semaphore
     global-lock
     (lambda ()
       (unless seq-lock
         (set! seq-lock (make-semaphore 1)))))
    
    ;; "Try" really should succeed, because multiple refreshes are
    ;; prevented through other flags. Still, we don't want to block if
    ;; someone previously escaped from a repaint.
    (void (semaphore-try-wait? seq-lock)))

  (def/public (end-sequence-lock)
    (semaphore-post seq-lock))

  (def/public (wait-sequence-lock)
    (cond
     [seq-lock
      (sync seq-lock)
      (lambda ()
        (semaphore-post seq-lock))]
     [else void]))

  (def/public (get-file [(make-or-false path-string?) path])
    (editor-get-file "choose a file" (extract-parent) #f path))

  (def/public (put-file [(make-or-false path-string?) dir]
                        [(make-or-false path-string?) suggested-name])
    (editor-put-file "save file as" (extract-parent) dir suggested-name))
  
  (def/public (set-load-overwrites-styles [any? b?])
    (set! loadoverwritesstyles? (and b? #t)))

  (def/public (get-load-overwrites-styles) loadoverwritesstyles?))

(define editor<%> (class->interface editor%))

;; ------------------------------------------------------------

(define/top (add-editor-keymap-functions [keymap% tab])
  (let ([add (lambda (n f)
               (send tab add-function n f))])
    (add "copy-clipboard" (lambda (e event) (send e copy #f (send event get-time-stamp))))
    (add "copy-append-clipboard" (lambda (e event) (send e copy #t (send event get-time-stamp))))
    (add "paste-clipboard" (lambda (e event) (send e paste (send event get-time-stamp))))
    (add "paste-x-selection" (lambda (e event) (send e paste-x-selection (send event get-time-stamp))))
    (add "cut-clipboard"  (lambda (e event) (send e cut #f (send event get-time-stamp))))
    (add "cut-append-clipboard" (lambda (e event) (send e cut #t (send event get-time-stamp))))
    (add "delete-to-end-of-line" (lambda (e event) (send e kill (send event get-time-stamp))))
    (add "undo" (lambda (e event) (send e undo)))
    (add "redo" (lambda (e event) (send e redo)))
    (add "delete-selection" (lambda (e event) (send e clear)))
    (add "clear-selection" (lambda (e event) (send e clear)))
    (add "select-all" (lambda (e event) (send e select-all)))))

;; ------------------------------------------------------------

(define (write-buffer-data f data)
  (let loop ([data data])
    (if data
        (let ([mp (send f do-map-position (send data get-s-dataclass))])
          (send f put mp)
          (let ([req? (send (send data get-s-dataclass) get-s-required?)])
            (let-values ([(data-start data-pos)
                          (if req?
                              (values #f #f)
                              (values (send f tell)
                                      (begin
                                        (send f put-fixed 0)
                                        (send f tell))))])
              (if (not (send data write f))
                  #f
                  (begin
                    (unless req?
                      (let ([data-end (send f tell)])
                        (send f jump-to data-start)
                        (send f put-fixed (- data-end data-pos))
                        (send f jump-to data-end)))
                    (loop (send data get-s-next)))))))
        (begin
          (send f put 0)
          #t))))

(define (write-snips-to-file f style-list snip-list
                             start-snip end-snip
                             extra-data buffer)
  (and 
   (write-styles-to-file style-list f)
   (let ([all-start (send f tell)])
     (send f put-fixed 0)

     (let ([snip-list
            (if snip-list
                (reverse snip-list)
                (let loop ([snip start-snip])
                  (if (and snip
                           (not (eq? snip end-snip)))
                      (cons snip (loop (snip->next snip)))
                      null)))])

       (let ([num-headers
              (let loop ([num-headers 0]
                         [snips snip-list])
                (if (null? snips)
                    num-headers
                    (let ([snip (car snips)])
                      (let ([sclass (snip->snipclass snip)])
                        (unless sclass
                          (error 'write-snips-to-file "snip has no snipclass"))
                        (if (not (send f do-get-header-flag sclass))
                            (begin
                              (send f put (send f do-map-position sclass))
                              (let ([header-start (send f tell)])
                                (send f put-fixed 0)
                                (let ([header-pos (send f tell)])
                                  (if (not (send sclass write-header f))
                                      #f
                                      (begin
                                        (send f do-set-header-flag sclass)
                                        (let ([header-end (send f tell)])
                                          (send f jump-to header-start)
                                          (send f put-fixed (- header-end header-pos))
                                          (send f jump-to header-end)
                                          (if (send f ok?)
                                              (loop (add1 num-headers)
                                                    (cdr snips))
                                              #f)))))))
                            (loop num-headers (cdr snips)))))))])

         (and
          num-headers
          (let ([all-end (send f tell)])
            (send f jump-to all-start)
            (send f put-fixed num-headers)
            (send f jump-to all-end)

            (send f put (length snip-list))
            
            (andmap
             (lambda (snip data)
               (let ([sclass (snip->snipclass snip)])
                 (if sclass
                     (send f put (send f do-map-position sclass))
                     (send f put -1))
                 (let-values ([(snip-start snip-pos)
                               (if (or (not sclass)
                                       (not (send sclass get-s-required?)))
                                   (values (send f tell)
                                           (begin
                                             (send f put-fixed 0)
                                             (send f tell)))
                                   (values #f #f))])
                   (let ([style-index (send style-list style-to-index (snip->style snip))])
                     (when (not style-index)
                       (error 'write-snips-to-file "bad style discovered"))
                     (send f put style-index))
                   (send snip write f)
                   (and (write-buffer-data f data)
                        (begin
                          (when snip-start
                            (let ([snip-end (send f tell)])
                              (send f jump-to snip-start)
                              (send f put-fixed (- snip-end snip-pos))
                              (send f jump-to snip-end)))
                          (send f ok?))))))
             snip-list
             (if extra-data
                 (reverse extra-data)
                 (map (lambda (snip)
                        (send buffer get-snip-data snip))
                      snip-list))))))))))

;; ------------------------------------------------------------

;; Copy and the copy ring: the current clipboard content is stored in
;; common-copy-buffer, etc. to implement the copy ring, then when a
;; copy is started, we moved the wxmb_common-copy-buffer, etc. values
;; into a copy ring. yanking from the ring swaps the values in
;;  wxmb_common-copy-buffer, etc.  and the ring values and adjust the
;; pointer into the ring.

(define copy-depth 0)

(define copy-ring-size 30)
(define copy-ring-pos 0)
(define copy-ring-max 1)
(define copy-ring-dest 1)

(define copy-ring-buffer1 (make-vector copy-ring-size #f))
(define copy-ring-buffer2 (make-vector copy-ring-size #f))

(define copy-ring-style (make-vector copy-ring-size #f))
(define copy-ring-data (make-vector copy-ring-size #f))

(define common-copy-buffer null)
(define common-copy-buffer2 null)
(define copy-style-list #f)
(define common-copy-region-data #f)

(define selection-copy-buffer #f)
(define selection-copy-buffer2 #f)
(define selection-copy-style-list #f)
(define selection-copy-region-data #f)

(define (set-common-copy-region-data! v) (set! common-copy-region-data v))
(define (cons-common-copy-buffer! v) (set! common-copy-buffer (cons v common-copy-buffer)))
(define (cons-common-copy-buffer2! v) (set! common-copy-buffer2 (cons v common-copy-buffer2)))

(define copying-self 0)

(define editor-x-selection-mode? ALLOW-X-STYLE-SELECTION?)
(define editor-x-selection-owner #f)
(define editor-x-selection-allowed #f)
(define x-selection-copied? #f)
(define x-clipboard-hack? #f)

(define (generic-get-data fformat copy-buffer copy-buffer2 copy-styles copy-region-data)
  (cond
   [(equal? fformat "TEXT")
    (string->bytes/utf-8
     (let ([out (open-output-string)])
       (for-each (lambda (snip)
                   (let ([s (send snip get-text 0 (snip->count snip) #t)])
                     (display s out)))
                 (reverse copy-buffer))
       (let ([s (get-output-string out)])
         (cond
          [(eq? 'macosx (system-type))
           ;; change newline to return
           (regexp-replace* #rx"\r" s "\n")]
          [(eq? 'windows (system-type))
           ;; change newline to return-newline:
           (regexp-replace* #rx"\n" s "\r\n")]
          [else s]))))]
   [(equal? fformat "WXME")
    (let* ([b (make-object editor-stream-out-bytes-base%)]
           [mf (make-object editor-stream-out% b)])
      (write-editor-version mf b)
      (write-editor-global-header mf)
      (and (send mf ok?)
           (begin
            (send mf put-fixed 0)
            (and (write-snips-to-file mf copy-styles copy-buffer #f #f copy-buffer2 #f)
                 (begin
                   (send mf put-fixed 0)
                   (write-buffer-data mf copy-region-data))))
           (write-editor-global-footer mf)
           (send b get-bytes)))]
   [else #""]))

(defclass editor-clipboard-client% clipboard-client%
  (inherit add-type)
  (super-new)
  (add-type "TEXT")
  (add-type "WXME")
  (define/override (get-data format)
    (generic-get-data format
                      common-copy-buffer
                      common-copy-buffer2
                      copy-style-list
                      common-copy-region-data))
  (define/override (on-replaced)
    (void)))

(defclass editor-x-clipboard-client% clipboard-client%
  (inherit add-type)
  (super-new)
  (add-type "TEXT")
  (add-type "WXME")
  (define/override (get-data format)
    (cond
     [(and (not x-selection-copied?)
           (not editor-x-selection-owner))
      ""]
     [else
      (when (or (not x-selection-copied?)
                editor-x-selection-owner)
        (copy-into-selection))
      
      ;; if nothing is copied (e.g., do-copy is overriden to not copy anything
      ;; or copies directly to clipboard):
      (if (not selection-copy-style-list)
          (if (send the-x-selection-clipboard same-clipboard-client? this)
              #f
              (send the-x-selection-clipboard get-clipboard-data format 0))
          (generic-get-data format
                            selection-copy-buffer
                            selection-copy-buffer2
                            selection-copy-style-list
                            selection-copy-region-data))]))
  (define/override (on-replaced)
    (if editor-x-selection-owner
        ;; in case this client replaced itself somewhere along the way:
        (when (not (send the-x-selection-clipboard same-clipboard-client? this))
          (let ([b editor-x-selection-owner])
            (set! editor-x-selection-owner #f)
            (set! x-selection-copied? #f)
            (send b own-x-selection #f #t #f)))
        (set! x-selection-copied? #f))))

(define the-editor-clipboard-client
  (new editor-clipboard-client%))
(define the-editor-x-clipboard-client
  (new editor-x-clipboard-client%))

(define/top (editor-set-x-selection-mode [any? on?])
  (when ALLOW-X-STYLE-SELECTION?
    (set! editor-x-selection-mode? (and on? #t))
    (when (and (not on?)
               (send the-x-selection-clipboard same-clipboard-client?
                     the-editor-x-clipboard-client))
      (send the-x-selection-clipboard set-clipboard-string "" 0))))

(define (copy-into-selection)
  ;; copy all the snips:
  (set! x-clipboard-hack? #t)
  
  ;; save normal buffers:
  (let ([save-buffer common-copy-buffer]
        [save-buffer2 common-copy-buffer2]
        [save-styles copy-style-list]
        [save-data common-copy-region-data])
    
    ;; set up new selection buffers, and redirect:
    (set! common-copy-buffer null)
    (set! common-copy-buffer2 null)
    (set! copy-style-list #f)
    (set! common-copy-region-data #f)
    
    (send editor-x-selection-owner copy #f 0)
    
    ;; move "normal" buffers to selection:
    (set! selection-copy-buffer common-copy-buffer)
    (set! selection-copy-buffer2 common-copy-buffer2)
    (set! selection-copy-style-list copy-style-list)
    (set! selection-copy-region-data common-copy-region-data)

    ;; restore normal buffers:
    (set! common-copy-buffer save-buffer)
    (set! common-copy-buffer2 save-buffer2)
    (set! copy-style-list save-styles)
    (set! common-copy-region-data save-data))
  
  (set! x-clipboard-hack? #f))

;; ------------------------------------------------------------

(define (read-buffer-data f)
  (let loop ([data #f])
    (let-boxes ([extra-data-index 0])
        (send f get extra-data-index)
      (if (zero? extra-data-index)
          data
          (let ([dclass (send (send f get-s-bdl) find-by-map-position f extra-data-index)])
            (let ([datalen (if (or (not dclass)
                                   (not (send dclass get-s-required?)))
                               (let-boxes ([datalen 0])
                                   (send f get datalen)
                                 datalen)
                               -1)])
              (if dclass
                  (let ([start (send f tell)])
                    (when (datalen . >= . 0)
                      (send f set-boundary datalen))
                    (let ([newdata (send dclass read f)])
                      (and 
                       newdata
                       (begin
                         (send newdata set-s-next data)
                         (let ([data newdata])
                           (when (datalen . >= . 0)
                             (let ([rcount (- (send f tell) start)])
                               (when (rcount . < . datalen)
                                 (error 'read-buffer-data "underread (caused by file corruption?)"))
                               (send f skip (- datalen rcount)))
                             (send f remove-boundary))
                           (and (send f ok?)
                                (loop data)))))))
                  ;; unknown extra data
                  (begin
                    (send f skip datalen)
                    (and (send f ok?)
                         (loop data))))))))))

;; ------------------------------------------------------------

(define MRED-READER-STR #"#reader(lib\"read.ss\"\"wxme\")")
(define MRED-START-STR #"WXME")
(define MRED-FORMAT-STR #"01")
(define MRED-VERSION-STR #"08")
(define MRED-VERSION-RX #rx"^0[1-8]$")

(define (write-editor-version f b)
  (send b write-bytes MRED-READER-STR)
  (send b write-bytes MRED-START-STR)
  (send b write-bytes MRED-FORMAT-STR)
  (send b write-bytes MRED-VERSION-STR)
  (send b write-bytes #" ## ")
  (not (send b bad?)))

(define MRED-READER+START-STR (bytes-append MRED-READER-STR MRED-START-STR))

(define (detect-wxme-file who f peek?)
  (let* ([l1 (bytes-length MRED-START-STR)]
         [s (if peek?
                (peek-bytes l1 0 f)
                (read-bytes l1 f))])
    (or (equal? s MRED-START-STR)
        (and (equal? s (subbytes MRED-READER-STR 0 l1))
             (let ([s (bytes-append
                       s
                       (let ([v (if peek?                           
                                    (peek-bytes (- (bytes-length MRED-READER+START-STR) l1) l1 f)
                                    (read-bytes (- (bytes-length MRED-READER+START-STR) l1) f))])
                         (if (eof-object? v)
                             ""
                             v)))])
               (equal? s MRED-READER+START-STR))))))

(define (read-editor-version mf b parse-format? [show-errors? #t])
  (and
   (or
    (not parse-format?)
    (let* ([n1 (bytes-length MRED-START-STR)]
           [vbuf (make-vector n1)])
      (let ([n (send b read vbuf)])
        (or (and (= n (vector-length vbuf))
                 (bytes=? MRED-START-STR (string->bytes/latin-1 (list->string (vector->list vbuf)))))
            ;; maybe we have a #reader... prefix?
            (let* ([n2 (bytes-length MRED-READER-STR)]
                   [vbuf2 (make-vector (- n2 n1))])
              (let ([n (send b read vbuf2)])
                (and (= n (- n2 n1))
                     (bytes=? MRED-READER-STR
                              (string->bytes/latin-1 
                               (string-append (list->string (vector->list vbuf))
                                              (list->string (vector->list vbuf2)))))
                     ;; yes, so try reading start again.
                     (let ([n (send b read vbuf)])
                       (and (= n (vector-length vbuf))
                            (bytes=? MRED-START-STR (string->bytes/latin-1 (list->string (vector->list vbuf)))))))))
            (if show-errors?
                (error (method-name 'pasteboard%: 'insert-file) "not a WXME file")
                #f)))))
   (begin
     (let* ([n1 (bytes-length MRED-FORMAT-STR)]
            [vbuf (make-vector n1)])
       (let ([n (send b read vbuf)])
         (send mf set-s-read-format (string->bytes/latin-1 (list->string (vector->list vbuf))))))
     (let* ([n1 (bytes-length MRED-VERSION-STR)]
            [vbuf (make-vector n1)])
       (let ([n (send b read vbuf)])
         (and (= n n1)
              (send mf set-s-read-version (string->bytes/latin-1 (list->string (vector->list vbuf)))))))
     (check-format-and-version mf b show-errors?))))

(define (read-editor-global-header f)
  (send (send f get-s-scl) reset-header-flags f)
  (if (not (send (send f get-s-scl) read f))
      #f
      (begin
        (setup-style-reads-writes f)
        (send (send f get-s-bdl) read f))))

(define (read-editor-global-footer f)
  (done-style-reads-writes f)
  (send (send f get-s-scl) reset-header-flags f)
  #t)

(define (write-editor-global-header f)
  (send f pretty-start)
  (send (send f get-s-scl) reset-header-flags f)
  (if (not (send (send f get-s-scl) write f))
      #f
      (begin
        (setup-style-reads-writes f)
        (send (send f get-s-bdl) write f))))

(define (write-editor-global-footer f)
  (done-style-reads-writes f)
  (send (send f get-s-scl) reset-header-flags f)
  (send f pretty-finish)
  #t)

(define (check-format-and-version s b show-errors?)
  (and
   (or (bytes=? (send s get-s-read-format) MRED-FORMAT-STR)
       (if show-errors?
           (error 'load-file "unknown format number in WXME file format: ~s"
                  (send s get-s-read-format))
           #f))
   (or (regexp-match MRED-VERSION-RX (send s get-s-read-format))
       (if show-errors?
           (error 'load-file "unknown version number in WXME file format")
           #f))
   (if ((send s get-wxme-version) . > . 3)
       ;; need to skip " ## "
       (let* ([v (make-vector 4)]
              [n (send b read v)])
         (or (and (= n 4)
                  (char=? (vector-ref v 0) #\space)
                  (char=? (vector-ref v 1) #\#)
                  (char=? (vector-ref v 2) #\#)
                  (member (vector-ref v 3) '(#\space #\return #\newline)))
             (if show-errors?
                 (error 'load-file "WXME file missing ' ## ' mark")
                 #f)))
       #t)))
