#lang scheme/unit

  (require mzlib/class
           "sig.ss"
           mred/mred-sig
           mzlib/list
           mzlib/etc)
  
  (import [prefix icon: framework:icon^]
          mred^)
  (export framework:panel^)
  (init-depend mred^)
  
  (define single<%> (interface (area-container<%>) active-child))
  (define single-mixin
    (mixin (area-container<%>) (single<%>)
      (inherit get-alignment change-children)
      (define/override (after-new-child c)
        (unless (is-a? c window<%>)
          
          ;; would like to remove the child here, waiting on a PR submitted
          ;; about change-children during after-new-child
          (change-children
           (λ (l)
             (remq c l)))
          
          (error 'single-mixin::after-new-child
                 "all children must implement window<%>, got ~e"
                 c))
        (if current-active-child
            (send c show #f)
            (set! current-active-child c)))
      [define/override (container-size l)
        (if (null? l)
            (values 0 0)
            (values (apply max (map car l)) (apply max (map cadr l))))]
      [define/override (place-children l width height)
        (let-values ([(h-align-spec v-align-spec) (get-alignment)])
          (let ([align
                 (λ (total-size spec item-size)
                   (floor
                    (case spec
                      [(center) (- (/ total-size 2) (/ item-size 2))]
                      [(left top) 0]
                      [(right bottom) (- total-size item-size)]
                      [else (error 'place-children
                                   "alignment spec is unknown ~a\n" spec)])))])
            (map (λ (l) 
                   (let*-values ([(min-width min-height h-stretch? v-stretch?)
                                  (apply values l)]
                                 [(x this-width)
                                  (if h-stretch?
                                      (values 0 width)
                                      (values (align width h-align-spec min-width)
                                              min-width))]
                                 [(y this-height)
                                  (if v-stretch?
                                      (values 0 height)
                                      (values (align height v-align-spec min-height)
                                              min-height))])
                     (list x y this-width this-height)))
                 l)))]
      
      (inherit get-children begin-container-sequence end-container-sequence)
      [define current-active-child #f]
      (define/public active-child
        (case-lambda
          [() current-active-child]
          [(x) 
           (unless (memq x (get-children))
             (error 'active-child "got a panel that is not a child: ~e" x))
           (unless (eq? x current-active-child)
             (begin-container-sequence)
             (for-each (λ (x) (send x show #f))
                       (get-children))
             (set! current-active-child x)
             (send current-active-child show #t)
             (end-container-sequence))]))
      (super-instantiate ())))
  
  (define single-window<%> (interface (single<%> window<%>)))
  (define single-window-mixin
    (mixin (single<%> window<%>) (single-window<%>)
      (inherit get-client-size get-size)
      [define/override container-size
        (λ (l)
          (let-values ([(super-width super-height) (super container-size l)]
                       [(client-width client-height) (get-client-size)]
                       [(window-width window-height) (get-size)]
                       [(calc-size)
                        (λ (super client window)
                          (+ super (max 0 (- window client))))])
            
            (values
             (calc-size super-width client-width window-width)
             (calc-size super-height client-height window-height))))]
      (super-new)))
  
  (define multi-view<%>
    (interface (area-container<%>)
      split-vertically
      split-horizontally
      collapse))
  
  (define multi-view-mixin
    (mixin (area-container<%>) (multi-view<%>) 
      (init-field parent editor)
      (public get-editor-canvas% get-vertical% get-horizontal%)
      [define get-editor-canvas%
        (λ ()
          editor-canvas%)]
      [define get-vertical%
        (λ ()
          vertical-panel%)]
      [define get-horizontal%
        (λ ()
          horizontal-panel%)]
      
      (define/private (split p%)
        (let ([canvas (send (send parent get-top-level-window) get-edit-target-window)]
              [ec% (get-editor-canvas%)])
          (when (and canvas
                     (is-a? canvas ec%)
                     (eq? (send canvas get-editor) editor))
            (let ([p (send canvas get-parent)])
              (send p change-children (λ (x) null))
              (let ([pc (make-object p% p)])
                (send (make-object ec% (make-object vertical-panel% pc) editor) focus)
                (make-object ec% (make-object vertical-panel% pc) editor))))))
      [define/public split-vertically
        (λ ()
          (split (get-vertical%)))]
      [define/public split-horizontally
        (λ ()
          (split (get-horizontal%)))]
      
      (define/public (collapse)
        (let ([canvas (send (send parent get-top-level-window) get-edit-target-window)]
              [ec% (get-editor-canvas%)])
          (when (and canvas
                     (is-a? canvas ec%)
                     (eq? (send canvas get-editor) editor))
            (let ([p (send canvas get-parent)])
              (if (eq? p this)
                  (bell)
                  (let* ([sp (send p get-parent)]
                         [p-to-remain (send sp get-parent)])
                    (send p-to-remain change-children (λ (x) null))
                    (send (make-object ec% p-to-remain editor) focus)))))))
      
      
      (super-instantiate () (parent parent))
      (make-object (get-editor-canvas%) this editor)))
  
  (define single% (single-window-mixin (single-mixin panel%)))
  (define single-pane% (single-mixin pane%))
  (define multi-view% (multi-view-mixin vertical-panel%))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
  ;; type gap = (make-gap number area<%> percentage number area<%> percentage)
  (define-struct gap (before before-dim before-percentage after after-dim after-percentage))
  
  ;; type percentage : (make-percentage number)
  (define-struct percentage (%) #:mutable)
  
  (define dragable<%>
    (interface (window<%> area-container<%>)
      after-percentage-change
      set-percentages
      get-percentages
      get-vertical?))
  
  (define vertical-dragable<%>
    (interface (dragable<%>)))
  
  (define horizontal-dragable<%>
    (interface (dragable<%>)))
  
  (define dragable-mixin
    (mixin (window<%> area-container<%>) (dragable<%>)
      (init parent)
      
      (define/public (get-vertical?)
        (error 'get-vertical "abstract method"))
      (define/private (min-extent child) 
        (let-values ([(w h) (send child get-graphical-min-size)])
          (if (get-vertical?)
              (max (send child min-height) h)
              (max (send child min-width) w))))
      (define/private (event-get-dim evt)
        (if (get-vertical?)
            (send evt get-y)
            (send evt get-x)))
      (define/private (get-gap-cursor)
        (if (get-vertical?)
            (icon:get-up/down-cursor)
            (icon:get-left/right-cursor)))
      
      (inherit get-client-size container-flow-modified)
      
      (init-field [bar-thickness 5])
      
      ;; percentages : (listof percentage)
      (define percentages null)
      
      ;; get-percentages : -> (listof number)
      (define/public (get-percentages)
        (map percentage-% percentages))
      
      (define/public (set-percentages ps)
        (unless (and (list? ps)
                     (andmap number? ps)
                     (= 1 (apply + ps))
                     (andmap positive? ps))
          (error 'set-percentages 
                 "expected a list of numbers that are all positive and sum to 1, got: ~e"
                 ps))
        (unless (= (length ps) (length (get-children)))
          (error 'set-percentages 
                 "expected a list of numbers whose length is the number of children: ~a, got ~e"
                 (length (get-children))
                 ps))
        (set! percentages (map make-percentage ps))
        (container-flow-modified))
      
      (define/pubment (after-percentage-change) (inner (void) after-percentage-change))
      
      (define/private (get-available-extent)
        (let-values ([(width height) (get-client-size)])
          (- (if (get-vertical?) height width)
             (* bar-thickness (- (length (get-children)) 1)))))
      
      (inherit get-children)
      
      (define/private (update-percentages)
        (let ([len-children (length (get-children))])
          (unless (= len-children (length percentages))
            (cond
              [(zero? len-children)
               '()]
              [else
               (let ([rat (/ 1 len-children)])
                 (set! percentages (build-list len-children (λ (i) (make-percentage rat)))))])
            (after-percentage-change))))
      
      (define/override (after-new-child child)
        (update-percentages))
      
      (define resizing-dim #f)
      (define resizing-gap #f)
      
      (inherit set-cursor)
      (define/override (on-subwindow-event receiver evt)
        (if (eq? receiver this)
            (let ([gap
                   (ormap (λ (gap) 
                            (and (<= (gap-before-dim gap) 
                                     (event-get-dim evt)
                                     (gap-after-dim gap))
                                 gap))
                          cursor-gaps)])
              (set-cursor (and (or gap
                                   resizing-dim)
                               (let ([c (get-gap-cursor)])
                                 (and (send c ok?)
                                      c))))
              (cond
                [(and gap (send evt button-down? 'left))
                 (set! resizing-dim (event-get-dim evt))
                 (set! resizing-gap gap)]
                [(send evt button-up? 'left)
                 (set! resizing-dim #f)
                 (set! resizing-gap #f)]
                [(and resizing-dim resizing-gap (send evt moving?))
                 (let-values ([(width height) (get-client-size)])
                   (let* ([before-percentage (gap-before-percentage resizing-gap)]
                          [orig-before (percentage-% before-percentage)]
                          [after-percentage (gap-after-percentage resizing-gap)]
                          [orig-after (percentage-% after-percentage)]
                          [available-extent (get-available-extent)]
                          [change-in-percentage (/ (- resizing-dim (event-get-dim evt)) available-extent)]
                          [new-before (- (percentage-% before-percentage) change-in-percentage)]
                          [new-after (+ (percentage-% after-percentage) change-in-percentage)])
                     (when ((floor (* new-before available-extent)) . > . (min-extent (gap-before resizing-gap)))
                       (when ((floor (* new-after available-extent)) . > . (min-extent (gap-after resizing-gap)))
                         (set-percentage-%! before-percentage new-before)
                         (set-percentage-%! after-percentage new-after)
                         (after-percentage-change)
                         (set! resizing-dim (event-get-dim evt))
                         (container-flow-modified)))))]
                [else (super on-subwindow-event receiver evt)]))
            (begin
              (set-cursor #f)
              (super on-subwindow-event receiver evt))))
      
      (define cursor-gaps null)
      
      (define/override (place-children _infos width height)
        (set! cursor-gaps null)
        (update-percentages)
        (cond
          [(null? _infos) null]
          [(null? (cdr _infos)) (list (list 0 0 width height))]
          [else
           (let ([available-extent (get-available-extent)]
                 [show-error
                  (λ (n)
                    (error 'panel.ss::dragable-panel "internal error.~a" n))])
             (let loop ([percentages percentages]
                        [children (get-children)]
                        [infos _infos]
                        [dim 0])
               (cond
                 [(null? percentages)
                  (unless (null? infos) (show-error 1))
                  (unless (null? children) (show-error 2))
                  null]
                 [(null? (cdr percentages))
                  (when (null? infos) (show-error 3))
                  (when (null? children) (show-error 4))
                  (unless (null? (cdr infos)) (show-error 5))
                  (unless (null? (cdr children)) (show-error 6))
                  (if (get-vertical?)
                      (list (list 0 dim width (- height dim)))
                      (list (list dim 0 (- width dim) height)))]
                 [else
                  (when (null? infos) (show-error 7))
                  (when (null? children) (show-error 8))
                  (when (null? (cdr infos)) (show-error 9))
                  (when (null? (cdr children)) (show-error 10))
                  (let* ([info (car infos)]
                         [percentage (car percentages)]
                         [this-space (floor (* (percentage-% percentage) available-extent))])
                    (set! cursor-gaps (cons (make-gap (car children)
                                                      (+ dim this-space)
                                                      percentage
                                                      (cadr children)
                                                      (+ dim this-space bar-thickness)
                                                      (cadr percentages))
                                            cursor-gaps))
                    (cons (if (get-vertical?)
                              (list 0 dim width this-space)
                              (list dim 0 this-space height))
                          (loop (cdr percentages)
                                (cdr children)
                                (cdr infos)
                                (+ dim this-space bar-thickness))))])))]))
      
      (define/override (container-size children-info)
        (update-percentages)
        (let loop ([percentages percentages]
                   [children-info children-info]
                   [major-size 0]
                   [minor-size 0])
          (cond
            [(null? children-info)
             (if (get-vertical?)
                 (values (ceiling minor-size) (ceiling major-size))
                 (values (ceiling major-size) (ceiling minor-size)))]
            [(null? percentages)
             (error 'panel.ss::dragable-panel "internal error.12")]
            [else
             (let ([child-info (car children-info)]
                   [percentage (car percentages)])
               (let-values ([(child-major major-stretch? child-minor minor-stretch?)
                             (if (get-vertical?)
                                 (values (list-ref child-info 1)
                                         (list-ref child-info 3)
                                         (list-ref child-info 0)
                                         (list-ref child-info 2))
                                 (values (list-ref child-info 0)
                                         (list-ref child-info 2)
                                         (list-ref child-info 1)
                                         (list-ref child-info 3)))])
                 (loop (cdr percentages)
                       (cdr children-info)
                       (max (if (zero? (percentage-% percentage))
                                0
                                (/ child-major (percentage-% percentage)))
                            major-size)
                       (max child-minor minor-size))))])))
      
      (super-instantiate (parent))))
  
  (define three-bar-pen-bar-width 8)
  
  (define three-bar-canvas%
    (class canvas%
      (inherit get-dc get-client-size)
      (define/override (on-paint)
        (let ([dc (get-dc)])
          (let-values ([(w h) (get-client-size)])
            (let ([sx (floor (- (/ w 2) (/ three-bar-pen-bar-width 2)))])
              (send dc set-brush (send the-brush-list find-or-create-brush (get-panel-background) 'panel))
              (send dc set-pen (send the-pen-list find-or-create-pen "black" 1 'transparent))
              (send dc draw-rectangle 0 0 w h)
              
              (send dc set-pen (send the-pen-list find-or-create-pen  "black" 1 'solid))
              (send dc draw-line sx 1 (+ sx three-bar-pen-bar-width) 1)
              (send dc draw-line sx 4 (+ sx three-bar-pen-bar-width) 4)
              (send dc draw-line sx 7 (+ sx three-bar-pen-bar-width) 7)
              
              (send dc set-pen (send the-pen-list find-or-create-pen  "gray" 1 'solid))
              (send dc draw-line sx 2 (+ sx three-bar-pen-bar-width) 2)
              (send dc draw-line sx 5 (+ sx three-bar-pen-bar-width) 5)
              (send dc draw-line sx 8 (+ sx three-bar-pen-bar-width) 8)))))
      
      (super-instantiate ())
      (inherit stretchable-height min-height)
      (stretchable-height #f)
      (min-height 10)))
  
  
  (define vertical-dragable-mixin
    (mixin (dragable<%>) (vertical-dragable<%>)
      (define/override (get-vertical?) #t)
      (super-instantiate ())))
  
  (define horizontal-dragable-mixin
    (mixin (dragable<%>) (vertical-dragable<%>)
      (define/override (get-vertical?) #f)
      (super-instantiate ())))
  
  (define vertical-dragable% (vertical-dragable-mixin (dragable-mixin vertical-panel%)))
  
  (define horizontal-dragable% (horizontal-dragable-mixin (dragable-mixin horizontal-panel%)))

