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

import org.jspecify.annotations.Nullable;
import org.lwjgl.opengl.GL45;

import speiger.src.collections.ints.collections.IntIterator;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.platform.graphics.api.buffer.states.IndeciesType;
import speiger.src.coreengine.platform.graphics.api.mesh.Mesh;
import speiger.src.coreengine.platform.graphics.api.vertex.VertexLayout;
import speiger.src.coreengine.platform.graphics.api.vertex.VertexLayout.Element;
import speiger.src.coreengine.platform.graphics.opengl.buffer.GLVertexBuffer;
import speiger.src.coreengine.platform.graphics.opengl.utils.GLUtils;

public class GLMesh extends Mesh {
	int vao;
	boolean managed;
	
	public GLMesh(Int2ObjectMap<LayoutInfo> layouts, Int2ObjectMap<VertexBuffer> buffers, @Nullable VertexBuffer indeciesBuffer, @Nullable IndeciesType indeciesType, boolean managed) {
		super(layouts, buffers, indeciesBuffer, indeciesType);
		this.managed = managed;
		vao = GL45.glCreateVertexArrays();
		if(managed) {
			bindElement();
			for(IntIterator iter = layouts.keySet().iterator();iter.hasNext();) {
				bindBuffer(iter.nextInt());
			}
		}
	}
	
	public int id() {
		return vao;
	}
	
	@Override
	public boolean equals(Object obj) {
		return obj instanceof GLMesh mesh && mesh.vao == vao;
	}
	
	@Override
	public boolean isRemoved() { return vao == 0; }
	
	@Override
	public GLMesh buffer(int binding, VertexBuffer buffer) {
		super.buffer(binding, buffer);
		bindBuffer(binding);
		return this;
	}
	
	@Override
	public GLMesh indexBuffer(VertexBuffer indexBuffer, IndeciesType indeciesType) {
		super.indexBuffer(indexBuffer, indeciesType);
		bindElement();
		return this;
	}
	
	private void bindElement() {
		indeciesBuffer.bind();
		GL45.glVertexArrayElementBuffer(vao, ((GLVertexBuffer)indeciesBuffer).id());
	}
	
	private void bindBuffer(int binding) {
		LayoutInfo info = layouts.get(binding);
		VertexLayout layout = info.layout();
		GLVertexBuffer buffer = (GLVertexBuffer)buffers.get(binding);
		buffer.bind();
		GL45.glVertexArrayVertexBuffer(vao, binding, buffer.id(), 0, layout.bytes());
		if(info.instanceCount() > 0) GL45.glVertexArrayBindingDivisor(vao, binding, info.instanceCount());
		int index = 0;
		for(Element element : layout) {
			GL45.glVertexArrayAttribFormat(vao, element.index(), element.size(), GLUtils.toGL(element.type()), element.normalized(), layout.offset(index++));
			GL45.glVertexArrayAttribBinding(vao, element.index(), binding);
			GL45.glEnableVertexArrayAttrib(vao, element.index());
		}
	}
	
	@Override
	public void remove() {
		if(vao == 0) return;
		GL45.glDeleteVertexArrays(vao);
		vao = 0;
		if(!managed) return;
		if(indeciesBuffer != null) indeciesBuffer.remove();
		buffers.values().forEach(VertexBuffer::close);
	}
	
	@Override
	public void closeWithBuffers() {
		boolean wasManaged = managed;
		managed = true;
		close();
		managed = wasManaged;
	}
}