 (defn animated-sprite
   ([{:keys [animation] :as data}]
    (let [{:keys [cell-width cell-height]} (:spritesheet animation)]
-     (merge (AnimatedSprite. animation cell-width cell-height 0 0 0 0)
+     (merge (AnimatedSprite. animation cell-width cell-height 0 0 0 0 nil)
             data)))
   ([{:keys [spritesheet] :as animation} x y]
    (let [{:keys [cell-width cell-height]} spritesheet]
-     (animated-sprite animation cell-width cell-height x y 0 0)))
+     (animated-sprite animation cell-width cell-height x y 0 0 nil)))
   ([animation width height x y]
-   (animated-sprite animation width height x y 0 0))
+   (animated-sprite animation width height x y 0 0 nil))
   ([animation width height x y velocity-x velocity-y]
-   (AnimatedSprite. animation width height x y velocity-x velocity-y)))
+   (animated-sprite animation width height x y velocity-x velocity-y nil))
+  ([animation width height x y velocity-x velocity-y update-fn]
+   (AnimatedSprite. animation width height x y velocity-x velocity-y update-fn)))
 
 (defn ^:export new
   "Creates a sprite record based on graphics resource"

(defprotocol TypeInfo
  (get-type [this]))

(defn ^:export move [{:keys [x y velocity-x velocity-y] :as sprite}]
  "Moves a sprite based on its velocity on the x and y-axis"
  (assoc sprite

         :y (+ y velocity-y)))

(defn- update-sprite
  [sprite]
  (if-let [update-fn (:update-fn sprite)]
    (-> sprite
        update-fn
        move)
    (move sprite)))

(defn- update-static
  [element]
  (if-let [update-fn (:update-fn element)]
    (update-fn element)
    element))

(defn- update-element
  [& [element scene]]
   (let [updated-element (condp = (type element)
                           ::sprite (update-sprite element)
                          (update-static element))]
     (if scene
       (assoc scene (:se-id (meta element)) updated-element)
       updated-element)))

(defn- update-animated-sprite
  ([sprite]
   (update-animated-sprite sprite nil))
  ([sprite scene]
   (update-element (update-in sprite [:animation] graphic/update-frame
                              (:animation sprite)) scene)))

(defprotocol SpriteActions
  (render [this context])
  (update [this] [this scene]))

(defrecord StaticImage [image width height x y update-fn]
  TypeInfo
  (get-type [this] :static-image)
  SpriteActions
  (render [this context] (graphic/render-image context (:image image) x y width height))
  (update [this] (update-element this))
  (update [this scene] (update-element this scene)))

(defn ^:export static-image
  "Creates a static image"
  ([{:keys [image] :as data}]
   (let [{:keys [width height]} image]
     (merge (StaticImage. image width height 0 0 nil)
            data)))
  ([{:keys [width height] :as image} x y]
   (static-image image width height x y nil))
  ([image width height x y]
   (static-image image width height x y nil))
  ([image width height x y update-fn]
   (StaticImage. image width height x y update-fn)))

(defrecord ImageSprite [image width height x y velocity-x velocity-y update-fn]
  TypeInfo
  (get-type [this] :image-sprite)
  SpriteActions
  (render [this context] (graphic/render-image context (:image image) x y width height 0 0 width height
                                               (* (/ width 2) -1) (* (/ height 2) -1)))
  (update [this] (update-element this))
  (update [this scene] (update-element this scene)))

(defn image-sprite
  "Creates a sprite represented by the given image with the specified values"
  ([{:keys [image] :as data}]
   (let [{:keys [width height]} image]
     (merge (ImageSprite. image width height 0 0 0 0 nil)
            data)))
  ([{:keys [width height] :as image} x y]
   (image-sprite image width height x y 0 0 nil))
  ([image width height x y]
   (image-sprite image width height x y 0 0 nil))
  ([image width height x y velocity-x velocity-y]
   (image-sprite image width height x y velocity-x velocity-y nil))
  ([image width height x y velocity-x velocity-y update-fn]
   (ImageSprite. image width height x y velocity-x velocity-y update-fn)))

(defrecord SpritesheetSprite [spritesheet cell width height x y velocity-x velocity-y update-fn]
  TypeInfo
  (get-type [this] :spritesheet-sprite)
  SpriteActions
  (render [this context] (graphic/render-frame context spritesheet cell x y))
  (update [this] (update-element this))
  (update [this scene] (update-element this scene)))

(defn spritesheet-sprite
  ([{:keys [spritesheet] :as data}]
   (let [{:keys [cell-width cell-height]} spritesheet]
     (merge (SpritesheetSprite. spritesheet 0 cell-width cell-height 0 0 0 0 nil)
            data)))
  ([{:keys [cell-width cell-height] :as spritesheet} cell x y]
   (spritesheet-sprite spritesheet cell cell-width cell-height x y 0 0 nil))
  ([spritesheet cell width height x y]
   (spritesheet-sprite spritesheet width height cell x y 0 0 nil))
  ([spritesheet cell width height x y velocity-x velocity-y]
   (spritesheet-sprite spritesheet width height cell x y velocity-x velocity-y nil))
  ([spritesheet cell width height x y velocity-x velocity-y update-fn]
   (SpritesheetSprite. spritesheet width height cell x y velocity-x velocity-y update-fn)))

(defrecord AnimatedSprite [animation width height x y velocity-x velocity-y update-fn]
  TypeInfo
  (get-type [this] :animated-sprite)
  SpriteActions
