#lang racket/base
(require racket/class
         racket/draw/private/dc
         racket/draw/private/bitmap-dc
         racket/draw/private/bitmap
         racket/draw/private/local
         racket/draw/private/record-dc
         racket/draw/unsafe/cairo
         "../../lock.rkt"
         "queue.rkt")

(provide 
 (protect-out backing-dc%
              backing-draw-bm
              
              ;; scoped method names:
              clean-slate
              get-backing-size
              queue-backing-flush
              on-backing-flush
              start-backing-retained
              end-backing-retained
              reset-backing-retained
              make-backing-bitmap
              request-delay
              cancel-delay
              end-delay))

(define-local-member-name
  clean-slate
  get-backing-size
  queue-backing-flush
  on-backing-flush
  start-backing-retained
  end-backing-retained
  reset-backing-retained
  make-backing-bitmap
  request-delay
  cancel-delay
  end-delay)

(define backing-dc%
  (class (record-dc-mixin (dc-mixin bitmap-dc-backend%))
    (init transparent?)

    (inherit internal-get-bitmap
             internal-set-bitmap
             reset-cr
             set-recording-limit
             get-recorded-command
             get-clear-operator)

    (super-new)

    (set-recording-limit (if transparent? 1024 -1))

    (define/override (ok?) #t)

    ;; Override this method to get the right size
    (define/public (get-backing-size xb yb)
      (set-box! xb 1)
      (set-box! yb 1))

    ;; override this method to set up a callback to
    ;;  `on-backing-flush' when the backing store can be rendered
    ;;  to the screen; called atomically (expecting no exceptions)
    (define/public (queue-backing-flush)
      (void))

    (define retained-cr #f)
    (define retained-counter 0)
    (define needs-flush? #f)
    (define nada? #t)

    ;; called with a procedure that is applied to a bitmap;
    ;;  returns #f if there's nothing to flush
    (define/public (on-backing-flush proc)
      (cond
       [(not retained-cr) #f]
       [(positive? retained-counter) 
        (unless nada?
          (proc (or (get-recorded-command)
                    (internal-get-bitmap))))
        #t]
       [else 
        (reset-backing-retained proc)
        #t]))
    
    (define/public (can-backing-flush?)
      (and retained-cr #t))

    (define/public (reset-backing-retained [proc void])
      (let ([cr retained-cr])
        (when cr 
          (let ([bm (internal-get-bitmap)])
            (set! retained-cr #f)
            (internal-set-bitmap #f #t)
            (super release-cr retained-cr)
            (proc bm)
            (release-backing-bitmap bm)))))

    (define/public (start-backing-retained)
      (as-entry
       (lambda () 
         (set! retained-counter (add1 retained-counter)))))

    (define/public (end-backing-retained)
      (as-entry
       (lambda () 
         (if (zero? retained-counter)
             (log-error "unbalanced end-on-paint")
             (set! retained-counter (sub1 retained-counter))))))

    (define/public (make-backing-bitmap w h)
      (make-object bitmap% w h #f #t))

    (define/public (ensure-ready) (get-cr))

    (define/override (get-cr)
      (or retained-cr
          (let ([w (box 0)]
                [h (box 0)])
            (get-backing-size w h)
            (let ([bm (get-backing-bitmap (lambda (w h) (make-backing-bitmap w h)) (unbox w) (unbox h))])
              (internal-set-bitmap bm #t))
            (let ([cr (super get-cr)])
              (set! retained-cr cr)
              (reset-cr cr)
              cr))))

    (define/override (release-cr cr)
      (set! nada? #f)
      (when (zero? flush-suspends)
        (queue-backing-flush)))

    (define/override (erase)
      (super erase)
      (when (= (get-clear-operator)
               CAIRO_OPERATOR_CLEAR)
        (set! nada? #t)))

    (define/public (clean-slate)
      (super erase)
      (set! nada? #t))

    (define flush-suspends 0)
    (define req #f)

    (define/public (request-delay) (void))
    (define/public (cancel-delay req) (void))

    (define/override (suspend-flush) 
      (atomically
       (when (zero? flush-suspends)
         (when req (cancel-delay req))
         (set! req (request-delay)))
       (set! flush-suspends (add1 flush-suspends))))

    (define/override (resume-flush)  
      (atomically 
       (unless (zero? flush-suspends)
         (set! flush-suspends (sub1 flush-suspends))
         (when (zero? flush-suspends)
           (queue-backing-flush)))))

    (define/public (end-delay)
      ;; call in atomic mode
      (when (and (zero? flush-suspends) req)
        (cancel-delay req)
        (set! req #f)))))

(define (get-backing-bitmap make-bitmap w h)
  (make-bitmap w h))

(define (release-backing-bitmap bm)
  (send bm release-bitmap-storage))

(define cairo-dc
  (make-object (dc-mixin
                (class default-dc-backend%
                  (inherit reset-cr)

                  (define cr #f)
                  (define w 0)
                  (define h 0)

                  (super-new)

                  (define/public (set-cr new-cr new-w new-h)
                    (set! cr new-cr)
                    (set! w new-w)
                    (set! h new-h)
                    (when cr
                      (reset-cr cr)))

                  (define/override (get-cr) cr)

                  (define/override (reset-clip cr)
                    (super reset-clip cr)
                    (cairo_rectangle cr 0 0 w h)
                    (cairo_clip cr))))))

(define (backing-draw-bm bm cr w h)
  (if (procedure? bm)
      (begin
        (send cairo-dc reset-config)
        (send cairo-dc set-cr cr w h)
        (bm cairo-dc)
        (send cairo-dc set-cr #f 0 0))
      (let ([s (cairo_get_source cr)])
        (cairo_pattern_reference s)
        (cairo_set_source_surface cr (send bm get-cairo-surface) 0 0)
        (cairo_new_path cr)
        (cairo_rectangle cr 0 0 w h)
        (cairo_fill cr)
        (cairo_set_source cr s)
        (cairo_pattern_destroy s))))
