package speiger.src.coreengine.rendering.utils;

import java.util.Iterator;
import java.util.Set;

import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL33;

import speiger.src.collections.ints.collections.IntStack;
import speiger.src.collections.ints.lists.IntArrayList;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.sets.ObjectLinkedOpenHashSet;
import speiger.src.collections.utils.Stack;

public class GLStamper {
	public static GLStamper INSTANCE = new GLStamper();
	Object2ObjectMap<String, Set<GLStamp>> stamps = Object2ObjectMap.builder().linkedMap();
	Stack<GLStamp> unusedStamps = new ObjectArrayList<>();
	IntStack queryIDs = new IntArrayList();
	
	public GLStamp createStamp(String ownerID) {
		Set<GLStamp> list = stamps.supplyIfAbsent(ownerID, ObjectLinkedOpenHashSet::new);
		GLStamp stamp = unusedStamps.isEmpty() ? new GLStamp(this) : unusedStamps.pop();
		stamp.setOwner(ownerID);
		list.add(stamp);
		return stamp;
	}
	
	public void removeOwner(String ownerID) {
		Set<GLStamp> set = stamps.remove(ownerID);
		if(set == null) return;
		set.forEach(GLStamp::release);
	}
	
	public void cleanup() {
		if(!stamps.isEmpty()) {
			for(Iterator<Set<GLStamp>> iter = stamps.values().iterator();iter.hasNext();iter.remove()) {
				iter.next().forEach(GLStamp::release);
			}
		}
		if(queryIDs.isEmpty()) return;
		GL15.glDeleteQueries(queryIDs.toIntArray());
		queryIDs.clear();
	}
	
	protected void release(GLStamp stamp) {
		Set<GLStamp> set = stamps.get(stamp.getOwner());
		if(set != null) set.remove(stamp);
		unusedStamps.push(stamp);
	}
	
	protected int getQueryID() {
		int id = queryIDs.isEmpty() ? GL15.glGenQueries() : queryIDs.pop();
		GL33.glQueryCounter(id, GL33.GL_TIMESTAMP);
		return id;
	}
	
	protected void releaseQueryID(int id) {
		if(id != -1) queryIDs.push(id);
	}
	
	public static class GLStamp {
		GLStamper stamp;
		String owner;
		int startID = -1;
		int endID = -1;
		
		private GLStamp(GLStamper owner) {
			stamp = owner;
		}
		
		void setOwner(String owner) { this.owner = owner; }
		String getOwner() { return owner; }
		
		public GLStamp start() {
			if(startID == -1) startID = stamp.getQueryID();
			return this;
		}
		
		public GLStamp stop() {
			if(endID == -1) endID = stamp.getQueryID();
			return this;
		}
		
		public void release() {
			stamp.releaseQueryID(startID);
			stamp.releaseQueryID(endID);
			stamp.release(this);
			startID = -1;
			endID = -1;
		}
		
		public boolean isFinished() { return GL15.glGetQueryObjectui(endID, GL15.GL_QUERY_RESULT_AVAILABLE) == 1; }
		public long getResult() { return GL33.glGetQueryObjectui64(endID, GL15.GL_QUERY_RESULT) - GL33.glGetQueryObjectui64(startID, GL15.GL_QUERY_RESULT); }
	}
}
