(unit/sig framework:text^
  (import mred^
	  [icon : framework:icon^]
	  [editor : framework:editor^]
	  [preferences : framework:preferences^]
	  [keymap : framework:keymap^]
          [gui-utils : framework:gui-utils^]
	  [color-model : framework:color-model^]
	  [frame : framework:frame^]
	  [mzlib:function : mzlib:function^])
  
  (rename [-keymap% keymap%])
  
  (define-struct range (start end b/w-bitmap color caret-space?))
  (define-struct rectangle (left top right bottom b/w-bitmap color))
  
  ;; wx: `default-wrapping?', add as the initial value for auto-wrap bitmap,
  ;; unless matthew makes it primitive
  
  (define basic<%>
    (interface (editor:basic<%> (class->interface text%))
      highlight-range      
      get-styles-fixed
      set-styles-fixed
      move/copy-to-edit
      initial-autowrap-bitmap))
  
  (define basic-mixin
    (mixin (editor:basic<%> (class->interface text%)) (basic<%>) args
	   (inherit get-canvases get-admin split-snip get-snip-position
		    set-autowrap-bitmap
		    delete find-snip invalidate-bitmap-cache
		    set-file-format get-file-format
		    get-style-list is-modified? change-style set-modified
		    position-location get-extent)
	   
	   (private
	     [b1 (box 0)]
	     [b2 (box 0)]
	     [b3 (box 0)]
	     [b4 (box 0)]
	     [range-rectangles null]

             [invalidate-rectangles
              (lambda (rectangles)
                (let-values
		    ([(min-left max-right)
		      (let loop ([left #f]
				 [right #f]
				 [canvases (get-canvases)])
			(cond
			 [(null? canvases)
			  (values left right)]
			 [else
			  (let-values ([(this-left this-right)
					(send (car canvases)
					      call-as-primary-owner
					      (lambda ()
						(send (get-admin) get-view b1 b2 b3 b4)
						(let* ([this-left (unbox b1)]
						       [this-width (unbox b3)]
						       [this-right (+ this-left this-width)])
						  (values this-left
							  this-right))))])
			    (if (and left right)
				(loop (min this-left left)
				      (max this-right right)
				      (cdr canvases))
				(loop this-left
				      this-right
				      (cdr canvases))))]))])
		  (when (and min-left max-right)
		    (let loop ([left #f]
			       [top #f]
			       [right #f]
			       [bottom #f]
			       [rectangles rectangles])
		      (cond
		       [(null? rectangles)
			(when left
			  (let ([width (- right left)]
				[height (- bottom top)])
			    (when (and (> width 0)
				       (> height 0))
			      (invalidate-bitmap-cache left top width height))))]
		       [else (let* ([r (car rectangles)]
				    
				    [rleft (rectangle-left r)]
				    [rright (rectangle-right r)]
				    [rtop (rectangle-top r)]
				    [rbottom (rectangle-bottom r)]
				    
				    [this-left (if (number? rleft)
						   rleft
						   min-left)]
				    [this-right (if (number? rright)
						    rright
						    max-right)]
				    [this-bottom rbottom]
				    [this-top rtop])
			       (if (and left top right bottom)
				   (loop (min this-left left)
					 (min this-top top)
					 (max this-right right)
					 (max this-bottom bottom)
					 (cdr rectangles))
				   (loop this-left 
					 this-top
					 this-right
					 this-bottom
					 (cdr rectangles))))])))))]

	     [recompute-range-rectangles
	      (lambda ()
		(let ([new-rectangles
		       (lambda (range)
			 (let* ([start (range-start range)]
				[end (range-end range)]
				[b/w-bitmap (range-b/w-bitmap range)]
				[color (range-color range)]
				[caret-space? (range-caret-space? range)]
				[start-eol? #f]
				[end-eol? (if (= start end)
					      start-eol?
					      #t)])
			   (let-values ([(start-x top-start-y)
					 (begin 
					   (position-location start b1 b2 #t start-eol? #t)
					   (values (if caret-space?
						       (+ 1 (unbox b1))
						       (unbox b1))
						   (unbox b2)))]
					[(end-x top-end-y)
					 (begin (position-location end b1 b2 #t end-eol? #t)
						(values (unbox b1) (unbox b2)))]
					[(bottom-start-y)
					 (begin (position-location start b1 b2 #f start-eol? #t)
						(unbox b2))]
					[(bottom-end-y)
					 (begin (position-location end b1 b2 #f end-eol? #t)
						(unbox b2))])
			     (cond
			       [(= top-start-y top-end-y)
				(list 
				 (make-rectangle start-x
						 top-start-y
						 (if (= end-x start-x)
						     (+ end-x 1)
						     end-x)
						 bottom-start-y
						 b/w-bitmap 
						 color))]
			       [else
				(list
				 (make-rectangle start-x
						 top-start-y
						 'right-edge
						 bottom-start-y
						 b/w-bitmap
						 color)
				 (make-rectangle 'left-edge
						 bottom-start-y
						 'max-width
						 top-end-y
						 b/w-bitmap
						 color)
				 (make-rectangle 'left-edge
						 top-end-y
						 end-x
						 bottom-end-y
						 b/w-bitmap
						 color))]))))]
		      [old-rectangles range-rectangles])
		  
		  (set! range-rectangles 
			(mzlib:function:foldl (lambda (x l) (append (new-rectangles x) l))
					      null ranges))))]
	     [ranges null]
	     [pen (make-object pen% "BLACK" 0 'solid)]
	     [brush (make-object brush% "black" 'solid)])
	   (public
	     ;; the bitmap is used in b/w and the color is used in color.
	     [highlight-range
	      (opt-lambda (start end color bitmap [caret-space? #f] [priority 'low])
		(let ([l (make-range start end bitmap color caret-space?)])
                  (unless (or (eq? priority 'high) (eq? priority 'low))
		    (error 'highlight-range "expected last argument to be either 'high or 'low, got: ~e"
                           priority))
		  (invalidate-rectangles range-rectangles)
		  (set! ranges (if (eq? priority 'high) (cons l ranges) (append ranges (list l))))
		  (recompute-range-rectangles)
		  (invalidate-rectangles range-rectangles)
		  (lambda ()
		    (let ([old-rectangles range-rectangles])
		      (set! ranges
			    (let loop ([r ranges])
			      (cond
			       [(null? r) r]
			       [else (if (eq? (car r) l)
					 (cdr r)
					 (cons (car r) (loop (cdr r))))])))
		      (recompute-range-rectangles)                    
		      (invalidate-rectangles old-rectangles)))))])
	   (rename [super-on-paint on-paint])
	   (override
	    [on-paint
	     (lambda (before dc left-margin top-margin right-margin bottom-margin dx dy draw-caret)
	       (super-on-paint before dc left-margin top-margin right-margin bottom-margin dx dy draw-caret)
               (recompute-range-rectangles)
	       (for-each
		(lambda (rectangle)
		  (let-values ([(view-x view-y view-width view-height)
				(begin 
				  (send (get-admin) get-view b1 b2 b3 b4)
				  (values (unbox b1)
					  (unbox b2)
					  (unbox b3)
					  (unbox b4)))])
		    (let* ([old-pen (send dc get-pen)]
			   [old-brush (send dc get-brush)]
			   [b/w-bitmap (rectangle-b/w-bitmap rectangle)]
			   [color (let* ([rc (rectangle-color rectangle)]
					 [tmpc (make-object color% 0 0 0)])
                                     (if rc
                                         (begin (send dc try-color rc tmpc)
                                                (if (<= (color-model:rgb-color-distance
                                                         (send rc red)
                                                         (send rc green)
                                                         (send rc blue)
                                                         (send tmpc red)
                                                         (send tmpc green)
                                                         (send tmpc blue))
                                                        18)
                                                    rc
                                                    #f))
                                         rc))]
			   [first-number (lambda (x y) (if (number? x) x y))]
			   [left (max left-margin (first-number (rectangle-left rectangle) view-x))]
			   [top (max top-margin (rectangle-top rectangle))]
			   [right (min right-margin
				       (if (number? (rectangle-right rectangle))
					   (rectangle-right rectangle)
					   (+ view-x view-width)))]
			   [bottom (min bottom-margin (rectangle-bottom rectangle))]
			   [width (max 0 (- right left))]
			   [height (max 0 (- bottom top))])
		      (let/ec k
			(cond
			  [(and before color)
			   (send pen set-color color)
			   (send brush set-color color)]
			  [(and (not before) (not color) b/w-bitmap)
			   (send pen set-stipple b/w-bitmap)
			   (send brush set-stipple b/w-bitmap)]
			  [else (k (void))])
			(send dc set-pen pen)
			(send dc set-brush brush)
			(send dc draw-rectangle 
			      (+ left dx)
			      (+ top dy)
			      width
			      height)
			(send dc set-pen old-pen)
			(send dc set-brush old-brush)))))
		range-rectangles))])
	   
	   (private
	     [styles-fixed? #f]
	     [styles-fixed-edit-modified? #f])
	   (public
	     [get-styles-fixed (lambda () styles-fixed?)]
	     [set-styles-fixed (lambda (b) (set! styles-fixed? b))])
	   (rename
	     [super-on-change-style on-change-style]
	     [super-after-change-style after-change-style]
	     [super-after-insert after-insert])
	   (override
	    [on-change-style
	     (lambda (start len)
	       (when styles-fixed?
		 (set! styles-fixed-edit-modified? (is-modified?)))
	       (super-on-change-style start len))]
	    [after-insert
	     (lambda (start len)
	       (when styles-fixed?
		 (change-style (send (get-style-list) find-named-style "Standard")
			       start
			       (+ start len)))
	       (super-after-insert start len))]
	    [after-change-style
	     (lambda (start len)
	       (super-after-change-style start len)
	       (when styles-fixed?
		 (set-modified styles-fixed-edit-modified?)))])
	   
	   (public
	     [move/copy-to-edit
	      (lambda (dest-edit start end dest-position)
		(let ([insert-edit (ivar dest-edit insert)])
		  (split-snip start)
		  (split-snip end)
		  (let loop ([snip (find-snip end 'before)])
		    (cond
		      [(or (not snip) (< (get-snip-position snip) start))
		       (void)]
		      [else
		       (let ([prev (send snip previous)]
			     [released/copied (if (send snip release-from-owner)
						  snip
						  (let* ([copy (send snip copy)]
							 [snip-start (get-snip-position snip)]
							 [snip-end (+ snip-start (send snip get-count))])
						    (delete snip-start snip-end)
						    snip))])
			 (insert-edit released/copied dest-position dest-position)
			 (loop prev))]))))])
	   
	   (public
	     [initial-autowrap-bitmap (lambda () (icon:get-autowrap-bitmap))])
	   
	   (sequence
	     (apply super-init args)
	     (set-autowrap-bitmap (initial-autowrap-bitmap)))))

  (define searching<%> (interface (editor:keymap<%> basic<%>)))
  (define searching-mixin
    (mixin (editor:keymap<%> basic<%>) (searching<%>) args
	   (inherit get-end-position get-start-position last-position 
		    find-string get-snip-position get-admin find-snip)
	   ;(rename [super-on-new-box on-new-box])
	   ;(override
	   ; [on-new-box
	   ;  (lambda (type)
	   ;    (if (eq? type 'text)
	;	   (make-object editor-snip% (make-object searching%))
	;	   (super-on-new-box)))])
	   
	   (rename [super-get-keymaps get-keymaps])
	   (override
	    [get-keymaps
	     (lambda ()
	       (cons (keymap:get-search) (super-get-keymaps)))])
	   
	   (sequence
	     (apply super-init args))))
  
  (define return<%> (interface ((class->interface text%))))
  
  (define return-mixin
    (mixin ((class->interface text%)) (return<%>) (return . args)
	   (rename [super-on-local-char on-local-char])
	   (override
	    [on-local-char
	     (lambda (key)
	       (let ([cr-code #\return]
		     [lf-code #\newline]
		     [code (send key get-key-code)])
		 (or (and (char? code)
			  (or (char=? lf-code code)
			      (char=? cr-code code))
			  (return))
		     (super-on-local-char key))))])
	   (sequence
	     (apply super-init args))))
  
  (define info<%> (interface (basic<%>)))
  
  (define info-mixin
    (mixin (editor:keymap<%> basic<%>) (info<%>) args
	   (inherit get-start-position get-end-position get-canvas
		    run-after-edit-sequence)
	   (rename [super-after-set-position after-set-position]
		   [super-after-edit-sequence after-edit-sequence]
		   [super-on-edit-sequence on-edit-sequence]
		   [super-after-insert after-insert]
		   [super-after-delete after-delete]
		   [super-set-overwrite-mode set-overwrite-mode]
		   [super-set-anchor set-anchor])
	   (private
	     [enqueue-for-frame
	      (lambda (ivar-sym tag)
		(run-after-edit-sequence
		 (rec from-enqueue-for-frame
		      (lambda ()
			(let ([canvas (get-canvas)])
			  (when canvas
			    (let ([frame (send canvas get-top-level-window)])
			      (when (is-a? frame frame:text-info<%>)
				((ivar/proc frame ivar-sym))))))))
		 tag))])
	   (override
	    [set-anchor
	     (lambda (x)
	       (super-set-anchor x)
	       (enqueue-for-frame 'anchor-status-changed
				  'framework:anchor-status-changed))]
	    [set-overwrite-mode
	     (lambda (x)
	       (super-set-overwrite-mode x)
	       (enqueue-for-frame 'overwrite-status-changed
				  'framework:overwrite-status-changed))]
	    [after-set-position
	     (lambda ()
	       (super-after-set-position)
	       (enqueue-for-frame 'editor-position-changed
				  'framework:editor-position-changed))]
	    [after-insert
	     (lambda (start len)
	       (super-after-insert start len)
	       (enqueue-for-frame 'editor-position-changed
				  'framework:editor-position-changed))]
	    [after-delete
	     (lambda (start len)
	       (super-after-delete start len)
	       (enqueue-for-frame 'editor-position-changed
				  'framework:editor-position-changed))])
	   (sequence
	     (apply super-init args))))
  
  (define clever-file-format<%> (interface ((class->interface text%))))
  
  (define clever-file-format-mixin
    (mixin ((class->interface text%)) (clever-file-format<%>) args
      (inherit get-file-format set-file-format find-first-snip)
      (rename [super-on-save-file on-save-file])
      (override
       [on-save-file
	(let ([all-string-snips 
	       (lambda ()
		 (let loop ([s (find-first-snip)])
		   (cond
		    [(not s) #t]
		    [(is-a? s string-snip%)
		     (loop (send s next))]
		    [else #f])))])
	  (lambda (name format)
	    (let ([all-strings? (all-string-snips)])
	      (cond
	       [(and all-strings?
		     (or (eq? format 'same) (eq? format 'copy))
		     (eq? 'standard (get-file-format))
		     (or (not (preferences:get 'framework:verify-change-format))
			 (gui-utils:get-choice
			  "Save this file as plain text?" "Yes" "No")))
		(set-file-format 'text)]
	       [(and (not all-strings?)
		     (or (eq? format 'same) (eq? format 'copy))
		     (eq? 'text (get-file-format))
		     (or (not (preferences:get 'framework:verify-change-format))
			 (gui-utils:get-choice
			  "Save this file in drscheme-specific non-text format?" "Yes" "No")))
		(set-file-format 'standard)]
	       [else (void)]))
	    (super-on-save-file name format)))])
      (sequence
	(apply super-init args))))
  
  (define basic% (basic-mixin (editor:basic-mixin text%)))
  (define -keymap% (editor:keymap-mixin basic%))
  (define return% (return-mixin -keymap%))
  (define autowrap% (editor:autowrap-mixin -keymap%))
  (define file% (editor:file-mixin autowrap%))
  (define clever-file-format% (clever-file-format-mixin file%))
  (define backup-autosave% (editor:backup-autosave-mixin clever-file-format%))
  (define searching% (searching-mixin backup-autosave%))
  (define info% (info-mixin (editor:info-mixin searching%))))
