#lang racket/base
(require racket/class
         ffi/unsafe
         ffi/unsafe/objc
          "../../syntax.rkt"
         "item.rkt"
         "types.rkt"
         "const.rkt"
         "utils.rkt"
         "window.rkt"
         "queue.rkt"
         "../common/event.rkt"
         "../common/queue.rkt"
         "../common/freeze.rkt"
         "../../lock.rkt")

(provide 
 (protect-out slider%))

;; ----------------------------------------

(import-class NSSlider NSTextField NSView)

(define-objc-class RacketSlider NSSlider
  #:mixins (FocusResponder KeyMouseResponder CursorDisplayer)
  [wxb]
  (-a _void (changed: [_id sender])
      (let ([wx (->wx wxb)])
        (when wx
          (send wx update-message)
          (queue-window-event wx (lambda () (send wx changed)))
          (constrained-reply
           (send wx get-eventspace)
           (lambda () (let loop () (pre-event-sync #t) (when (yield/no-sync) (loop))))
           (void))))))

(defclass slider% item%
  (init parent cb
        label
        val lo hi
        x y w
        style
        font)
  (inherit get-cocoa register-as-child
           init-font)

  (define vert? (or (memq 'vertical style)
                    (memq 'upward style)))
  (define up? (memq 'upward style))

  (define slider-lo lo)
  (define slider-hi hi)

  (define slider-cocoa
    (let ([cocoa (as-objc-allocation
                  (tell (tell RacketSlider alloc) init))])
      (tellv cocoa setMinValue: #:type _double* lo)
      (tellv cocoa setMaxValue: #:type _double* hi)
      (tellv cocoa setDoubleValue: #:type _double* (flip val))
      ;; heuristic: show up to tick marks:
      (when ((- hi lo) . < . 64)
        (tellv cocoa setNumberOfTickMarks: #:type _NSUInteger (add1 (- hi lo)))
        (tellv cocoa setAllowsTickMarkValuesOnly: #:type _BOOL #t))
      (tellv cocoa setFrame: #:type _NSRect (make-NSRect 
                                             (make-NSPoint 0 0)
                                             (make-NSSize (if vert? 24 32)
                                                          (if vert? 64 24))))
      (when (and vert? (version-10.12-or-later?))
        (tellv cocoa setVertical: #:type _BOOL #t))
      (tellv cocoa setContinuous: #:type _BOOL #t)
      ;; (tellv cocoa sizeToFit)
      cocoa))

  (define-values (message-cocoa message-w message-h)
    (if (memq 'plain style)
        (values #f #f #f)
        (let ([cocoa (as-objc-allocation
                      (tell (tell NSTextField alloc) init))])
          (init-font cocoa font)
          (tellv cocoa setSelectable: #:type _BOOL #f)
          (tellv cocoa setEditable: #:type _BOOL #f)
          (tellv cocoa setBordered: #:type _BOOL #f)
          (tellv cocoa setDrawsBackground: #:type _BOOL #f)
          (tellv cocoa setStringValue: #:type _NSString (format "~a" hi))
          (tellv cocoa sizeToFit)
          (let ([r1 (tell #:type _NSRect cocoa frame)])
            (tellv cocoa setStringValue: #:type _NSString (format "~a" lo))
            (tellv cocoa sizeToFit)
            (let ([r2 (tell #:type _NSRect cocoa frame)])
              (tellv cocoa setStringValue: #:type _NSString (format "~a" val))
              (values cocoa
                      (max (NSSize-width (NSRect-size r1))
                           (NSSize-width (NSRect-size r2)))
                      (max (NSSize-height (NSRect-size r1))
                           (NSSize-height (NSRect-size r2)))))))))

  (define cocoa
    (if message-cocoa
        (let* ([f (tell #:type _NSRect slider-cocoa frame)]
               [w (+ (if vert?
                         message-w
                         0)
                     (NSSize-width (NSRect-size f)))]
               [h (+ (if vert?
                         0
                         message-h)
                     (NSSize-height (NSRect-size f)))])
          (let ([cocoa (as-objc-allocation
                        (tell (tell NSView alloc)
                              initWithFrame: #:type _NSRect (make-NSRect 
                                                             (make-init-point x y)
                                                             (make-NSSize w h))))])
            (tellv cocoa addSubview: slider-cocoa)
            (tellv cocoa addSubview: message-cocoa)
            (arrange-parts w h)
            cocoa))
        slider-cocoa))

  (define/private (arrange-parts w h)
    (tellv slider-cocoa setFrame: #:type _NSRect (make-NSRect
                                                  (make-NSPoint 0
                                                                (if vert? 0 message-h))
                                                  (make-NSSize (- w (if vert? message-w 0))
                                                               (- h (if vert? 0 message-h)))))
    (tellv message-cocoa setFrame: #:type _NSRect (make-NSRect
                                                   (make-NSPoint (if vert? 
                                                                     (- w message-w)
                                                                     (/ (- w message-w) 2))
                                                                 (if vert?
                                                                     (/ (- h message-h) 2)
                                                                     0))
                                                   (make-NSSize message-w message-h))))

  (define/override (set-size x y w h)
    (super set-size x y w h)
    (when message-cocoa
      (arrange-parts w h)))
  
  (when message-cocoa
    (set-ivar! slider-cocoa wxb (->wxb this)))

  (super-new [parent parent]
             [cocoa cocoa]
             [callback cb]
             [no-show? (memq 'deleted style)])

  (define/override (get-cocoa-control) slider-cocoa)

  (tellv slider-cocoa setTarget: slider-cocoa)
  (tellv slider-cocoa setAction: #:type _SEL (selector changed:))

  (define callback cb)
  (define/public (changed)
    (callback this (new control-event%
                        [event-type 'slider]
                        [time-stamp (current-milliseconds)])))

  (define/private (flip v)
    (if vert?
        (if up?
            v
            (+ slider-lo (- slider-hi v)))
        v))

  (define/public (set-value v)
    (atomically
     (tellv slider-cocoa setDoubleValue: #:type _double* (flip v))
     (update-message v)))
  (define/public (get-value)
    (flip (inexact->exact (floor (tell #:type _double slider-cocoa doubleValue)))))

  (define/public (update-message [val (get-value)])
    (tellv message-cocoa setStringValue: #:type _NSString (format "~a" val))
    (tellv message-cocoa sizeToFit))

  (inherit get-cocoa-window)
  (define/override (post-mouse-down)
    ;; For some reason, dragging a slider disabled mouse-moved
    ;;  events for the window, so turn them back on:
    (tellv (get-cocoa-window) setAcceptsMouseMovedEvents: #:type _BOOL #t))

  (define/override (maybe-register-as-child parent on?)
    (register-as-child parent on?)))

