package me.lucaspickering.terra.render;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;

import java.awt.Color;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.Objects;

import me.lucaspickering.utils.Point;

public class VertexBufferObject {


    private static final int VERTEX_SIZE = 2;

    public enum ColorMode {
        RGB(3) {
            @Override
            float[] getComponents(Color color) {
                color.getColorComponents(colorArray);
                return colorArray;
            }
        },
        RGBA(4) {
            @Override
            float[] getComponents(Color color) {
                color.getComponents(colorArray);
                return colorArray;
            }
        };

        private final int size;
        protected final float[] colorArray;

        ColorMode(int size) {
            this.size = size;
            colorArray = new float[size];
        }

        abstract float[] getComponents(Color color);

        private int bytes() {
            return size * Float.BYTES;
        }
    }

    public static class Builder {

        private int numVertices;
        private ColorMode colorMode = ColorMode.RGB;
        private Runnable drawFunction;

        public Builder setColorMode(ColorMode colorMode) {
            Objects.requireNonNull(colorMode);
            this.colorMode = colorMode;
            return this;
        }

        public Builder setNumVertices(int numVertices) {
            this.numVertices = numVertices;
            return this;
        }

        public Builder setDrawFunction(Runnable drawFunction) {
            this.drawFunction = drawFunction;
            return this;
        }

        public VertexBufferObject build() {
            if (drawFunction == null) {
                throw new IllegalStateException("Draw function cannot be null");
            }
            return new VertexBufferObject(numVertices, colorMode, drawFunction);
        }
    }

    private final int numVertices;
    private final ColorMode colorMode;
    private final Runnable drawFunction;

    private int vertexHandle;
    private final DoubleBuffer vertexBuffer;

    private final FloatBuffer colorBuffer;
    private int colorHandle;

    private VertexBufferObject(int numVertices, ColorMode colorMode, Runnable drawFunction) {
        this.numVertices = numVertices;
        this.colorMode = colorMode;
        this.drawFunction = drawFunction;

        vertexBuffer = BufferUtils.createDoubleBuffer(VERTEX_SIZE * numVertices);
        colorBuffer = BufferUtils.createFloatBuffer(colorMode.size * numVertices);
    }

    public int getVertexHandle() {
        return vertexHandle;
    }

    public void setVertexHandle(int vertexHandle) {
        this.vertexHandle = vertexHandle;
    }

    public int getColorHandle() {
        return colorHandle;
    }

    public void setColorHandle(int colorHandle) {
        this.colorHandle = colorHandle;
    }

    public void addVertex(Point point) {
        vertexBuffer.put(point.x());
        vertexBuffer.put(point.y());
    }

    public void addColor(Color color) {
        colorBuffer.put(colorMode.getComponents(color)); // Add the color to the buffer
    }

    public void bindVertexBuffer(int usage) {
        vertexBuffer.rewind(); // Reset the buffer before binding
        vertexHandle = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexHandle);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, usage);
        GL30.glBindVertexArray(0);
    }

    public void bindColorBuffer(int usage) {
        colorBuffer.rewind(); // Reset the buffer before binding
        colorHandle = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, colorHandle);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, colorBuffer, usage);
        GL30.glBindVertexArray(0);
    }

    /**
     * Sets the color at the given offset. Returns the number of bytes set. The color after this
     * one in the buffer will be located at the given offset plus the return value of this call.
     *
     * @param offset the position of the color in the buffer (in bytes)
     * @param color  the new color to use
     * @return the number of bytes set
     */
    public int setVertexColor(long offset, Color color) {
        GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, offset, colorMode.getComponents(color));
        return colorMode.bytes();
    }

    public void draw() {
        // Set up the vertex buffer
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexHandle);
        GL11.glVertexPointer(VERTEX_SIZE, GL11.GL_DOUBLE, 0, 0L);

        // Set up the color buffer
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, colorHandle);
        GL11.glColorPointer(colorMode.size, GL11.GL_FLOAT, 0, 0L);

        GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
        GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);

        drawFunction.run(); // Do the actual draw

        GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
        GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
    }
}

@@ -182,38 +182,8 @@
 or;%0A
-import java.nio.DoubleBuffer;%0A
 impo
@@ -2182,22 +2182,21 @@
 e final 
-Double
+Float
 Buffer v
@@ -2540,14 +2540,13 @@
 eate
-Double
+Float
 Buff
@@ -3066,24 +3066,32 @@
 xBuffer.put(
+(float) 
 point.x());%0A
@@ -3115,16 +3115,24 @@
 fer.put(
+(float) 
 point.y(
@@ -3341,311 +3341,210 @@
 rtex
-Buffer.rewind(); // Reset the buffer before binding%0A        vertexHandle = GL15.glGenBuffers(
+Handle = bindBuffer(vertexBuffer, usage
 );%0A    
+%7D%0A%0A
     
-GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexHandle);%0A        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, usage);%0A        GL30.glBindVertexArray(0);%0A    %7D%0A%0A    public void bindColorBuffer(
+public void bindColorBuffer(int usage) %7B%0A        colorHandle = bindBuffer(colorBuffer, usage);%0A    %7D%0A%0A    private int bindBuffer(FloatBuffer buffer, 
 int 
@@ -3556,30 +3556,25 @@
 ) %7B%0A        
-colorB
+b
 uffer.rewind
@@ -3612,38 +3612,43 @@
 binding%0A        
-colorH
+final int h
 andle = GL15.glG
@@ -3700,38 +3700,33 @@
 L_ARRAY_BUFFER, 
-colorH
+h
 andle);%0A        
@@ -3765,22 +3765,17 @@
 BUFFER, 
-colorB
+b
 uffer, u
@@ -3816,16 +3816,39 @@
 ray(0);%0A
+        return handle;%0A
     %7D%0A%0A 
@@ -4585,14 +4585,13 @@
 .GL_
-DOUBLE
+FLOAT
 , 0,

Change VBOs to use FloatBuffers for vertices, reduced memory footprint by approximately 25% (~240MB down to ~180MB)