(ns nol.core
  (:gen-class)
  (:require
   [clojure.math])
  (:import
   [java.nio ByteBuffer FloatBuffer IntBuffer]
   [java.util.concurrent LinkedBlockingQueue]
   [org.lwjgl PointerBuffer]
   [org.lwjgl.assimp AIColor4D AIFace AIMaterial AIMatrix4x4 AIMesh AINode AIScene AIString AITexture AIVector3D Assimp]
   [org.lwjgl.glfw GLFW GLFWKeyCallbackI]
   [org.lwjgl.opengl GL GL11 GL20 GL30 GL45]
   [org.lwjgl.stb STBImage]
   [org.joml Matrix4f Vector2f Vector3f]
   [org.lwjgl.system MemoryStack MemoryUtil]))

(def ^LinkedBlockingQueue render-queue (LinkedBlockingQueue.))

(defonce camera-fly (atom {:type :fly
                           :pos (Vector3f.)
                           :yaw 0.0
                           :pitch 0.0
                           :last-x 0.0
                           :last-y 0.0
                           :target (Vector3f.)
                           :view-matrix (.identity (Matrix4f.))}))

(defonce camera-orbit (atom {:type :orbit
                             :pos (Vector3f.)
                             :yaw 0.0
                             :pitch 0.0
                             :last-x 0.0
                             :last-y 0.0
                             :focus-point (Vector3f.)
                             :view-matrix (.identity (Matrix4f.))}))

(def state (atom {:last-time 0.0
                  :drag-start-pos nil
                  :current-mode :static}))

(defonce gl-thread (atom nil))

(defmacro with-gl [& body]
  `(let [result# (promise)]
     (when-not (and @gl-thread (.isAlive ^Thread @gl-thread))
       (throw (RuntimeException. "GL thread is not running")))
     (.put render-queue #(deliver result# (try (do ~@body)
                                               (catch Exception e# e#))))
     (let [r# (deref result#)]
       (if (instance? Exception r#)
         (throw r#)
         r#))))

(defn- compile-shader [type source]
  (let [shader (GL20/glCreateShader type)]
    (GL20/glShaderSource shader ^String source)
    (GL20/glCompileShader shader)
    (when (zero? (GL20/glGetShaderi shader GL20/GL_COMPILE_STATUS))
      (throw (RuntimeException. (str "Shader compile error: " (GL20/glGetShaderInfoLog shader)))))
    shader))

(defn- link-program [vertex-shader fragment-shader]
  (let [program (GL20/glCreateProgram)]
    (GL20/glAttachShader program vertex-shader)
    (GL20/glAttachShader program fragment-shader)
    (GL20/glLinkProgram program)
    (when (zero? (GL20/glGetProgrami program GL20/GL_LINK_STATUS))
      (throw (RuntimeException. (str "Program link error: " (GL20/glGetProgramInfoLog program)))))
    program))

(defn ai-matrix->matrix4f [^AIMatrix4x4 m]
  (Matrix4f. (.a1 m) (.b1 m) (.c1 m) (.d1 m)
             (.a2 m) (.b2 m) (.c2 m) (.d2 m)
             (.a3 m) (.b3 m) (.c3 m) (.d3 m)
             (.a4 m) (.b4 m) (.c4 m) (.d4 m)))

(defn matrix4f->vectors [^Matrix4f m]
  [(vector (.m00 m) (.m01 m) (.m02 m) (.m03 m))
   (vector (.m10 m) (.m11 m) (.m12 m) (.m13 m))
   (vector (.m20 m) (.m21 m) (.m22 m) (.m23 m))
   (vector (.m30 m) (.m31 m) (.m32 m) (.m33 m))])

(defn node-matrix4f [^AINode c]
  (-> c
      (.mTransformation)
      (ai-matrix->matrix4f)))

(defonce loaded-scene (atom nil))
(defonce uploaded-scene (atom nil))
(defonce shaders (atom nil))

(defn camera-forward [^double yaw ^double pitch]
  (Vector3f. (* (Math/cos pitch) (Math/sin yaw))
             (Math/sin pitch)
             (- (* (Math/cos pitch) (Math/cos yaw)))))

(defn camera-right [^double yaw]
  (Vector3f. (Math/cos yaw) 0.0 (Math/sin yaw)))

(defn camera-up [^Vector3f forward ^Vector3f right]
  (-> (Vector3f. forward) (.cross right)))

(defn get-ndc [^double x ^double y ^long w ^long h]
  [(- (* 2 (/ x w)) 1)
   (- 1 (* 2 (/ y h)))])

(defn- key-down? [window key]
  (= (GLFW/glfwGetKey window key) GLFW/GLFW_PRESS))

(defn- handle-key-actions! [_win key _scancode action _mods]
  (when (= action GLFW/GLFW_PRESS)
    (when (= key GLFW/GLFW_KEY_ESCAPE)
      (swap! state assoc :should-close true))
    (when (= key GLFW/GLFW_KEY_R)
      (swap! state update :current-camera
             #(merge % {:pos (Vector3f. 0.0 0.0 10.0)
                        :yaw 0.0
                        :pitch 0.0
                        :focus-point (Vector3f. 0.0 0.0 0.0)
                        :target (Vector3f.)})))))

(defn- drag-detected? [^double x1 ^double y1 ^double x2 ^double y2]
  (let [threshold 10.0
        dx (- x2 x1)
        dy (- y2 y1)]
    (< threshold (+ (* dx dx) (* dy dy)))))

(def cam-speed 0.04)

(defn- handle-keyboard-state! [window]
  (when-let [camera (when (= (:current-mode @state) :fly)
                      @camera-fly)]
    (let [move-dir (Vector3f.)
          {:keys [yaw pitch ^Vector3f pos]} camera]
      (when (key-down? window GLFW/GLFW_KEY_W)
        (.add move-dir 0.0 0.0 1.0))
      (when (key-down? window GLFW/GLFW_KEY_S)
        (.add move-dir 0.0 0.0 -1.0))
      (when (key-down? window GLFW/GLFW_KEY_A)
        (.add move-dir -1.0 0.0 0.0))
      (when (key-down? window GLFW/GLFW_KEY_D)
        (.add move-dir 1.0 0.0 0.0))
      (when (key-down? window GLFW/GLFW_KEY_Q)
        (.add move-dir 0.0 1.0 0.0))
      (when (key-down? window GLFW/GLFW_KEY_E)
        (.add move-dir 0.0 -1.0 0.0))
      (when (> (.length move-dir) 0.0)
        (.normalize move-dir)
        (let [forward ^Vector3f (camera-forward yaw pitch)
              right ^Vector3f (camera-right yaw)
              up ^Vector3f (camera-up forward right)
              velocity ^Vector3f (-> (Vector3f. right)
                                     (.mul (.x move-dir))
                                     (.fma (.z move-dir) forward)
                                     (.fma (.y move-dir) up)
                                     (.mul ^double cam-speed))]
          (.add pos velocity)))
      (.set move-dir 0.0 0.0 0.0))))

(defn- handle-mouse-move! [_window ^double xpos ^double ypos]
  (when-let [camera (case (:current-mode @state)
                      :fly camera-fly
                      :orbit camera-orbit
                      :static nil)]
    (when (and (= (:type @camera) :orbit))
      (let [{:keys [^double yaw ^double pitch last-x last-y]} @camera
            last-x (double (or last-x xpos))
            last-y (double (or last-y ypos))
            sensitivity 0.005
            dx (- last-x xpos)
            dy (- last-y ypos)
            fwd ^Vector3f (camera-forward yaw pitch)]
        (swap! camera assoc
               :last-x xpos
               :last-y ypos
               :yaw (- yaw (* dx sensitivity))
               :pitch (-> (- pitch (* dy sensitivity))
                          (max (- (/ Math/PI 2)))
                          (min (/ Math/PI 2))))))
    #_(when-let [cursor-pos ^Vector2f (:drag-start-pos @state)]
        (when (and (drag-detected? xpos ypos (.x cursor-pos) (.y cursor-pos)))
          (swap! state assoc :orbit-mode true)))
    (when (and (= (:type @camera) :fly))
      (let [{:keys [^double yaw ^double pitch pos last-x last-y]} @camera
            last-x (double (or last-x xpos))
            last-y (double (or last-y ypos))
            sensitivity 0.001
            dx (- last-x xpos)
            dy (- last-y ypos)]
        (swap! camera assoc
               :last-x xpos
               :last-y ypos
               :yaw (- yaw (* dx sensitivity))
               :pitch (+ pitch (* dy sensitivity)))))))

(defn- get-current-cursor-pos! ^Vector2f [^MemoryStack stack ^long window]
  (let [xbuf (.mallocDouble stack 1)
        ybuf (.mallocDouble stack 1)]
    (GLFW/glfwGetCursorPos window xbuf ybuf)
    (Vector2f. (.get xbuf 0) (.get ybuf 0))))

(defn- handle-mouse-buttons! [^long win button action mods]
  (cond
    (and (= button GLFW/GLFW_MOUSE_BUTTON_RIGHT) (= action GLFW/GLFW_PRESS))
    (let []
      (swap! state assoc :current-mode :fly)
      (GLFW/glfwSetInputMode win GLFW/GLFW_CURSOR GLFW/GLFW_CURSOR_DISABLED))

    (and (= button GLFW/GLFW_MOUSE_BUTTON_RIGHT) (= action GLFW/GLFW_RELEASE))
    (let []
      (swap! state assoc :current-mode :static)
      (swap! camera-fly assoc :last-x nil :last-y nil)
      (GLFW/glfwSetInputMode win GLFW/GLFW_CURSOR GLFW/GLFW_CURSOR_NORMAL))

    (and (= button GLFW/GLFW_MOUSE_BUTTON_LEFT) (= action GLFW/GLFW_PRESS))
    (do
      (when (mods :ctrl)
        (swap! state assoc :current-mode :orbit))
      (swap! state assoc :drag-start-pos (get-current-cursor-pos! (MemoryStack/stackGet) win)))

    (and (= button GLFW/GLFW_MOUSE_BUTTON_LEFT) (= action GLFW/GLFW_RELEASE))
    (let []
      (swap! state assoc :current-mode :static :drag-start-pos nil)
      (GLFW/glfwSetCursor win MemoryUtil/NULL))))

(defn render-scene [^MemoryStack stack scene node ^Matrix4f mtx]
  (let [buf (.mallocFloat stack 16)
        model-mtx (Matrix4f.)]
    (.mul mtx ^Matrix4f (:transform node) model-mtx)
    (.get model-mtx buf)
    (when-let [mesh-indices (seq (:meshes node))]
      (let [{:keys [gl-meshes gl-textures]} scene
            {:keys [_program uniforms]} @shaders]
        (doseq [idx mesh-indices
                :let [gl-mesh (nth gl-meshes idx)
                      mesh (:mesh gl-mesh)
                      [r g b a] (:color mesh)
                      tex-idx (:texture-idx mesh)]]
          (GL20/glUniform4f (:color uniforms) r g b a)
          (GL20/glUniformMatrix4fv ^int (:mm uniforms) false buf)
          (GL30/glBindVertexArray (:vao gl-mesh))
          (when tex-idx
            (let [tex-id (:gl-id (nth gl-textures tex-idx))]
              (GL45/glActiveTexture GL45/GL_TEXTURE0)
              (GL11/glBindTexture GL11/GL_TEXTURE_2D tex-id)
              (GL20/glUniform1i (:tex uniforms) 0)))
          (GL11/glDrawElements GL11/GL_TRIANGLES (:indices-count gl-mesh) GL11/GL_UNSIGNED_INT 0))))
    (when-let [children (seq (:children node))]
      (doseq [child children]
        (render-scene stack scene child model-mtx)))))

(defn- camera-view-matrix [{:keys [yaw pitch ^Vector3f pos ^Vector3f target ^Matrix4f view-matrix]}]
  (-> view-matrix
      (.identity)
      (.lookAt (.x pos) (.y pos) (.z pos)
               (.x target) (.y target) (.z target)
               0.0 1.0 0.0)))

(defn- update-loop [window ^MemoryStack stack data]
  (let [w-buf (.mallocInt stack 1)
        h-buf (.mallocInt stack 1)
        _     (GLFW/glfwGetFramebufferSize ^long window w-buf h-buf)
        w     (.get w-buf 0)
        h     (.get h-buf 0)
        t (GLFW/glfwGetTime)]
    (GL11/glViewport 0 0 w h)
    (GL11/glClear GL11/GL_COLOR_BUFFER_BIT)
    (GL11/glClear (bit-or GL11/GL_COLOR_BUFFER_BIT GL11/GL_DEPTH_BUFFER_BIT))

    (try
      (when (and @uploaded-scene @shaders)
        (handle-keyboard-state! window)
        (let [scene @uploaded-scene
              {:keys [program uniforms]} @shaders]
          (GL20/glUseProgram program)
          (let [vbuf (.mallocFloat stack 16)
                pbuf (.mallocFloat stack 16)
                camera (if (= :orbit (:current-mode @state))
                         camera-orbit
                         camera-fly)
                view (camera-view-matrix @camera)
                proj (:projection-matrix @state)]
            (.get ^Matrix4f view vbuf)
            (.get ^Matrix4f proj pbuf)
            (GL20/glUniformMatrix4fv ^int (:mv uniforms) false vbuf)
            (GL20/glUniformMatrix4fv ^int (:mp uniforms) false pbuf)
            (render-scene stack scene (:hiearchy scene) (Matrix4f.)))))
      (catch Exception e
        (println "Update Loop GL thread error:" e)))

    (GL30/glBindVertexArray 0)
    (GL20/glUseProgram 0)

    (GLFW/glfwSwapBuffers window)))

(defn stop! []
  (when @uploaded-scene
    (doseq [{:keys [^int vbo ^int vao ^int ebo]} (:gl-meshes @uploaded-scene)]
      (GL45/glDeleteBuffers vbo)
      (GL45/glDeleteBuffers ebo)
      (GL30/glDeleteVertexArrays vao))
    (doseq [{:keys [^int gl-id]} (:gl-textures @uploaded-scene)]
      (GL11/glDeleteTextures gl-id)))
  (reset! uploaded-scene nil)
  (reset! shaders nil)
  (swap! state assoc :should-close true))

(defn- init [window width height]
  (swap! state assoc
         :window window
         :should-close false
         :projection-matrix 
         (-> (Matrix4f.)
             (.identity)
             (.perspective (float (Math/toRadians 45))
                           (float (/ (float width) (float height)))
                           (float 0.1)
                           (float 100.0))))) 

(defn -main []
  (when-not (GLFW/glfwInit)
    (throw (RuntimeException. "Failed to init GLFW")))

  (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MAJOR 4)
  (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MINOR 5)
  (GLFW/glfwWindowHint GLFW/GLFW_OPENGL_PROFILE GLFW/GLFW_OPENGL_CORE_PROFILE)

  (let [width 960 #_1900
        height 500 #_1100
        window (GLFW/glfwCreateWindow 960 500 "NOL" MemoryUtil/NULL MemoryUtil/NULL)]
    (when (= window MemoryUtil/NULL)
      (GLFW/glfwTerminate)
      (throw (RuntimeException. "Failed to create window")))


    (GLFW/glfwMakeContextCurrent window)
    (GL/createCapabilities)
    (GL11/glEnable GL11/GL_DEPTH_TEST)
    (GL11/glEnable GL11/GL_CULL_FACE)
    (GL11/glCullFace GL11/GL_BACK)
    (GLFW/glfwSwapInterval 1)

    (GLFW/glfwSetKeyCallback window
      (reify GLFWKeyCallbackI
        (invoke [_ win key scancode action mods]
          (try
            (handle-key-actions! win key scancode action mods)
            (catch Exception e
              (println "Key Callback Exception:" e))))))
    (when (GLFW/glfwRawMouseMotionSupported)
      (GLFW/glfwSetInputMode window GLFW/GLFW_RAW_MOUSE_MOTION GLFW/GLFW_TRUE))
    (GLFW/glfwSetCursorPosCallback window
      (reify org.lwjgl.glfw.GLFWCursorPosCallbackI
        (invoke [_ win xpos ypos]
          (try
            (handle-mouse-move! win xpos ypos)
            (catch Exception e
              (println "Cursor Pos Callback Exception:" e))))))
    (GLFW/glfwSetMouseButtonCallback window
      (reify org.lwjgl.glfw.GLFWMouseButtonCallbackI
        (invoke [_ win button action mods]
          (try
            (let [mods (cond-> #{}
                         (pos? (bit-and mods GLFW/GLFW_MOD_SHIFT))   (conj :shift)
                         (pos? (bit-and mods GLFW/GLFW_MOD_CONTROL)) (conj :ctrl)
                         (pos? (bit-and mods GLFW/GLFW_MOD_ALT))     (conj :alt)
                         (pos? (bit-and mods GLFW/GLFW_MOD_SUPER))   (conj :super))]
              (handle-mouse-buttons! win button action mods))
            (catch Exception e
              (println "Mouse Button Callback Exception:" e))))))
    (init window width height)
    (loop []
      (loop []
        (when-let [f (.poll render-queue)]
          (f)
          (recur)))
      (when-not (or (:should-close @state)
                    (GLFW/glfwWindowShouldClose window))
        (GLFW/glfwPollEvents)
        (let [stack (MemoryStack/stackPush)]
          (try
            (update-loop window stack {})
            (finally
              (MemoryStack/stackPop))))
        (recur)))

    (stop!)
    (GLFW/glfwDestroyWindow window)
    (GLFW/glfwTerminate)))

(defn start! []
  (when (and @gl-thread (.isAlive ^Thread @gl-thread))
    (throw (RuntimeException. "Already running")))
  (reset! gl-thread
    (doto (Thread. (fn []
                     (try
                       (-main)
                       (catch Exception e
                         (println "GL thread error:" e)))))
      (.setName "opengl-thread")
      (.setDaemon true)
      (.start))))

(defn load-model [^String path]
  (let [flags (bit-or Assimp/aiProcess_Triangulate
                      Assimp/aiProcess_FlipUVs)]
    (Assimp/aiImportFile path flags)))

(defn extract-mesh [^AIScene scene ^long idx]
  (let [meshes ^PointerBuffer (.mMeshes scene)]
    (let [mesh (AIMesh/create (long (.get meshes idx)))
          mat-idx (.mMaterialIndex mesh)
          mat-ptr ^long (.get (.mMaterials scene) mat-idx)
          material (AIMaterial/create (long mat-ptr))
          mat-color (AIColor4D/create)
          _ (Assimp/aiGetMaterialColor material Assimp/AI_MATKEY_COLOR_DIFFUSE 0 0 ^AIColor4D mat-color)
          ^IntBuffer no-int nil
          ^FloatBuffer no-float nil
          path (AIString/create)
          _ (Assimp/aiGetMaterialTexture material Assimp/aiTextureType_DIFFUSE 0 path no-int no-int no-float no-int no-int no-int)
          data-path (.dataString path)
          verts (.mVertices mesh)
          n (.mNumVertices mesh)
          vert-arr (float-array (* 5 n))
          faces (.mFaces mesh)
          nf (.mNumFaces mesh)
          idx-arr (int-array (* 3 nf))
          uvs (.mTextureCoords mesh 0)]
      (dotimes [i n]
        (let [vert ^AIVector3D (.get verts i)
              uv (when uvs (.get uvs i))
              [^float u ^float v] (if uvs [(.x ^AIVector3D uv) (.y ^AIVector3D uv)] [0.0 0.0])]
          (aset vert-arr (+ (* i 5) 0) (.x vert))
          (aset vert-arr (+ (* i 5) 1) (.y vert))
          (aset vert-arr (+ (* i 5) 2) (.z vert))
          (aset vert-arr (+ (* i 5) 3) u)
          (aset vert-arr (+ (* i 5) 4) v)))
      (dotimes [i nf]
        (let [face ^AIFace (.get faces i)
              idxs (.mIndices face)]
          (aset idx-arr (+ (* i 3) 0) (.get idxs 0))
          (aset idx-arr (+ (* i 3) 1) (.get idxs 1))
          (aset idx-arr (+ (* i 3) 2) (.get idxs 2))))
      {:verts vert-arr :indices idx-arr :mat-idx (.mMaterialIndex mesh)
       :texture-idx (when (.startsWith data-path "*")
                      (parse-long (subs data-path 1)))
       :color [(.r mat-color) (.g mat-color) (.b mat-color) (.a mat-color)]})))

(defn traverse [^AINode node]
  {:node node
   :meshes (let [buf (.mMeshes node)]
             (mapv #(.get buf ^int %) (range (.mNumMeshes node))))
   :transform (node-matrix4f node)
   :children (mapv #(traverse (AINode/create ^long (.get (.mChildren node) ^int %)))
                   (range (.mNumChildren node)))})

(defn load-scene [path]
  (let [scene ^AIScene (load-model path)
        meshes (doall
                 (for [i (range (.mNumMeshes scene))]
                   (extract-mesh scene i)))
        textures (let [texs (.mTextures scene)]
                   (doall
                     (for [i (range (.mNumTextures scene))]
                       (let [ai-tex (AITexture/create (.get texs ^int i))
                             buf (.pcDataCompressed ai-tex)
                             w (int-array 1)
                             h (int-array 1)
                             channels (int-array 1)]
                         {:data (STBImage/stbi_load_from_memory buf w h channels 4)
                          :w (aget w 0) :h (aget h 0)}))))
        hiearchy (traverse (.mRootNode scene))]
    (Assimp/aiReleaseImport scene)
    {:meshes meshes :textures textures :hiearchy hiearchy}))

(defn upload-scene! [scene]
  (let [gl-meshes
        (mapv
          #(let [vbo ^int (GL45/glCreateBuffers)
                 ebo ^int (GL45/glCreateBuffers)
                 vao ^int (GL45/glCreateVertexArrays)]
             (GL45/glNamedBufferStorage vbo ^floats (:verts %)   GL45/GL_DYNAMIC_STORAGE_BIT)
             (GL45/glNamedBufferStorage ebo ^ints (:indices %) GL45/GL_DYNAMIC_STORAGE_BIT)
             (GL45/glVertexArrayElementBuffer vao ebo)
             (GL45/glVertexArrayVertexBuffer vao 0 vbo 0 (* 5 Float/BYTES))

             (GL45/glVertexArrayAttribFormat vao 0 3 GL11/GL_FLOAT false 0)
             (GL45/glVertexArrayAttribBinding vao 0 0)

             (GL45/glVertexArrayAttribFormat vao 1 2 GL11/GL_FLOAT false (* 3 Float/BYTES))
             (GL45/glVertexArrayAttribBinding vao 1 0)

             (GL45/glEnableVertexArrayAttrib vao 0)
             (GL45/glEnableVertexArrayAttrib vao 1)
             {:vbo vbo :vao vao :ebo ebo
              :mesh %
              :indices-count (alength ^ints (:indices %))})
          (:meshes scene))
        gl-textures
        (mapv
          #(let [w (:w %)
                 h (:h %)
                 tex-id (GL45/glCreateTextures GL11/GL_TEXTURE_2D)]
             (GL45/glTextureStorage2D tex-id 1 GL11/GL_RGBA8 w h)
             (GL45/glTextureSubImage2D tex-id 0 0 0 ^int w ^int h GL11/GL_RGBA GL11/GL_UNSIGNED_BYTE ^ByteBuffer (:data %))
             (GL45/glTextureParameteri tex-id GL11/GL_TEXTURE_MIN_FILTER GL11/GL_LINEAR)
             (GL45/glTextureParameteri tex-id GL11/GL_TEXTURE_MAG_FILTER GL11/GL_LINEAR)
             (assoc % :gl-id tex-id))
          (:textures scene))]
    (assoc scene :gl-textures gl-textures :gl-meshes gl-meshes)))

(defn load-shaders! [vert-path frag-path]
  (let [vert (compile-shader GL20/GL_VERTEX_SHADER (slurp vert-path))
        frag (compile-shader GL20/GL_FRAGMENT_SHADER (slurp frag-path))
        program (link-program vert frag)]
    {:program program
     :uniforms {:color (GL20/glGetUniformLocation ^int program "uColor")
                :tex (GL20/glGetUniformLocation ^int program "texture_id")
                :mm (GL20/glGetUniformLocation ^int program "uModel")
                :mv (GL20/glGetUniformLocation ^int program "uView")
                :mp (GL20/glGetUniformLocation ^int program "uProjection")}}))

(comment
  (start!)
  (with-gl (stop!))

  (reset! loaded-scene
          (do
            (when @loaded-scene
              (doseq [tex (:textures @loaded-scene)]
                (when-let [data (:data tex)]
                  (STBImage/stbi_image_free ^ByteBuffer data))))
            (load-scene "assets/model.glb")))
  (reset! uploaded-scene (with-gl (upload-scene! @loaded-scene)))
  (reset! shaders (with-gl (load-shaders! "shaders/base.vert" "shaders/base.frag")))

  (swap! state assoc :current-state :static)

  (with-gl
    (GL11/glClearColor 0.392 0.584 0.929 1.0))
  (with-gl
    (GL11/glClearColor 0.2 0.25 0.3 1.0))

  :-)
