
(module base mzscheme
  (require (prefix mred: mred)
	   mzlib/class
	   mzlib/file
	   mzlib/pretty
	   mzlib/etc
	   mzlib/list
	   "utils.ss")

  (define GB:SNIP-VERSION 5)
  (define MINOR-VERSION 0)

  ;; Info about the output mode:
  (define-struct output-mode (as-class? no-free-vars?))

  (define gb:snip%
    (class mred:snip% 
      (init-field [lm 5][tm 5][rm 5][bm 5])
      (inherit get-admin set-snipclass set-count)
      (private-field
	(need-recalc? #t)
	(prev-min-w 0)
	(prev-min-h 0))
      (field
       [x 0]
       [stable-x 0]
       [y 0]
       [stable-y 0]
       [w (+ lm rm)]
       [h (+ tm bm)]
       [spacing 3]
       [hilited? #f])
      (public*
       [spacing-+
	(lambda args
	  (+ (apply + args)
	     (let ([c (let loop ([l args])
			(cond
			 [(null? l) 0]
			 [(zero? (car l)) (loop (cdr l))]
			 [else (add1 (loop (cdr l)))]))])
	       (if (positive? c)
		   (* spacing (sub1 c))
		   0))))])
      (public*
       [init-horizontal-child-alignment (lambda () 2)]
       [init-vertical-child-alignment (lambda () 2)]
       [init-name (lambda () (new-name "item"))]
       [get-classname (lambda () "gb:core")]
       [container? (lambda () #t)]
       [init-y-stretch? (lambda () #t)]
       [init-x-stretch? (lambda () #t)])
      (field
       [horizontal-child-alignment (init-horizontal-child-alignment)]
       [vertical-child-alignment (init-vertical-child-alignment)]
       [with-border? #f]
       [dialog #f]
       [name (init-name)]
       [id #f]
       [original-id #f]
       [original-children-ids #f]
       (parent #f)
       (pb #f)
       (children null)
       (y-stretch? (init-y-stretch?))
       (x-stretch? (init-x-stretch?)))
      (public*
       (set-id (lambda (x) (set! id x)))
       (set-horizontal-child-alignment
	(lambda (v) (set! horizontal-child-alignment v)))
       (set-vertical-child-alignment
	(lambda (v) (set! vertical-child-alignment v)))
       (set-with-border
	(lambda (v) (set! with-border? v)))

       (get-frame%
	(lambda ()
	  (class mred:frame%
	    (init-field do-on-close)
	    (inherit show)
	    (public*
	      [get-kind (lambda () "Panel")]
	      [on-main (lambda (x) x)]
	      [find-control (lambda (tag) #f)])
	    (super-make-object (format "~a Settings" (get-kind)) #f 200 10)
	    (private-field
	      [main (on-main (make-object mred:vertical-panel% this))]
	      [name-edit (make-one-line/callback-edit main "Scheme Name:"
						      (lambda (txt)
							(set! name txt))
						      name)])
	    (field
	      [controls (make-object mred:vertical-panel% main)])
	    (augment*
	     [on-close (lambda () (do-on-close))])
	    (send controls set-alignment 'left 'center)
	    (let* ([p (make-object mred:vertical-panel% main)]
		   [make-sc
		    (lambda (name set)
		      (make-object mred:check-box% 
				   name
				   p
				   (lambda (c e)
				     (set (send c get-value))
				     (gb-need-recalc-size))))]
		   [xsc (make-sc "Allow Horizontal Stretching"
				 (lambda (on?) (set! x-stretch? on?)))]
		   [ysc (make-sc "Allow Vertical Stretching"
				 (lambda (on?) (set! y-stretch? on?)))])
	      (send p set-alignment 'left 'center)
	      (send xsc set-value x-stretch?)
	      (send ysc set-value y-stretch?)
	      (let ([p (make-object mred:vertical-panel% p)])
		(send p stretchable-height #f))))))
       
       (gb-add-child 
	(case-lambda 
	 [(c) (gb-add-child c (length children))]
	 [(c pos)
	  (set! children 
		(let loop ([l children][p pos])
		  (cond
		   [(or (zero? p) (null? l)) (cons c l)]
		   [else (cons (car l) (loop (cdr l) (sub1 p)))]))) 
	  (when pb
	    (send c gb-install pb this)
	    (send pb insert c x (+ y h)))
	  (gb-need-recalc-size)]))
       (gb-remove-child 
	(lambda (c) 
	  (set! children (remq c children))
	  (gb-need-recalc-size)))
       
       (gb-need-recalc-size
	(lambda ()
	  (set! need-recalc? #t)
	  (resized)))
       
       (gb-install
	(lambda (pb-in parent-in)
	  (set! parent parent-in)
	  (if pb
	      (when parent
		(send pb set-before this parent))
	      (set! pb pb-in))
	  (set! id (send pb new-id))
	  (for-each
	   (lambda (c)
	     (send pb insert c x (+ y h))
	     (send c gb-install pb this))
	   children)))
       
       (gb-get-child-x-start
	(lambda (mw mh w h) 
	  0))
       (gb-get-child-y-start
	(lambda (mw mh w h)
	  (if (or (= vertical-child-alignment 1)
		  (ormap (lambda (c) (gb-y-stretch? c)) children))
	      0
	      (case vertical-child-alignment
		[(2) (/ (- h mh) 2)]
		[(3) (- h mh)]))))
       (gb-combine-child-width (lambda (a b) (max a b)))
       (gb-combine-child-height (lambda (a b) (spacing-+ a b)))
       
       (gb-compute-child-x-pos
	(lambda (dc c w)
	  (if (gb-x-stretch? c)
	      0
	      (case horizontal-child-alignment
		[(2) (let-values ([(cw ch) (send c gb-get-min-size dc)])
		       (/ (- w cw) 2))]
		[(1) 0]
		[(3) (let-values ([(cw ch) (send c gb-get-min-size dc)])
		       (- w cw))]))))
       (gb-compute-child-y-pos
	(lambda (dc c h)
	  0))
       (gb-compute-child-width
	(lambda (dc c w xsc dw)
	  (if (gb-x-stretch? c)
	      w
	      (let-values ([(cw ch) (send c gb-get-min-size dc)])
		cw))))
       (gb-compute-child-height
	(lambda (dc c h ysc dh)
	  (let-values ([(cw ch) (send c gb-get-min-size dc)])
	    (if (gb-y-stretch? c)
		(+ ch (/ dh ysc))
		ch))))
       
       (gb-combine-child-x-offset (lambda (a b) a))
       (gb-combine-child-y-offset (lambda (a b) (spacing-+ a b)))
       
       (gb-get-min-size
	(lambda (dc)
	  (let loop ([lw 0][lh 0][l children])
	    (cond
	     [(null? l) (let* ([w (+ lw lm rm)]
			       [h (+ lh tm bm)])
			  (set! prev-min-h h)
			  (values w h))]
	     [else
	      (let ([c (car l)])
		(let-values ([(cw ch) (send c gb-get-min-size dc)])
		  (loop (gb-combine-child-width lw cw) 
			(gb-combine-child-height lh ch) 
			(cdr l))))]))))
       (gb-set-shape
	(lambda (dc x-in y-in w-in h-in)
	  (let*-values ([(xsc) (apply + (map 
					 (lambda (c) (if (gb-x-stretch? c) 1 0))
					 children))] 
			[(ysc) (apply + (map 
					 (lambda (c) (if (gb-y-stretch? c) 1 0))
					 children))]
			[(mw mh) (gb-get-min-size dc)]
			[(ew eh) (values (- w-in lm rm) (- h-in tm bm))]
			[(dw dh) (values (- w-in mw) (- h-in mh))])
	    (let loop ([lx (+ lm x-in (gb-get-child-x-start mw mh w-in h-in))]
		       [ly (+ tm y-in (gb-get-child-y-start mw mh w-in h-in))]
		       [l children])
	      (cond
	       [(null? l) 0]
	       [else
		(let ([c (car l)])
		  (let-values ([(cw ch)
				(send c gb-set-shape dc
				      (+ lx (gb-compute-child-x-pos dc c ew))
				      (+ ly (gb-compute-child-y-pos dc c eh))
				      (gb-compute-child-width dc c ew xsc dw)
				      (gb-compute-child-height dc c eh ysc dh))])
		    (loop (gb-combine-child-x-offset lx cw) 
			  (gb-combine-child-y-offset ly ch) 
			  (cdr l))))])))
	  (unless parent
	    (when (and pb (not (and (= w w-in) (= h h-in))))
	      (send pb top-resized this w h w-in h-in)))
	  (set! x x-in)
	  (set! y y-in)
	  (set! w w-in)
	  (set! h h-in)
	  (resized)
	  (when pb
	    (send pb move-to this x-in y-in))
	  (values w-in h-in)))
       
       (find-position-<
	(lambda (fx fy cx cy)
	  (< fy cy)))
       (gb-find-position
	(lambda (fx fy)
	  (let loop ([l children][pos 0])
	    (if (null? l)
		pos
		(let*-values ([(c) (car l)]
			      [(cx) (send c gb-get-stable-x)]
			      [(cy) (send c gb-get-stable-y)]
			      [(w h) (send c gb-get-size)])
		  (if (find-position-< fx fy (+ cx w) (+ cy h))
		      pos
		      (loop (cdr l) (add1 pos))))))))
       (gb-get-child-pos
	(lambda (c)
	  (let loop ([l children][pos 0])
	    (cond
	     [(null? l) pos]
	     [(eq? (car l) c) pos]
	     [else (loop (cdr l) (add1 pos))]))))
       
       (gb-get-saved-min-size
	(lambda ()
	  (values prev-min-w prev-min-h)))
       
       (gb-recalc-size
	(lambda (dc)
	  (if parent
	      (send parent gb-recalc-size dc)
	      (let-values ([(mw mh) (gb-get-min-size dc)]
			   [(xb) (box 0)]
			   [(yb) (box 0)])
		(when pb 
		  (send pb get-snip-location this xb yb #f)
		  (send pb get-main-location this dc xb yb))
		(gb-set-shape dc (unbox xb) (unbox yb) 
			      (if x-stretch? (max w mw) mw)
			      (if y-stretch? (max h mh) mh))))))
       
       (gb-hilite
	(lambda (on?)
	  (unless (eq? on? hilited?)
	    (set! hilited? on?)
	    (refresh))))
       
       (gb-get-parent
	(lambda () parent))
       (gb-get-children
	(lambda () children))
       (gb-get-size
	(lambda () (values w h)))
       (gb-get-x (lambda () x))
       (gb-get-y (lambda () y))
       (gb-get-stable-x (lambda () stable-x))
       (gb-get-stable-y (lambda () stable-y))
       (gb-get-position-and-size
	(lambda () (values x y w h)))
       
       (gb-set-stable-position
	(lambda ()
	  (set! stable-x x)
	  (set! stable-y y)))
       
       (gb-drag-children-along
	(lambda (new-x new-y)
	  (when (not (and (= x new-x) (= y new-y)))
	    (for-each
	     (lambda (c)
	       (let ([cx (+ new-x (- (send c gb-get-stable-x) stable-x))]
		     [cy (+ new-y (- (send c gb-get-stable-y) stable-y))])
		 (send pb move-to c cx cy)
		 (send c gb-drag-children-along cx cy)))
	     children)
	    (set! x new-x)
	    (set! y new-y))))
       
       (gb-open-dialog
	(lambda ()
	  (if dialog
	      (send dialog show #t)
	      (let ([f (make-object (get-frame%) (lambda () (set! dialog #f)))])
		(set! dialog f)
		(send f show #t)))))
       
       (gb-reconnect-to-original-children
	(lambda ()
	  (if original-children-ids
	      (let ([sl (map
			 (lambda (id) (send pb find-snip-by-original-id id))
			 original-children-ids)])
		(set! original-children-ids #f)
		(for-each
		 (lambda (s)
		   (when s
		     (gb-add-child s)
		     (send pb remove-selected s)))
		 sl)
		#t)
	      #f)))
       (gb-forget-original-id
	(lambda ()
	  ;; Make unique name
	  (let ([orig-name name])
	    (set! name #f)
	    (let loop ([new-name orig-name])
	      (if (send pb find-snip-by-name new-name)
		  (loop (string-append new-name "+"))
		  (set! name new-name))))
	  (set! original-id #f)
	  (set! original-children-ids #f)))
       
       (gb-get-instantiate-class-getter
	(lambda ()
	  `(,(string->symbol (string-append "get-" name "%")))))
       (gb-get-style
	(lambda ()
	  (if with-border?
	      '(border)
	      null)))
       (gb-local-instantiate
	(lambda (parent mode)
	  `(new ,(if (output-mode-as-class? mode)
		     (gb-get-instantiate-class-getter)
		     (gb-get-default-class))
		[parent ,parent]
		,@(gb-instantiate-arguments))))
       (gb-instantiate-arguments
	(lambda () `([style ',(gb-get-style)]
		     [stretchable-width ,x-stretch?]
		     [stretchable-height ,y-stretch?])))

       (gb-get-default-class (lambda () 'vertical-panel%))
       (gb-aux-instantiate
	(lambda (mode)
	  (if (output-mode-as-class? mode)
	      `((public* [,(string->symbol (string-append "get-" name "%"))
			  (lambda () ,(gb-get-default-class))]))
	      null)))
       (gb-instantiate
	(lambda (parent mode)
	  (let ([v (gb-local-instantiate parent mode)]
		[name (string->symbol name)])
	    `(,@(gb-aux-instantiate mode)
	      ,(if (output-mode-as-class? mode)
		   `(field [,name ,v])
		   `(define ,name ,v))
	      ,@(apply append
		       (map (lambda (c) (send c gb-instantiate name mode)) children))))))

       (draw-box
	(lambda (dc x y w h)
	  (let* ((xw (sub1 (+ x w)))
		 (yh (sub1 (+ y h)))
		 (x (add1 x))
		 (y (add1 y)))
	    (send dc draw-line x y xw y)
	    (send dc draw-line xw y xw yh)
	    (send dc draw-line x yh xw yh)
	    (send dc draw-line x y x yh))))
       
       (base-setup
	(lambda (nm xs? ys? nw nh hca vca wb? id children-ids)
	  (set! name nm)
	  (set! x-stretch? xs?)
	  (set! y-stretch? ys?)
	  (set! w nw)
	  (set! h nh)
	  (set! horizontal-child-alignment hca)
	  (set! vertical-child-alignment vca)
	  (set! with-border? wb?)
	  (set! original-id id)
	  (set! original-children-ids children-ids)))

       [get-tagged-value
	(lambda (tag) #f)]
       [set-tagged-value (lambda (t v-in) (void))]

	(refresh
	 (lambda ()
	   (let ([admin (get-admin)])
	     (when admin
	       (send admin needs-update this 0 0 w h)))))
	(resized
	 (lambda ()
	   (let ([admin (get-admin)])
	     (when admin
	       (send admin resized this #t))))))

      (override*
       (get-extent
	(lambda (dc x y wbox hbox descentbox spacebox
		    lspacebox rspacebox)
	  (when need-recalc?
	    (set! need-recalc? #f)
	    (gb-recalc-size dc))
	  (when hbox
	    (set-box! hbox h))
	  (when wbox
	    (set-box! wbox w))
	  (when descentbox
	    (set-box! descentbox 0))
	  (when spacebox
	    (set-box! spacebox 0))
	  (when rspacebox
	    (set-box! rspacebox 0))
	  (when lspacebox
	    (set-box! lspacebox 0))))
       (draw
	(lambda (dc x y . other)
	  (draw-box dc x y w h)
	  (when (or with-border? hilited?)
	    (draw-box dc (add1 x) (add1 y) (- w 2) (- h 2)))
	  (when (and with-border? hilited?)
	    (draw-box dc (+ 2 x) (+ 2 y) (- w 4) (- h 4)))))
       (copy
	(lambda ()
	  (let ([o (make-object (hash-table-get interface->class-table
						(object-interface this))
				lm tm rm bm)])
	    (send o base-setup 
		  name
		  x-stretch? y-stretch? w h 
		  horizontal-child-alignment
		  vertical-child-alignment
		  with-border?
		  (or original-id id)
		  (or original-children-ids 
		      (and (pair? children)
			   (map (lambda (child) (gb-id child)) children))))
	    o)))
       (write
	(lambda (stream)
	  (send stream put (string->bytes/utf-8 name))
	  (send stream put (if x-stretch? 1 0))
	  (send stream put (if y-stretch? 1 0))
	  (send stream put (floor (inexact->exact w)))
	  (send stream put (floor (inexact->exact h)))
	  (send stream put horizontal-child-alignment)
	  (send stream put vertical-child-alignment)
	  (send stream put (if with-border? 1 0))
	  (send stream put (string->bytes/utf-8 (if id id "BAD")))
	  (stream-write-list stream (map (lambda (c) (gb-id c)) children)))))
      (public*
       (read
	(lambda (stream version)
	  (base-setup
	   ;; name
	   (if (>= version 3) 
	       ((get-bytes->string version) (send stream get-bytes))
	       name)
	   (positive? (send stream get-exact))
	   (positive? (send stream get-exact))
	   (send stream get-exact) ; w
	   (send stream get-exact) ; h
	   (if (>= version 2) (send stream get-exact) horizontal-child-alignment) ; hca
	   (if (>= version 2) (send stream get-exact) vertical-child-alignment) ; vca
	   (if (>= version 2) (positive? (send stream get-exact)) #f) ; with-border?
	   ((get-bytes->string version) (send stream get-bytes))
	   (let ([v (stream-read-list stream version)])
	     (if (null? v) #f v))))))
      (override*
       (resize 
	(lambda (w-in h-in)
	  (if (not parent) 
	      (let-values ([(mw mh) (values prev-min-w prev-min-h)])
		(if (or (and (> w-in mw) x-stretch?)
			(and (> h-in mh) y-stretch?))
		    (begin
		      (when x-stretch? (set! w (max mw w-in)))
		      (when y-stretch? (set! h (max mh h-in)))
		      (gb-need-recalc-size)
		      #t)
		    #f))
	      #f))))
      (super-new)
      (set-snipclass (send (mred:get-the-snip-class-list) find (get-classname)))
      (set-count 1)))

  (define gb:atomic-snip%
    (class gb:snip%
      (override*
       (init-x-stretch? (lambda () #f))
       (init-y-stretch? (lambda () #f))
       (container? (lambda () #f)))
      (super-new)))
  
  (define gb-y-stretch? (class-field-accessor gb:snip% y-stretch?))
  (define gb-x-stretch? (class-field-accessor gb:snip% x-stretch?))
  (define gb-id (class-field-accessor gb:snip% id))
  (define gb-original-id (class-field-accessor gb:snip% original-id))
  (define gb-parent (class-field-accessor gb:snip% parent))
  (define gb-name (class-field-accessor gb:snip% name))
  
  (define interface->class-table (make-hash-table))

  (define register-class
    (lambda (class% classname)
      (hash-table-put!
       interface->class-table
       (class->interface class%)
       class%)
      (let ([snipclass
	     (make-object 
	      (class mred:snip-class% ()
		(inherit set-classname set-version reading-version)
		(override*
		  [read
		   (lambda (stream)
		     (let ([o (make-object class%)])
		       (send o read stream (reading-version stream))
		       o))])
		(super-new)
		(set-classname classname)
		(set-version GB:SNIP-VERSION)))])
	(send (mred:get-the-snip-class-list) add snipclass))))
  
  (register-class gb:snip% "gb:core")
  
  (provide gb:snip%
	   gb:atomic-snip%

	   gb-y-stretch?
	   gb-x-stretch?
	   gb-id
	   gb-original-id
	   gb-parent
	   gb-name

	   (struct output-mode (as-class? no-free-vars?))
	   
	   interface->class-table

	   register-class))
