package cn.lambdalib.pipeline.api;


import cn.lambdalib.pipeline.api.Material.Instance;
import cn.lambdalib.pipeline.api.Material.Layout;
import cn.lambdalib.pipeline.api.Material.Mesh;
import cn.lambdalib.pipeline.api.state.StateContext;
import cn.lambdalib.pipeline.core.GLBuffer;
import cn.lambdalib.pipeline.core.VAO;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.lwjgl.BufferUtils;

import java.nio.FloatBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glUseProgram;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL33.glVertexAttribDivisor;

public class GraphicPipeline {

    private Map<Material, Multimap<SubGroup, Instance>> drawCalls = new HashMap<>();

    private final StateContext context = new StateContext();

    public void draw(Material mat, Mesh mesh, Instance instance) {
        put(mat, new SubGroup(mesh, true, -1), instance);
    }

    public void draw(Material mat, Mesh mesh, Instance instance, int subMeshIndex) {
        put(mat, new SubGroup(mesh, false, subMeshIndex), instance);
    }

    /**
     * @return The state context that will serve as the default value when this pipeline {@link #flush}s.
     */
    public StateContext stateContext() {
        return context;
    }

    private void put(Material mat, SubGroup group, Instance instance) {
        if (!drawCalls.containsKey(mat)) {
            drawCalls.put(mat, ArrayListMultimap.create());
        }

        drawCalls.get(mat).put(group, instance);
    }

    public void flush() {
        for (Material mat : drawCalls.keySet()) {
            glUseProgram(mat.program.getProgramID());
            // --------- Update states and uniforms -----------
            context.apply();
            mat.stateContext().apply();
            mat.updateUniforms();
            // -------------------------------------------------

            Multimap<SubGroup, Instance> thisMatDraws = drawCalls.get(mat);

            GLBuffer instanceVBO = GLBuffer.create();
            VAO vao = VAO.create();
            glBindVertexArray(vao.getID());

            // Per-instance data pointer
            glBindBuffer(GL_ARRAY_BUFFER, instanceVBO.getID());

            for (Layout layout : mat.instanceLayouts) {
                glEnableVertexAttribArray(layout.location);
                glVertexAttribPointer(layout.location, layout.attrType.dimension, GL_FLOAT, false,
                        mat.instanceFloats * 4,
                        layout.floatPadding * 4);
                glVertexAttribDivisor(layout.location, 1);
            }

            glBindBuffer(GL_ARRAY_BUFFER, 0);

            for (SubGroup group : thisMatDraws.keySet()) {
                Mesh mesh = group.mesh;
                Collection<Instance> instances = thisMatDraws.get(group);

                // Per-vertex data pointer
                glBindBuffer(GL_ARRAY_BUFFER, mesh.getVBO().getID());
                for (Layout layout : mat.vertexLayouts) {
                    glEnableVertexAttribArray(layout.location);
                    glVertexAttribPointer(layout.location, layout.attrType.dimension, GL_FLOAT, false,
                            mat.vertexFloats * 4,
                            layout.floatPadding * 4);
                    glVertexAttribDivisor(layout.location, 0);
                }
                glBindBuffer(GL_ARRAY_BUFFER, 0);

                // Update instance bufffer
                FloatBuffer buffer = BufferUtils.createFloatBuffer(mat.instanceFloats * instances.size());
                for (Instance i : instances) {
                    buffer.put(i.values);
                }
                buffer.flip();

                glBindBuffer(GL_ARRAY_BUFFER, instanceVBO.getID());
                glBufferData(GL_ARRAY_BUFFER, buffer, GL_DYNAMIC_DRAW);
                glBindBuffer(GL_ARRAY_BUFFER, 0);

                // Bind IBO
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, group.getIBO().getID());

                glDrawElementsInstanced(GL_TRIANGLES, mesh.getIndiceCount(), GL_UNSIGNED_INT, 0, instances.size());

                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
            }

            glBindVertexArray(0);
            glUseProgram(0);
        }

        drawCalls.clear();
    }

    private class SubGroup {

        final Mesh mesh;
        final boolean all;
        final int iboID;

        SubGroup(Mesh mesh, boolean all, int iboID) {
            this.mesh = mesh;
            this.all = all;
            this.iboID = iboID;
        }

        GLBuffer getIBO() {
            return all ? mesh.getIBOAll() : mesh.getIBOSub(iboID);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            SubGroup subGroup = (SubGroup) o;

            if (all != subGroup.all) return false;
            if (iboID != subGroup.iboID) return false;
            return mesh != null ? mesh.equals(subGroup.mesh) : subGroup.mesh == null;
        }

        @Override
        public int hashCode() {
            int result = mesh != null ? mesh.hashCode() : 0;
            result = 31 * result + (all ? 1 : 0);
            result = 31 * result + iboID;
            return result;
        }
    }


}

@@ -34,158 +34,8 @@
 ;%0A%0A%0A
-import cn.lambdalib.pipeline.api.Material.Instance;%0Aimport cn.lambdalib.pipeline.api.Material.Layout;%0Aimport cn.lambdalib.pipeline.api.Material.Mesh;%0A
 impo
@@ -894,32 +894,41 @@
 timap%3CSubGroup, 
+Material.
 Instance%3E%3E drawC
@@ -1041,32 +1041,41 @@
 w(Material mat, 
+Material.
 Mesh mesh, Insta
@@ -1061,32 +1061,41 @@
 rial.Mesh mesh, 
+Material.
 Instance instanc
@@ -1199,16 +1199,25 @@
 al mat, 
+Material.
 Mesh mes
@@ -1219,16 +1219,25 @@
 h mesh, 
+Material.
 Instance
@@ -1584,24 +1584,33 @@
 roup group, 
+Material.
 Instance ins
@@ -2182,16 +2182,25 @@
 bGroup, 
+Material.
 Instance
@@ -2487,32 +2487,41 @@
            for (
+Material.
 Layout layout : 
@@ -2996,16 +2996,25 @@
         
+Material.
 Mesh mes
@@ -3056,16 +3056,25 @@
 lection%3C
+Material.
 Instance
@@ -3247,16 +3247,25 @@
    for (
+Material.
 Layout l
@@ -3867,16 +3867,25 @@
    for (
+Material.
 Instance
@@ -4635,16 +4635,25 @@
   final 
+Material.
 Mesh mes
@@ -4725,16 +4725,25 @@
 ubGroup(
+Material.
 Mesh mes

Fix cross-compilation issue when referencing GraphicPipeline in scala