#lang racket/base
(require racket/class
         racket/draw
         ffi/unsafe
         "../../syntax.rkt"
         "../../lock.rkt"
         "../common/event.rkt"
         "item.rkt"
	 "utils.rkt"
	 "const.rkt"
	 "window.rkt"
	 "wndclass.rkt"
         "hbitmap.rkt"
         "types.rkt")

(provide
 (protect-out radio-box%))

(define SEP 4)
(define BM_SETCHECK #x00F1)

(define radio-box% 
  (class item%
    (init parent cb label
          x y w h
          labels
          val
          style
          font)

    (inherit auto-size set-control-font
             is-enabled-to-root?
             set-focus)

    (define callback cb)
    (define current-value val)

    (define hwnd
      (CreateWindowExW 0
                       "PLTTabPanel"
                       #f
                       (bitwise-ior WS_CHILD)
                       0 0 w h
                       (send parent get-content-hwnd)
                       #f
                       hInstance
                       #f))

    (define label-bitmaps null)

    (define radio-hwnds
      (let ([horiz? (memq 'horizontal style)])
        (let loop ([y 0] [w 0] [labels labels])
          (if (null? labels)
              (begin
                (MoveWindow hwnd 0 0 w y #t)
                null)
              (let* ([label (car labels)]
                     [bitmap? (label . is-a? . bitmap%)]
                     [radio-hwnd 
                      (CreateWindowExW/control 0
                                               "PLTBUTTON"
                                               (if (string? label)
                                                   label
                                                   "<image>")
                                               (bitwise-ior BS_RADIOBUTTON WS_CHILD WS_CLIPSIBLINGS
                                                            (if bitmap?
                                                                BS_BITMAP
                                                                0))
                                               0 0 0 0
                                               hwnd
                                               #f
                                               hInstance
                                               #f)])
                (when bitmap?
                  (let ([hbitmap (bitmap->hbitmap label)])
                    (set! label-bitmaps (cons hbitmap label-bitmaps))
                    (SendMessageW radio-hwnd BM_SETIMAGE IMAGE_BITMAP 
                                  (cast hbitmap _HBITMAP _LPARAM))))
                (ShowWindow radio-hwnd SW_SHOW)
                (set-control-font font radio-hwnd)
                (let-values ([(w1 h) 
                              (auto-size font label 0 0 20 4
                                         (lambda (w1 h1)
                                           (if horiz?
                                               (MoveWindow radio-hwnd (+ w SEP) 0 w1 h1 #t)
                                               (MoveWindow radio-hwnd 0 (+ y SEP) w1 h1 #t))
                                           (values w1 h1)))])
                  (cons radio-hwnd
                        (loop (if horiz? (max y h) (+ y SEP h))
                              (if horiz? (+ w SEP w1) (max w1 w))
                              (cdr labels)))))))))

    (unless (= val -1)
      (SendMessageW (list-ref radio-hwnds val) BM_SETCHECK 1 0))
    
    (super-new [callback cb]
               [parent parent]
               [hwnd hwnd]
               [extra-hwnds radio-hwnds]
               [style style])

    (define/override (is-hwnd? a-hwnd)
      (or (ptr-equal? hwnd a-hwnd)
          (for/or ([radio-hwnd (in-list radio-hwnds)])
            (ptr-equal? a-hwnd radio-hwnd))))

    (define/override (is-command? cmd)
      (= cmd BN_CLICKED))
    
    (define/override (do-command cmd control-hwnd)
      (let ([val (for/fold ([i 0]) ([radio-hwnd (in-list radio-hwnds)]
                                    [pos (in-naturals)])
                   (if (ptr-equal? control-hwnd radio-hwnd)
                       pos
                       i))])
        (unless (= val current-value)
          (set-selection val)
          (queue-window-event this (lambda ()
                                     (callback this
                                               (new control-event%
                                                    [event-type 'radio-box]
                                                    [time-stamp (current-milliseconds)])))))))


    (define focused 0)

    (define/public (button-focus i)
      (if (= i -1)
          (min focused (length radio-hwnds))
          (begin
            (set! focused i)
            (set-focus (list-ref radio-hwnds i)))))

    (define/override (get-focus-hwnd)
      (if (= focused -1)
          hwnd
          (list-ref radio-hwnds focused)))

    (define/public (set-selection val)
      (atomically
       (unless (= val current-value)
         (unless (= current-value -1)
           (SendMessageW (list-ref radio-hwnds current-value) BM_SETCHECK 0 0))
         (unless (= val -1)
           (SendMessageW (list-ref radio-hwnds val) BM_SETCHECK 1 0))
         (set! current-value val))))

    (define buttons-enabled (make-vector (length radio-hwnds) #t))
    (define/public (enable-button i on?)
      (unless (eq? (and on? #t) (vector-ref buttons-enabled i))
        (vector-set! buttons-enabled i (and on? #t))
        (when (is-enabled-to-root?)
          (void (EnableWindow (list-ref radio-hwnds i) on?)))))
    (define/override (internal-enable on?)
      (for ([radio-hwnd (in-list radio-hwnds)]
            [radio-on? (in-vector buttons-enabled)])
        (void (EnableWindow radio-hwnd (and on? radio-on?)))))

    (define/public (get-selection) current-value)

    (define/public (number) (length radio-hwnds))))

