#lang racket/base

(require racket/class
         racket/list
         (prefix-in wx: "kernel.rkt")
         "lock.rkt"
         "helper.rkt"
         "const.rkt"
         "wx.rkt"
         "check.rkt"
         "wxcontainer.rkt"
         "mrwindow.rkt")

(provide area-container<%>
         (protect-out internal-container<%>
                      check-container-parent
                      make-container%
                      make-subwindow%)
         area-container-window<%>
         (protect-out make-area-container-window%))

(define area-container<%>
  (interface (area<%>)
    reflow-container container-flow-modified begin-container-sequence end-container-sequence
    container-size
    get-children change-children place-children
    after-new-child
    add-child delete-child
    border spacing
    set-alignment get-alignment))

(define internal-container<%> (interface ()))

(define (check-container-parent who p)
  (unless (is-a? p internal-container<%>)
    (unless (is-a? p area-container<%>)
      (raise-argument-error (who->name who) "(is-a?/c area-container<%>)" p))
    (raise-arguments-error (who->name who) "invalid container;\n given container is not an instance of a built-in container class"
                           "given container" p)))

(define-local-member-name
  has-wx-child?
  adopt-wx-child
  forget-wx-child)

(define (make-container% %) ; % implements area<%>
  (class* % (area-container<%> internal-container<%>)
    (init mk-wx get-wx-pan get-wx-outer-pan mismatches parent
          ;; for keyword use
          [border no-val]
          [spacing no-val]
          [alignment no-val])
    (let ([cwho '(iconstructor area-container)])
      (unless (eq? border no-val) (check-margin-integer cwho border))
      (unless (eq? spacing no-val) (check-margin-integer cwho spacing))
      (unless (eq? alignment no-val)
        (unless (and (list? alignment)
                     (= 2 (length alignment))
                     (memq (car alignment) '(left center right))
                     (memq (cadr alignment) '(top center bottom)))
          (raise-argument-error (who->name cwho) "(list/c (or/c 'left center right) (or/c 'top 'center 'bottom))" alignment))))
    (define get-wx-panel get-wx-pan)
    
    (define bdr (param get-wx-panel border))
    (define spc (param get-wx-panel spacing))
    (public [bdr border] [spc spacing])
    (public*
     [after-new-child (lambda (c)
                        (check-instance '(method area-container<%> after-new-child) subarea<%> 'subarea<%> #f c)
                        (void))]
     [reflow-container (entry-point (lambda () (send (send (get-wx-panel) get-top-level) force-redraw)))]
     [container-flow-modified (entry-point (lambda ()
                                             (let ([p (get-wx-panel)])
                                               (send p need-move-children)
                                               (send p force-redraw))))]
     [begin-container-sequence (entry-point (lambda () (send (send (get-wx-panel) get-top-level) begin-container-sequence)))]
     [end-container-sequence (entry-point (lambda () (send (send (get-wx-panel) get-top-level) end-container-sequence)))]
     [get-children (entry-point (lambda () (map wx->proxy
                                                (let ([l (send (get-wx-panel) get-children)]
                                                      [h (send (get-wx-panel) get-hidden-child)])
                                                  (if h (remq h l) l)))))]
     [set-alignment (entry-point (lambda (h v) (send (get-wx-panel) alignment h v)))]
     [get-alignment (entry-point (lambda () (send (get-wx-panel) get-alignment)))]
     [change-children (entry-point
                       (lambda (f)
                         (unless (and (procedure? f)
                                      (procedure-arity-includes? f 1))
                           (raise-argument-error (who->name '(method container<%> change-children))
                                                 "(procedure-arity-includes/c 1)"
                                                 f))
                         (send (get-wx-panel) change-children
                               (lambda (kids)
                                 (let* ([hidden (send (get-wx-panel) get-hidden-child)]
                                        [mred-kids (map wx->proxy (remq hidden kids))]
                                        [l (as-exit (lambda () (f mred-kids)))])
                                   (unless (and (list? l)
                                                (andmap (lambda (x) (is-a? x internal-subarea<%>)) l))
                                     (raise-arguments-error 'change-children
                                                            "result of given procedure was not a list of subareas"
                                                            "procedure" f
                                                            "result" l))
                                   (append
                                    (if hidden (list hidden) null)
                                    (map mred->wx l)))))))]
     [container-size (entry-point
                      (lambda (l)
                                        ; Check l, even though we don't use it
                        (unless (and (list? l)
                                     (andmap
                                      (lambda (l)
                                        (and (list? l) (= (length l) 4)
                                             (integer? (car l)) (exact? (car l)) (<= 0 (car l) WIN-SIZE-MAX)
                                             (integer? (cadr l)) (exact? (cadr l)) (<= 0 (cadr l) WIN-SIZE-MAX)))
                                      l))
                          (raise-argument-error (who->name '(method area-container<%> container-size))
                                                (format
                                                 "(listof (list/c (integer-in 0 ~a) (integer-in 0 ~a) any/c any/c))"
                                                 WIN-SIZE-MAX
                                                 WIN-SIZE-MAX)
                                                l))
                        (let ([l (send (get-wx-panel) do-get-graphical-min-size)])
                          (apply values l))))]
     [place-children (entry-point (lambda (l w h) (send (get-wx-panel) do-place-children l w h)))]
     [add-child (entry-point
                 (lambda (c)
                   (check-instance '(method area-container<%> add-child) subwindow<%> 'subwindow<%> #f c)
                   (send (get-wx-panel) add-child (mred->wx c))))]
     [delete-child (entry-point
                    (lambda (c)
                      (check-instance '(method area-container<%> delete-child) subwindow<%> 'subwindow<%> #f c)
                      (send (get-wx-panel) delete-child (mred->wx c))))]
     [has-wx-child? (lambda (child-wx) ; called in atomic mode
                      (memq child-wx (send (get-wx-panel) get-children)))]
     [adopt-wx-child (lambda (child-wx) ; called in atomic mode
                       (let ([wxp (get-wx-panel)])
                         (send child-wx set-area-parent wxp)
                         (send wxp adopt-child child-wx)))])
    (super-make-object mk-wx get-wx-panel get-wx-outer-pan mismatches parent)
    (unless (eq? border no-val) (bdr border))
    (unless (eq? spacing no-val) (spc spacing))
    (unless (eq? alignment no-val) (set-alignment . alignment))))

(define area-container-window<%>
  (interface (window<%> area-container<%>)))

(define (make-area-container-window% %) ; % implements window<%> (and area-container<%>)
  (class* % (area-container-window<%>)
    (init mk-wx get-wx-pan get-wx-outer-pan mismatches label parent cursor)
    (super-make-object mk-wx get-wx-pan get-wx-outer-pan mismatches label parent cursor)))


(define (make-subwindow% %)
  (class %
    (super-new)
    (inherit set-parent
             get-parent
             is-shown?
             show)
    (define/public (reparent new-parent)
      (check-container-parent '(method subwindow<%> reparent) new-parent)
      (unless (as-entry
               (lambda ()
                 (let ([p1 (send (mred->wx this) get-top-level)]
                       [p2 (send (mred->wx new-parent) get-top-level)])
                   (eq? (send p1 get-eventspace) (send p1 get-eventspace)))))
        (raise-arguments-error
         (who->name '(method subwindow<%> reparent))
         "current parent's eventspace is not the same as the eventspace of the new parent"
         "subwindow" this
         "new parent" new-parent))
      (let loop ([p new-parent])
        (when p
          (when (eq? p this)
            (raise-arguments-error
             (who->name '(method subwindow<%> reparent))
             (if (eq? new-parent this)
                 "cannot set parent to self"
                 "cannot set parent to a descedant")
             "subwindow" this))
          (loop (send p get-parent))))
      (let* ([added? (memq this (send (get-parent) get-children))]
             [shown? (and added? (is-shown?))])
        (when added?
          (send (get-parent) delete-child this))
        (as-entry
         (lambda ()
           (let ([wx (mred->wx this)])
             ;; double-check that delete succeeded:
             (unless (send (get-parent) has-wx-child? wx)
               ;; double-check that we're not creating a loop at the wx level:
               (unless (let loop ([p (mred->wx new-parent)])
                         (and p
                              (or (eq? p wx)
                                  (loop (send p get-parent)))))
                 ;; Ok --- really reparent:
                 (send wx ensure-forgotten)
                 (send new-parent adopt-wx-child wx)
                 (set-parent new-parent))))))
        (when added?
          (send new-parent add-child this))
        (when shown?
          (show #t))))))
