(ns nol.core
  (:import [org.lwjgl.assimp Assimp AIScene AIMesh AIVector3D AIFace]
           [org.lwjgl.glfw GLFW GLFWKeyCallbackI GLFWErrorCallbackI]
           [org.lwjgl.opengl GL GL45 GL33 GL20 GL15 GL11 GL30]
           [org.lwjgl.system MemoryStack MemoryUtil]
           [org.joml Matrix4f]
           [java.nio FloatBuffer]))

(def vertex-shader-text (slurp "shaders/base.vert"))

(def fragment-shader-text (slurp "shaders/base.frag"))

(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))

(def state (atom {:last-time (GLFW/glfwGetTime)}))

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

(def vertices (float-array [0.0 0.5 0.0
                            -0.5 0.5 0.0
                            0.5 -0.5 0.0]))

(defn- update-loop [window stack data]
  (let [w-buf (.mallocInt stack 1)
        h-buf (.mallocInt stack 1)
        _     (GLFW/glfwGetFramebufferSize window w-buf h-buf)
        w     (.get w-buf 0)
        h     (.get h-buf 0)
        current-time (GLFW/glfwGetTime)]
    (GL11/glViewport 0 0 w h)
    (GL11/glClear GL11/GL_COLOR_BUFFER_BIT)

    (swap! state assoc :last-time current-time)

    (GLFW/glfwSwapBuffers window)))

(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 [window (GLFW/glfwCreateWindow 960 592 "NOL" MemoryUtil/NULL MemoryUtil/NULL)]
    (when (= window MemoryUtil/NULL)
      (GLFW/glfwTerminate)
      (throw (RuntimeException. "Failed to create window")))

    (GLFW/glfwMakeContextCurrent window)
    (GL/createCapabilities)
    (GLFW/glfwSwapInterval 1)

    (GLFW/glfwSetKeyCallback window
      (reify GLFWKeyCallbackI
        (invoke [_ win key scancode action mods]
          (when (and (= key GLFW/GLFW_KEY_ESCAPE) (= action GLFW/GLFW_PRESS))
            (GLFW/glfwSetWindowShouldClose win true)))))

    (loop []
      (when-not (GLFW/glfwWindowShouldClose window)
        (GLFW/glfwPollEvents)
        (let [stack (MemoryStack/stackPush)]
          (try
            (update-loop window stack)
            (finally
              (MemoryStack/stackPop))))
        (recur)))

    (GLFW/glfwDestroyWindow window)
    (GLFW/glfwTerminate)))

(comment
  (-main)
  (def ^AIScene scene (load-model "assets/model.glb"))
  (.mNumMeshes scene) ;; 54
  (.mNumMaterials scene) ;; 17
  :-)
