(module mrtop mzscheme
  (require mzlib/class
	   mzlib/class100
	   mzlib/etc
	   mzlib/list
	   (prefix wx: "kernel.ss")
	   "lock.ss"
	   "helper.ss"
	   "const.ss"
	   "kw.ss"
	   "check.ss"
	   "wx.ss"
	   "wxtop.ss"
	   "wxpanel.ss"
	   "wxitem.ss"
	   "mrwindow.ss"
	   "mrcontainer.ss"
           "app.ss")

  (provide top-level-window<%>
	   frame%
	   dialog%
	   (protect root-menu-frame)
	   get-top-level-windows
	   get-top-level-focus-window
	   get-top-level-edit-target-window
	   send-message-to-window
	   (protect check-top-level-parent/false
		    check-frame-parent/false))

  (define top-level-window<%>
    (interface (area-container-window<%>)
      get-eventspace
      on-activate on-traverse-char on-system-menu-char
      can-close? on-close
      can-exit? on-exit
      get-focus-window get-edit-target-window
      get-focus-object get-edit-target-object
      center move resize
      on-message))

  (define-keywords top-level-window%-keywords
    window%-keywords container%-keywords area%-keywords)

  (define-local-member-name
    do-create-status-line
    do-set-status-text)

  (define basic-top-level-window%
    (class100* (make-area-container-window% (make-window% #t (make-container% area%))) (top-level-window<%>) 
	       (mk-wx mismatches label parent)
      (inherit show)
      (rename [super-set-label set-label])
      (private
	[wx-object->proxy
	 (lambda (o)
	   (if (is-a? o wx:window%)
	       (wx->proxy o)
	       o))])
      (override
	[set-label (entry-point
		    (lambda (l)
		      (check-label-string/false '(method top-level-window<%> set-label) l)
		      (send wx set-title (or l ""))
		      (super-set-label l)))])
      (public
	[on-traverse-char (entry-point
			   (lambda (e)
			     (check-instance '(method top-level-window<%> on-traverse-char)
					     wx:key-event% 'key-event% #f e)
			     (send wx handle-traverse-key e)))]
	[on-system-menu-char (entry-point
			      (lambda (e)
				(check-instance '(method top-level-window<%> on-system-menu-char) 
						wx:key-event% 'key-event% #f e)
				(and (eq? #\space (send e get-key-code))
				     (send e get-meta-down)
				     (eq? 'windows (system-type))
				     (send wx system-menu) #t)))]
	[get-eventspace (entry-point (lambda () (send wx get-eventspace)))])
      (pubment
	[can-close? (lambda () (inner #t can-close?))]
	[on-close (lambda () (inner (void) on-close))])
      (public
	[can-exit? (lambda () (can-close?))]
	[on-exit (lambda () (on-close) (show #f))]
	[on-activate (lambda (x) (void))]
	[center (entry-point
		 (case-lambda
		  [() (send wx center 'both)]
		  [(dir) (send wx center dir)]))]
	[move (entry-point
	       (lambda (x y)
		 (check-slider-integer '(method top-level-window<%> move) x)
		 (check-slider-integer '(method top-level-window<%> move) y)
		 (send wx move x y)))]
	[resize (entry-point
		 (lambda (w h)
		   (check-range-integer '(method top-level-window<%> resize) w)
		   (check-range-integer '(method top-level-window<%> resize) h)
		   (send wx set-size -11111 -11111 w h)))]

	[get-focus-window (entry-point
			   (lambda () (let ([w (send wx get-focus-window)])
					(and w (wx->proxy w)))))]
	[get-edit-target-window (entry-point
				 (lambda () (let ([w (send wx get-edit-target-window)])
					      (and w (wx->proxy w)))))]
	[get-focus-object (entry-point
			   (lambda () (let ([o (send wx get-focus-object)])
					(and o (wx-object->proxy o)))))]
	[get-edit-target-object (entry-point
				 (lambda () (let ([o (send wx get-edit-target-object)])
					      (and o (wx-object->proxy o)))))]

	[on-message (lambda (m) (void))])
      (private-field
       [wx #f]
       [mid-panel #f] ;; supports status line
       [wx-panel #f]
       [status-message #f]
       [finish (entry-point
		(lambda (top-level hide-panel?)
		  (set! mid-panel (make-object wx-vertical-panel% #f this top-level null #f))
                  (send mid-panel skip-subwindow-events? #t)
                  (send (send mid-panel area-parent) add-child mid-panel)
		  (set! wx-panel (make-object wx-vertical-panel% #f this mid-panel null #f))
                  (send wx-panel skip-subwindow-events? #t)
		  (send (send wx-panel area-parent) add-child wx-panel)
		  (send top-level set-container wx-panel)
		  (when hide-panel?
		    (send mid-panel show #f))
		  top-level))])
      (public
        [do-create-status-line (lambda ()
                                 (unless status-message
                                   (set! status-message (make-object wx-message% this this mid-panel "" -1 -1 null #f))
                                   (send status-message stretchable-in-x #t)))]
        [do-set-status-text (lambda (s)
                              (when status-message
                                (send status-message set-label s)))])
      (override
        [get-client-handle (lambda () (send wx-panel get-client-handle))])
      (sequence 
	(super-init (lambda () (set! wx (mk-wx finish)) wx) 
                    (lambda () wx-panel) (lambda () mid-panel)
                    mismatches label parent arrow-cursor))))


  (define frame%
    (class100*/kw basic-top-level-window% ()
		  [(label [parent #f] [width #f] [height #f] [x #f] [y #f] [style null])
		   top-level-window%-keywords]
      (inherit on-traverse-char on-system-menu-char
               do-create-status-line do-set-status-text)
      (sequence
	(let ([cwho '(constructor frame)])
	  (check-label-string cwho label)
	  (check-frame-parent/false cwho parent)
	  (check-dimension cwho width)
	  (check-dimension cwho height)
	  (check-init-pos-integer cwho x)
	  (check-init-pos-integer cwho y)
	  (check-style cwho #f '(no-resize-border no-caption no-system-menu 
						  mdi-parent mdi-child 
						  toolbar-button hide-menu-bar float metal) 
		       style)
	  (when (memq 'mdi-child style)
	    (when (memq 'mdi-parent style)
	      (raise-type-error (who->name cwho) 
				"style list, 'mdi-child and 'mdi-parent are mutually exclusive" 
				style)))))
      (rename [super-on-subwindow-char on-subwindow-char])
      (private-field
       [wx #f]
       [status-line? #f]
       [modified? #f])
      (override
	[on-subwindow-char (lambda (w event)
			     (super-on-subwindow-char w event)
			     (or (on-menu-char event)
				 (on-system-menu-char event)
				 (on-traverse-char event)))])
      (public
	[on-menu-char (entry-point
		       (lambda (e)
			 (check-instance '(method frame% on-menu-char) wx:key-event% 'key-event% #f e)
			 (send wx handle-menu-key e)))]
	[on-mdi-activate (lambda (on?) (void))]
	[on-toolbar-button-click (lambda () (void))]
	[create-status-line (entry-point (lambda () (unless status-line? (do-create-status-line) (set! status-line? #t))))]
	[set-status-text (lambda (s) (do-set-status-text s))]
	[has-status-line? (lambda () status-line?)]
	[iconize (entry-point (lambda (on?) (send wx iconize on?)))]
	[is-iconized? (entry-point (lambda () (send wx iconized?)))]
	[set-icon (case-lambda 
		   [(i) (send wx set-icon i)]
		   [(i b) (send wx set-icon i b)]
		   [(i b l?) (send wx set-icon i b l?)])]
	[maximize (entry-point (lambda (on?) (send wx position-for-initial-show) (send wx maximize on?)))]
        [is-maximized? (entry-point (lambda () (send wx is-maximized?)))]
	[get-menu-bar (entry-point (lambda () (let ([mb (send wx get-the-menu-bar)])
						(and mb (wx->mred mb)))))]
	[modified (entry-point
		   (case-lambda
		    [() modified?]
		    [(m) 
		     (set! modified? m)
		     (send wx set-modified m)]))])
      (sequence
	(as-entry
	 (lambda ()
	   (super-init 
	    (lambda (finish) 
	      (set! wx (finish (make-object wx-frame% this this
					    (and parent (mred->wx parent)) label
					    (or x -11111) (or y -11111)
					    (or width -1) (or height -1)
					    style)
			       (memq 'mdi-parent style)))
	      (send wx set-mdi-parent (memq 'mdi-parent style))
	      wx)
	    (lambda ()
	      (let ([cwho '(constructor frame)])
		(check-container-ready cwho parent)
		(when (memq 'mdi-child style)
		  (let ([pwx (and parent (mred->wx parent))])
		    (unless (and pwx (send pwx get-mdi-parent))
		      (raise-mismatch-error (who->name cwho) "parent for 'mdi-child frame is not an 'mdi-parent frame: " parent))))))
	    label parent))))))

  (define dialog%
    (class100*/kw basic-top-level-window% ()
		  [(label [parent #f] [width #f] [height #f] [x #f] [y #f] [style null])
		   top-level-window%-keywords]
      (inherit on-traverse-char on-system-menu-char center)
      (sequence
	(let ([cwho '(constructor dialog)])
	  (check-label-string cwho label)
	  (check-top-level-parent/false cwho parent)
	  (for-each (lambda (x) (check-dimension cwho x)) (list width height x y))
	  (check-style cwho #f '(no-caption resize-border no-sheet close-button) style)))
      (rename [super-on-subwindow-char on-subwindow-char])
      (private-field [wx #f])
      (public
        [show-without-yield (lambda () 
                              (as-entry
                               (lambda ()
                                 (send wx call-show #t (lambda () (send wx show-without-yield))))))])
      (override
	[on-subwindow-char (lambda (w event)
			     (super-on-subwindow-char w event)
			     (or (on-system-menu-char event)
				 (on-traverse-char event)))])
      (sequence
	(as-entry
	 (lambda ()
	   (super-init (lambda (finish) 
			 (set! wx (finish (make-object wx-dialog% this this
						       (and parent (mred->wx parent)) label
						       (or x -11111) (or y -11111) (or width 0) (or height 0)
						       style)
					  #f))
			 wx)
		       (lambda () 
			 (let ([cwho '(constructor dialog)])
			   (check-container-ready cwho parent)))
		       label parent))))))
  
  (define (get-top-level-windows)
    (remq root-menu-frame (map wx->mred (wx:get-top-level-windows))))

  (define (get-top-level-focus-window)
    (ormap (lambda (f) (and (send f is-act-on?) 
			    (let ([f (wx->mred f)])
			      (and f
				   (not (eq? f root-menu-frame))
				   f))))
	   (wx:get-top-level-windows)))

  (define (get-top-level-edit-target-window)
    (let loop ([l (wx:get-top-level-windows)][f #f][s 0][ms 0])
      (if (null? l)
	  f
	  (let* ([f2 (car l)]
		 [f2m (wx->mred f2)]
		 [s2 (send f2 get-act-date/seconds)]
		 [ms2 (send f2 get-act-date/milliseconds)])
	    (if (and (or (not f)
			 (> s2 s)
			 (and (= s2 s) (> ms2 ms)))
		     (not (eq? f2m root-menu-frame)))
		(loop (cdr l) f2m s2 ms2)
		(loop (cdr l) f s ms))))))

  (define (send-message-to-window x y m)
    (check-slider-integer 'send-message-to-window x)
    (check-slider-integer 'send-message-to-window y)
    (let ([w (wx:location->window x y)])
      (and w (let ([f (wx->proxy w)])
	       (and f 
		    (not (eq? f root-menu-frame))
		    (send f on-message m))))))

  (define (check-top-level-parent/false who p)
    (unless (or (not p) (is-a? p frame%) (is-a? p dialog%))
      (raise-type-error (who->name who) "frame% or dialog% object or #f" p)))

  (define (check-frame-parent/false who p)
    (unless (or (not p) (is-a? p frame%))
      (raise-type-error (who->name who) "frame% object or #f" p)))

  (define root-menu-frame
    (and (current-eventspace-has-menu-root?)
         ;; The very first frame shown is somehow sticky under Cocoa,
         ;;  so create the root frame, show it , and hide it.
         (let* ([f (make-object (class frame%
                                  (define/override (on-exit)
                                    (exit))
                                  (super-make-object "Root" #f 0 0 -9000 -9000
                                                     '(no-resize-border no-caption))))]
                [wx (mred->wx f)])
           (set-root-menu-wx-frame! wx)
           (send wx designate-root-frame)
           f))))
