#lang racket/base
  (require mzlib/class
           mzlib/class100
           mzlib/string
           (prefix-in wx: "kernel.rkt")
           "const.rkt"
           "check.rkt"
           "helper.rkt"
           "editor.rkt"
           "mrtop.rkt"
           "mrcanvas.rkt"
           "mrpopup.rkt"
           "mrmenu.rkt"
           "mritem.rkt"
           "mrpanel.rkt")

  (provide message-box
	   message-box/custom
	   message+check-box
	   message+check-box/custom)

  (define do-message-box/custom
    (lambda (who title message
		 button1 button2 button3
		 parent style close-result
		 check? two-results? check-message
                 dialog-mixin)
      (check-label-string who title)
      (check-string/false who message)
      (when check?
	(check-label-string who check-message))
      (check-label-string-or-bitmap/false who button1)
      (check-label-string-or-bitmap/false who button2)
      (check-label-string-or-bitmap/false who button3)
      (check-top-level-parent/false who parent)
      (check-style who 
		   '(default=1 default=2 default=3 no-default) 
		   (let ([l '(disallow-close number-order caution stop)])
		     (if check?
			 (cons 'checked l)
			 l))
		   style)

      (let ([go (lambda ()
                  (create-message-box/custom
                   who title message
                   button1 button2 button3
                   parent style close-result
                   check? two-results? check-message
                   dialog-mixin))]
	    [es (if parent
		    (send parent get-eventspace)
		    (wx:current-eventspace))])
        (if (eq? (current-thread) (wx:eventspace-handler-thread es))
            ;; In the right thread:
            (go)
            ;; Not in the right thread:
            (let ([ch (make-channel)])
	      (parameterize ([wx:current-eventspace es])
                (wx:queue-callback
                 (lambda ()
                   (channel-put ch (call-with-values go list)))))
              (apply values (channel-get ch)))))))

  (define create-message-box/custom
    (lambda (who title message
		 button1 button2 button3
		 parent style close-result
		 check? two-results? check-message 
                 dialog-mixin)
      (let* ([strings (regexp-split #rx"\n" message)]
	     [single? (and (< (length strings) 10) 
			   (andmap (lambda (s) (< (string-length s) 60)) strings))]
             [f (make-object (dialog-mixin
                              (class100 dialog% ()
                                (public
                                  [get-message
                                   (lambda () message)])
                                (augment
                                 [can-close? (lambda ()
                                               (if (memq 'disallow-close style)
                                                   (begin
                                                     (wx:bell)
                                                     #f)
                                                   #t))])
                                (override
                                  [on-subwindow-event 
                                   (lambda (w e)
                                     (if (send e button-down?)
                                         (if (is-a? w button%)
                                             #f
                                             (if (or (is-a? w message%)
                                                     (and
                                                      (is-a? w editor-canvas%)
                                                      (let-values ([(w h) (send w get-client-size)])
                                                        (< (send e get-x) w))))
                                                 (begin
                                                   (send w popup-menu
                                                         (let ([m (make-object popup-menu%)])
                                                           (make-object menu-item%
                                                             "Copy Message"
                                                             m
                                                             (lambda (i e)
                                                               (send (wx:get-the-clipboard)
                                                                     set-clipboard-string
                                                                     message
                                                                     (send e get-time-stamp))))
                                                           m)
                                                         (send e get-x)
                                                         (send e get-y))
                                                   #t)
                                                 #f))
                                         #f))])
                                (sequence
                                  (super-init title parent box-width)))))]
	     [result close-result]
	     [icon-id (cond
		       [(memq 'stop style) 'stop]
		       [(memq 'caution style) 'caution]
		       [else 'app])])
	(let-values ([(msg-pnl btn-pnl cb-pnl extra-width btn-h-align msg-h-align msg-v-align)
		      (case (system-type)
			[(macosx) (let ([p (make-object horizontal-pane% f)])
				    (send f min-width 300)
				    (send p set-alignment 'center 'top)
				    (let ([m (make-object message% icon-id p)])
				      (send m horiz-margin 16)
				      (send m vert-margin 16))
				    (let* ([rhs-pnl (make-object vertical-pane% p)]
					   [msg-pnl (make-object vertical-pane% rhs-pnl)]
					   [btn-pnl (make-object vertical-pane% rhs-pnl)])
				      (send msg-pnl vert-margin 16)
				      (when single?
					(send msg-pnl horiz-margin 8))
				      (send btn-pnl vert-margin 8)
				      (send msg-pnl min-height 40)
				      (send msg-pnl min-width 300)
				      (send btn-pnl stretchable-height #f)
				      (values msg-pnl btn-pnl btn-pnl 96 'right 'left 'top)))]
			[else (let ([p (new horizontal-pane% [parent f] [alignment '(center top)])])
				(let ([icon-msg (make-object message% icon-id p)]
				      [msg-pnl (new vertical-pane% [parent p])])
				  (values (if (= 1 (length strings))
					      (new horizontal-pane% 
						   [parent msg-pnl]
						   [alignment '(center top)]
						   [min-height (send icon-msg min-height)])
					      msg-pnl)
					  f msg-pnl 0 'center 'center 'center)))])])
	  (if single?
	      (begin
		(send msg-pnl set-alignment (if (= (length strings) 1) msg-h-align 'left) msg-v-align)
		(for-each (lambda (s) (make-object message% (protect& s) msg-pnl)) strings)
		(send f stretchable-width #f)
		(send f stretchable-height #f))
	      ;; Try without scrollbar, then add one if necessary:
	      (let loop ([scroll? #f])
		(let* ([e (make-object text%)]
		       [c (make-object editor-canvas% msg-pnl e (if scroll?
								    '(no-hscroll)
								    '(no-hscroll no-vscroll transparent no-border)))])
		  (send c min-width 400)
		  (send c set-line-count 5)
		  (send c allow-tab-exit #t)
		  (send f reflow-container)
		  (send e auto-wrap #t)
		  (send e insert message)
		  (send e set-position 0)
		  (send e hide-caret #t)
		  (send e set-cursor (make-object wx:cursor% 'arrow) #t)
		  (send e lock #t)
		  (when (not scroll?)
		    ;; Check whether it actually fits
		    (let ([vh (box 0)]
			  [eh (box 0)])
		      (send e get-view-size #f vh)
		      (send e get-extent #f eh)
		      (unless ((unbox eh) . <= . (unbox vh))
			(send c show #f)
			(send msg-pnl delete-child c)
			(loop #t)))))))
	  (let ([check (and check?
			    (let ([p (new vertical-pane% [parent cb-pnl]
					  [stretchable-height #f]
					  [alignment '(left center)])])
			      (when (and single?
					 (eq? 'macosx (system-type)))
				;; Match text-panel margin:
				(send p horiz-margin 8))
			      (new check-box% 
				   [label check-message]
				   [parent p]
				   [callback void]
				   [value (memq 'checked style)])))])
	    (let* ([p (make-object horizontal-pane% btn-pnl)]
		   [mk-button (lambda (title v default?) 
				(let ([b (make-object button% title p (lambda (b e) (set! result v) (send f show #f))
						      (if default? '(border) null))])
				  (when default? (send b focus))))])
	      (send p set-alignment btn-h-align 'center)
	      (send p stretchable-height #f)
	      (send p stretchable-width #t) ; to get panel's centering
	      (let ([mk-1 (lambda () 
			    (when button1
			      (mk-button button1 1 (memq 'default=1 style))))]
		    [mk-2 (lambda ()
			    (when button2
			      (mk-button button2 2 (memq 'default=2 style))))]
		    [mk-3 (lambda ()
			    (when button3
			      (mk-button button3 3 (memq 'default=3 style))))])
		(cond 
		 [(or (memq 'number-order style)
		      (memq (system-type) '(windows)))
		  (mk-1)
		  (mk-2)
		  (mk-3)]
		 [else
		  (mk-3)
		  (make-object horizontal-pane% p)
		  (mk-2)
		  (mk-1)])))
	    (send f center)
	    (send f show #t)
	    (if two-results?
		(values result (and check? (send check get-value)))
		result))))))

  (define message-box/custom
    (lambda (title message 
                   button1
                   button2
                   button3
                   [parent #f]
                   [style '(no-default)]
                   [close-result #f]
                   #:dialog-mixin [dialog-mixin values])
      (do-message-box/custom 'message-box/custom
			     title message button1 button2 button3
			     parent style close-result
			     #f #f #f dialog-mixin)))

  (define do-message-box
    (lambda (who title message parent style check? check-message dialog-mixin)
      (check-label-string who title)
      (check-string/false who message)
      (when check?
	(check-label-string who check-message))
      (check-top-level-parent/false who parent)
      (check-style who
		   '(ok ok-cancel yes-no) 
		   (let ([l '(caution stop)])
		     (if check?
			 (cons 'checked l)
			 l))
		   style)

      (let-values ([(one two one-v two-v close-val default)
		    (cond
		     [(memq 'ok style) 
		      (values "OK" #f 'ok #f 1 'default=1)]
		     [(memq 'ok-cancel style)
		      (values "OK" "Cancel" 'ok 'cancel 2 'default=1)]
		     [(memq 'yes-no style)
		      (values "&Yes" "&No" 'yes 'no #f 'no-default)])])
	(let-values ([(result checked?)
		      (do-message-box/custom who
					     title message
					     one two #f
					     parent
					     (append
					      (cond
					       [(memq 'checked style) '(checked)]
					       [else null])
					      (cond
					       [(memq 'stop style) '(stop)]
					       [(memq 'caution style) '(caution)]
					       [else null])
					      (if close-val
						  (list default)
						  (list default 'disallow-close)))
					     close-val
					     check? #t check-message
                                             dialog-mixin)])
	  (let ([result (case result
			  [(1) one-v]
			  [(2) two-v])])
	    (if check?
		(values result checked?)
		result))))))

  (define message-box
    (lambda (title message [parent #f] [style '(ok)] #:dialog-mixin [dialog-mixin values])
      (do-message-box 'message-box title message parent style #f #f dialog-mixin)))

  (define message+check-box/custom
    (lambda (title message 
		       checkbox-message
		       button1
		       button2
		       button3
		       [parent #f]
		       [style '(no-default)]
		       [close-result #f]
                       #:dialog-mixin [dialog-mixin values])
      (do-message-box/custom 'message+check-box/custom
			     title message button1 button2 button3
                             parent style close-result
			     #t #t checkbox-message
                             dialog-mixin)))

  (define message+check-box
    (lambda (title message check-message [parent #f] [style '(ok)] #:dialog-mixin [dialog-mixin values])
      (do-message-box 'message-box title message parent style #t check-message dialog-mixin)))
