               :name android.clojure.IndependentDrawer
               :extends android.view.SurfaceView
               :implements [android.view.SurfaceHolder$Callback]
-              :state drawer_state
+              :state state
               :init drawer-init
               :post-init drawer-post-init)
   (:import [android.content Context]

                     msg
                     start-time (System/currentTimeMillis)]
                 (with-canvas canvas this
                   (call-on-func-or-func-seq #(% canvas 0) anim-actions)
                   (call-on-func-or-func-seq #(% canvas 0) anim-after-actions))
                 (Thread/sleep pause-time 0)

                       format
                       width
                       height]
  (let [new-bitmap ^Bitmap
        (if-let [orig-bmap ^Bitmap @(.drawing-bitmap ^DrawerState (.state this))]
          (Bitmap/createScaledBitmap orig-bmap width height true)
          (Bitmap/createBitmap width height android.graphics.Bitmap$Config/ARGB_8888))]
    (reset! (.drawing-bitmap ^DrawerState (.state this))
            new-bitmap)
    (reset! (.drawing-canvas ^DrawerState (.state this))
            (Canvas. new-bitmap))))


(declare make-drawing-thread)

(defn -surfaceCreated [^android.clojure.IndependentDrawer this
                       ^SurfaceHolder holder]
  (reset! (.surface-available? ^DrawerState (.state this)) true)
  (let [thread ^Thread (make-drawing-thread this)]
    (reset! (.drawing-thread ^DrawerState (.state this))
            thread)
    (.start thread)))

(defn -surfaceDestroyed [^android.clojure.IndependentDrawer this
                         ^SurfaceHolder holder]
  (reset! (.surface-available? ^DrawerState (.state this)) false)
  (loop []
    (when (not (try
                 (.join ^Thread @(.drawing-thread ^DrawerState (.state this)))
                 true
                 (catch InterruptedException e
                   false)))
      (recur))))


(defmacro ^{:private true} with-canvas
  "when non-empty-action evaluates to true the action will be executed
with canvas-var bound to drawing canvas whose contents is stored in drawing
bitmap and will be preserved between different actions.

after-action will be executed with canvas-var bounded to the
resulting canvas with drawing-bitmap drawn on. Content of this canvas will
not be preserved."
  ([canvas-var surface-view non-empty-action action after-action]
     `(if-let [surface-canvas# ^Canvas (.. ~surface-view (getHolder) (lockCanvas))]
        (try
          (when ~non-empty-action
            (let [~canvas-var ^Canvas @(.drawing-canvas ^DrawerState (.state ~surface-view))]
              (assert ~canvas-var "error: drawing-canvas of surface view is nil")
              ~action)
            (.drawBitmap surface-canvas#
                         ^Bitmap @(.drawing-bitmap ^DrawerState
                                                   (.state ~surface-view))
                         0.0
                         0.0
                         nil))
          (let [~canvas-var ^Canvas surface-canvas#]
            (assert ~canvas-var)
            ~after-action)
          (finally
            (.. ~surface-view (getHolder) (unlockCanvasAndPost surface-canvas#))))
        (log "Unexpected error: lockCanvas returned nil"))))

(defmacro ^{:private true} call-on-func-or-func-seq [f func-or-coll]
  `(when ~func-or-coll
     (if (function? ~func-or-coll)
       (~f ~func-or-coll)
       (dorun (map ~f ~func-or-coll)))))

(defn- ^Thread make-drawing-thread [^android.clojure.IndependentDrawer this]
  (let [msg-queue ^ConcurrentLinkedQueue (.message-queue ^DrawerState (.state this))]
    (Thread.
     (fn []
       (log "Drawing thread's born")
       ;; wait while surfaceChanged will set up drawing canvas for up
       (loop []
         (when-not @(.drawing-canvas ^DrawerState (.state this))
           (Thread/sleep 25 0)
           (recur)))
       (log "Drawing thread's ready to draw")
       (loop []
         (when @(.surface-available? ^DrawerState (.state this))
           ;; note: if exceptions happens when we're processing this message -
           ;; let it be, it's removed from queue here and there's no other
           ;; way around. If you try other ways around make sure they're
           ;; correct with respect to concurrent clear-drawing-queue invokations
           (if-let [msg (.poll msg-queue)]
             (case (msg :type)
               :plain
               (let [{:keys [actions after-actions]} msg]
                 (with-canvas canvas this
                   (not (nil? actions))
                   (call-on-func-or-func-seq #(% canvas) actions)
                   (call-on-func-or-func-seq #(% canvas) after-actions)))
               :animation
               ;; if type is :animation then anim-actions and anim-after-actions are
               ;; sequences of functions of two argumetns:
