               :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]

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


(declare make-drawing-thread)

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

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


(defmacro with-canvas
  "action will be executed with canvas-var bound to drawing canvas whole
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 action after-action]
     `(with-canvas ~canvas-var ~surface-view ~action ~after-action nil))
  ([canvas-var surface-view action after-action post-action]
     `(if-let [surface-canvas# ^Canvas (.. ~surface-view (getHolder) (lockCanvas))]
        (try
          (let [~canvas-var ^Canvas @(.drawing-canvas ^DrawerState (.drawer_state ~surface-view))]
            ~action)
          (.drawBitmap surface-canvas#
                       ^Bitmap @(.drawing-bitmap ^DrawerState
                                                 (.drawer_state ~surface-view))
                       0.0
                       0.0
                       nil)
          (let [~canvas-var ^Canvas surface-canvas#]
            ~after-action)
          (finally
            ~post-action
            (.. ~surface-view (getHolder) (unlockCanvasAndPost surface-canvas#))))
        (log "Unexpected error: lockCanvas returned nil"))))

(defmacro call-on-func-or-func-seq [f func-or-coll]
  `(when ~func-or-coll
     (if (seq? ~func-or-coll)
       (dorun (map ~f ~func-or-coll))
       (~f ~func-or-coll))))

(defn- ^Thread make-drawing-thread [^android.clojure.IndependentDrawer this]
  (let [msg-queue ^ConcurrentLinkedQueue (.message-queue ^DrawerState (.drawer_state this))]
    (Thread.
     (fn []
       (log "Drawing thread's born")
       (loop []
         (when @(.surface-available? ^DrawerState (.drawer_state this))
           (if-let [msg (.peek msg-queue)]
             (case (msg :type)
               :plain
               (let [{:keys [actions after-actions]} msg]
                 (with-canvas canvas this
                   (call-on-func-or-func-seq #(% canvas) actions)
                   (call-on-func-or-func-seq #(% canvas) after-actions)
                   (.poll msg-queue)))
               :animation
               ;; if type is :animation then anim-actions and anim-after-actions are
               ;; sequences of functions of two argumetns:

           (contains? #{:plain :animation} (msg :type)))
    (.add ^ConcurrentLinkedQueue
          (.message-queue ^DrawerState
                          (.state drawer))
          msg)
    (log "Error: attempt to send invalid message: %s" msg)))

