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

import java.util.Objects;

import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL45;

import speiger.src.coreengine.platform.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.platform.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.platform.graphics.api.compute.BarrierType;
import speiger.src.coreengine.platform.graphics.api.compute.ComputePipeline;
import speiger.src.coreengine.platform.graphics.api.core.ComputeCommandBuffer;
import speiger.src.coreengine.platform.graphics.api.sampler.Sampler;
import speiger.src.coreengine.platform.graphics.api.texture.Texture;
import speiger.src.coreengine.platform.graphics.api.utils.AccessType;
import speiger.src.coreengine.platform.graphics.opengl.buffer.GLVertexBuffer;
import speiger.src.coreengine.platform.graphics.opengl.sampler.GLSampler;
import speiger.src.coreengine.platform.graphics.opengl.shader.ShaderInstance;
import speiger.src.coreengine.platform.graphics.opengl.texture.GLTexture;
import speiger.src.coreengine.platform.graphics.opengl.utils.GLStates;
import speiger.src.coreengine.platform.graphics.opengl.utils.GLUtils;

public class GLImmidateComputeCommandBuffer implements ComputeCommandBuffer {
	GLGraphicsDevice device;
	int recorded = 0;
	boolean recording = false;
	boolean removed = false;
	
	ComputePipeline pipeline;
	ShaderInstance shader;
	
	public GLImmidateComputeCommandBuffer(GLGraphicsDevice device) {
		this.device = device;
	}

	protected void ensureDrawing() {
		if(removed) throw new IllegalStateException("CommandBuffer is removed");		
		if(!recording) throw new IllegalStateException("CommandBuffer isn't recording");
	}
	
	@Override
	public boolean isRemoved() { return removed; }
	
	@Override
	public void remove() {
		clear();
		device = null;
		removed = true;
		recorded = 0;
	}
	
	@Override
	public ComputeCommandBuffer begin() {
		if(removed) throw new IllegalStateException("CommandBuffer is removed");
		if(recording) throw new IllegalStateException("CommandBuffer already recording");
		recording = true;
		recorded = 0;
		return this;
	}
	
	@Override
	public ComputeCommandBuffer pipeline(ComputePipeline pipeline) {
		Objects.requireNonNull(pipeline);
		ensureDrawing();
		if(this.pipeline == pipeline) return this;
		this.pipeline = pipeline;
		shader = device.getShader(pipeline);
		if(shader == null) {
			//TODO implement logging
			return this;
		}
		GLStates states = device.states;
		states.shaders.bind(shader);
		recorded++;
		return this;
	}
	
	@Override
	public ComputeCommandBuffer texture(int binding, Texture texture, Sampler sampler) {
		Objects.requireNonNull(texture);
		Objects.requireNonNull(sampler);
		ensureDrawing();
		Objects.requireNonNull(shader, "No Pipeline found");
		Objects.requireNonNull(shader.samplers().byIndex(binding), "Texture["+binding+"] binding not found");
		GLStates states = device.states;
		states.textures.bind(binding, ((GLTexture)texture).id());
		states.samplers.bind(binding, ((GLSampler)sampler).id());
		recorded++;
		return this;
	}
	
	@Override
	public ComputeCommandBuffer texture(int binding, Texture texture, AccessType access) {
		Objects.requireNonNull(texture);
		Objects.requireNonNull(access);
		ensureDrawing();
		Objects.requireNonNull(shader, "No Pipeline found");
		Objects.requireNonNull(shader.buffers().byIndex(binding), "Texture["+binding+"] binding not found");
		GL45.glBindImageTexture(0, binding, ((GLTexture)texture).id(), false, 0, GLUtils.toGL(access), GLUtils.toGLInternal(texture.settings().internal()));
		recorded++;
		return this;
	}
	
	@Override
	public ComputeCommandBuffer uniform(int binding, VertexBuffer buffer) {
		Objects.requireNonNull(buffer);
		if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
		ensureDrawing();
		Objects.requireNonNull(shader, "No Pipeline found");
		Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
		GL30.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, binding, ((GLVertexBuffer)buffer).id());
		recorded++;
		return this;
	}

	@Override
	public ComputeCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size) {
		Objects.requireNonNull(buffer);
		if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
		ensureDrawing();
		Objects.requireNonNull(shader, "No Pipeline found");
		Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
		GL30.glBindBufferRange(GL31.GL_UNIFORM_BUFFER, binding, ((GLVertexBuffer)buffer).id(), offset, size);
		recorded++;
		return this;
	}
	
	@Override
	public ComputeCommandBuffer storage(int binding, VertexBuffer buffer) {
		Objects.requireNonNull(buffer);
		if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
		ensureDrawing();
		Objects.requireNonNull(shader, "No Pipeline found");
		Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
		GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, binding, ((GLVertexBuffer)buffer).id());
		recorded++;
		return this;
	}

	@Override
	public ComputeCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size) {
		Objects.requireNonNull(buffer);
		if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
		ensureDrawing();
		Objects.requireNonNull(shader, "No Pipeline found");
		Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
		GL30.glBindBufferRange(GL43.GL_SHADER_STORAGE_BUFFER, binding, ((GLVertexBuffer)buffer).id(), offset, size);
		recorded++;
		return this;
	}
	
	@Override
	public ComputeCommandBuffer dispatch(int numGroupsX, int numGroupsY, int numGroupsZ) {
		Objects.requireNonNull(shader);
		if(numGroupsX < 1) throw new IllegalArgumentException("X has to be 1 or more");
		if(numGroupsY < 1) throw new IllegalArgumentException("Y has to be 1 or more");
		if(numGroupsZ < 1) throw new IllegalArgumentException("Z has to be 1 or more");
		GL45.glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
		return this;
	}
	
	@Override
	public ComputeCommandBuffer barrier(BarrierType... types) {
		int result = 0;
		for(BarrierType type : types) {
			result |= GLUtils.toGL(type);
		}
		GL42.glMemoryBarrier(result);
		return this;
	}
	
	@Override
	public ComputeCommandBuffer end() {
		clear();
		return this;
	}
	
	private void clear() {
		recording = false;
		pipeline = null;
		shader = null;
	}

	@Override
	public int commandCount() {
		return recorded;
	}
	
}
