package speiger.src.coreengine.platform.graphics.opengl.buffer;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;

import org.jspecify.annotations.NonNull;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL45;
import org.lwjgl.system.MemoryUtil;

import speiger.src.collections.ints.misc.pairs.IntObjectPair;
import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.platform.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.platform.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.platform.graphics.api.utils.BufferOwner;
import speiger.src.coreengine.platform.graphics.opengl.utils.GLUtils;

public class GLVertexBuffer extends VertexBuffer {
	int id;
	public GLVertexBuffer(BufferType type, BufferState usage) {
		super(type, usage);
		id = GL45.glCreateBuffers();
	}
	
	public GLVertexBuffer(BufferType type, BufferState usage, int totalBytes) {
		super(type, usage);
		id = GL45.glCreateBuffers();
		bind().allocate(totalBytes).unbind();
	}
	
	public int id() {
		if(id == 0) throw new IllegalStateException("Buffer is already removed");
		return id;
	}
	
	@Override
	public boolean isRemoved() {
		return id == 0;
	}
	
	@Override
	public void remove() {
		if(id == 0) return;
		GL15.glDeleteBuffers(id);
		id = 0;
	}
	
	@Override
	public GLVertexBuffer bind() {
		GL15.glBindBuffer(GLUtils.toGL(type), id);
		return this;
	}
	
	@Override
	public GLVertexBuffer unbind() {
		GL15.glBindBuffer(GLUtils.toGL(type), 0);
		return this;
	}
	
	@Override
	public GLVertexBuffer allocate(int totalBytes) {
		this.size = totalBytes;
		GL45.glNamedBufferData(id, totalBytes, GLUtils.toGL(usage));
		return this;
	}
	
	@Override
	public GLVertexBuffer set(long pointer, int totalBytes, BufferOwner ownership) {
		this.size = totalBytes;
		GL45.nglNamedBufferData(id, totalBytes, pointer, GLUtils.toGL(usage));
		return this;
	}
	
	@Override
	public GLVertexBuffer fill(long pointer, int totalBytes, int offset, BufferOwner ownerhip) {
		GL45.nglNamedBufferSubData(id, offset, totalBytes, pointer);
		return this;
	}
	
	@Override
	public GLVertexBuffer read(long pointer, int totalBytes, int offset, @NonNull Runnable completion) {
		Objects.requireNonNull(completion, "Completion has to be required otherwise you can't see when the data is there");
		GL45.nglGetNamedBufferSubData(id, offset, totalBytes, pointer);
		completion.run();
		return this;
	}
	
	@Override
	public GLVertexBuffer fill(int offset, List<IntObjectPair<byte[]>> data) {
		if(data.size() > 0) {
			ByteBuffer buffer = GL45.glMapNamedBuffer(id, GL15.GL_WRITE_ONLY);
			for(int i = 0,m = data.size();i < m;i++) {
				IntObjectPair<byte[]> entry = data.get(i);
				buffer.position(entry.getIntKey() + offset);
				buffer.put(entry.getValue());
			}
			buffer.flip();
			if(!GL45.glUnmapNamedBuffer(id)) System.out.println("Memory Corruption?");
		}
		return this;
	}
	
	@Override
	public GLVertexBuffer shrink(int newSize) {
		if(size <= newSize) throw new IllegalArgumentException("New Size ["+newSize+"] is bigger then the buffer ["+size+"]");
		ByteBuffer buffer = GL45.glMapNamedBuffer(id, GL15.GL_READ_WRITE);
		if(size != buffer.remaining()) throw new IllegalStateException("Grow Function found inconsisten data: Found=["+buffer.remaining()+"], Expected=["+size+"]");
		ByteBuffer newBuff = MemoryUtil.memAlloc(newSize);
		MemoryUtil.memCopy(MemoryUtil.memAddress(buffer), MemoryUtil.memAddress(newBuff), newSize);
		newBuff.position(newSize).flip();
		GL45.glUnmapNamedBuffer(id);
		return set(MemoryUtil.memAddress(newBuff), newSize, BufferOwner.GIVEN);
	}
	
	@Override
	public GLVertexBuffer grow(int newSize) {
		if(size >= newSize) throw new IllegalArgumentException("New Size ["+newSize+"] is smaller then the buffer ["+size+"]");
		ByteBuffer buffer = GL45.glMapNamedBuffer(id, GL15.GL_READ_ONLY);
		if(size != buffer.remaining()) throw new IllegalStateException("Grow Function found inconsisten data: Found=["+buffer.remaining()+"], Expected=["+size+"]");
		ByteBuffer newBuff = MemoryUtil.memAlloc(newSize);
		MemoryUtil.memCopy(MemoryUtil.memAddress(buffer), MemoryUtil.memAddress(newBuff), size);
		newBuff.position(newSize).flip();
		GL45.glUnmapNamedBuffer(id);
		return set(MemoryUtil.memAddress(newBuff), newSize, BufferOwner.GIVEN);
	}
	
	@Override
	public GLVertexBuffer shift(int source, int dest, int length) {
		if(source < 0 || source + length >= size) throw new ArrayIndexOutOfBoundsException();
		if(dest < 0 || dest + length >= size) throw new ArrayIndexOutOfBoundsException();
		GL45.glCopyNamedBufferSubData(id, id, source, dest, length);
		return this;
	}
	
}
