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

import java.util.Map;

import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;

import speiger.src.collections.ints.maps.impl.hash.Int2IntOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2IntMap;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap;
import speiger.src.coreengine.platform.graphics.api.mesh.DynamicMesh;
import speiger.src.coreengine.platform.graphics.api.shader.ShaderPipeline;
import speiger.src.coreengine.platform.graphics.api.shader.VertexBinding;
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 VertexArrayCache {
	Map<ShaderPipeline, CacheEntry> cache = new Object2ObjectOpenHashMap<>();
	
	public void apply(ShaderPipeline pipeline, GLDynamicMesh mesh) {
		CacheEntry entry = cache.get(pipeline);
		if(entry == null) {
			entry = new CacheEntry(GL45.glCreateVertexArrays(), toIds(mesh));
			cache.put(pipeline, entry);
			apply(entry.vao, pipeline, mesh);
			GL30.glBindVertexArray(entry.vao);
			return;
		}
		Int2IntMap newIds = toIds(mesh);
		if(entry.existing.equals(newIds)) {
			GL30.glBindVertexArray(entry.vao);
			return;
		}
		entry.existing = newIds;
		apply(entry.vao, pipeline, mesh);
		GL30.glBindVertexArray(entry.vao);
	}
	
	private void apply(int vao, ShaderPipeline pipeline, DynamicMesh mesh) {
		for(VertexBinding binding : pipeline.attributes()) {
			bindBuffer(vao, binding.bindingIndex(), binding, mesh);
		}
		if(mesh.indeciesBuffer() != null) {
			mesh.indeciesBuffer().bind();
			GL45.glVertexArrayElementBuffer(vao, ((GLVertexBuffer)mesh.indeciesBuffer()).id());
		}
	}
	
	private void bindBuffer(int vao, int binding, VertexBinding info, DynamicMesh mesh) {
		VertexLayout layout = info.layout();
		GLVertexBuffer buffer = (GLVertexBuffer)mesh.buffer(binding);
		buffer.bind();
		GL45.glVertexArrayVertexBuffer(vao, binding, buffer.id(), 0, layout.bytes());
		if(info.instanceOffset() > 0) GL45.glVertexArrayBindingDivisor(vao, binding, info.instanceOffset());
		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());
		}
	}
	
	private Int2IntMap toIds(GLDynamicMesh mesh) {
		Int2IntMap existing = new Int2IntOpenHashMap();
		for(int entry : mesh.bindings()) {
			existing.put(entry, ((GLVertexBuffer)mesh.buffer(entry)).id());
		}
		return existing;
	}
	
	private static class CacheEntry {
		Int2IntMap existing;
		int vao;
		public CacheEntry(int vao, Int2IntMap existing) {
			this.existing = existing;
			this.vao = vao;
		}
		
		
	}
}
