package speiger.src.coreengine.platform.graphics.api.shader;

import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.platform.graphics.api.shader.states.DrawMode;
import speiger.src.coreengine.platform.graphics.api.shader.states.PolygonMode;
import speiger.src.coreengine.platform.graphics.api.shader.states.ShaderType;
import speiger.src.coreengine.platform.graphics.api.shader.states.Bindings.BufferBinding;
import speiger.src.coreengine.platform.graphics.api.shader.states.Bindings.BufferMode;
import speiger.src.coreengine.platform.graphics.api.shader.states.Bindings.TextureBinding;
import speiger.src.coreengine.platform.graphics.api.shader.states.Bindings.TextureMode;
import speiger.src.coreengine.platform.graphics.api.texture.states.TextureType;
import speiger.src.coreengine.platform.graphics.api.vertex.VertexLayout;

public record ShaderPipeline(ID id, Map<ShaderType, ID> shaders, List<VertexBinding> attributes, List<BufferBinding> uniforms, List<TextureBinding> textures, RasterizerState rasterizer, ColorTarget colorTarget, DepthTarget depthTarget) {
	
	public ShaderPipeline {
		Objects.requireNonNull(id);
		if(shaders.isEmpty()) throw new IllegalStateException("Shaders isn't allowed to be empty");
		if(shaders.containsKey(ShaderType.COMPUTE)) throw new IllegalStateException("Graphics Pipelines are not allowed to have Compute Shaders");
		Objects.requireNonNull(uniforms);
		Objects.requireNonNull(textures);
		Objects.requireNonNull(rasterizer);
		Objects.requireNonNull(colorTarget);
		Objects.requireNonNull(depthTarget);
	}
	
	public static Builder builder(ID id) {
		return new Builder(id);
	}
	
	public static class Builder {
		ID id;
		EnumMap<ShaderType, ID> shaders = new EnumMap<>(ShaderType.class);
		ObjectList<VertexBinding> attributes = new ObjectArrayList<>();
		ObjectList<BufferBinding> uniforms = new ObjectArrayList<>();
		ObjectList<TextureBinding> textures = new ObjectArrayList<>();
		RasterizerState rasterizer = RasterizerState.DEFAULT;
		ColorTarget colorTarget = ColorTarget.DEFAULT;
		DepthTarget depthTarget = DepthTarget.DEFAULT;
		
		private Builder(ID id) {
			this.id = Objects.requireNonNull(id);	
		}
		
		public Builder withStage(ShaderType type, ID location) {
			shaders.put(Objects.requireNonNull(type, "Shouldn't be null"), Objects.requireNonNull(location, "Resource shouldn't be null"));
			return this;
		}
		
		public Builder withUniform(String name, int binding, int slot, int bytes) {
			uniforms.add(new BufferBinding(Objects.requireNonNull(name), binding, slot, bytes, BufferMode.UNIFORM));
			return this;
		}
		
		public Builder withBuffer(String name, int binding, int slot, int bytes) {
			uniforms.add(new BufferBinding(Objects.requireNonNull(name), binding, slot, bytes, BufferMode.STORAGE));
			return this;
		}
		
		public Builder withTexture(String name, int binding, int slot, TextureType type) {
			textures.add(new TextureBinding(Objects.requireNonNull(name), binding, slot, type, TextureMode.SAMPLER));
			return this;
		}
		
		public Builder withStorage(String name, int binding, int slot, TextureType type) {
			textures.add(new TextureBinding(Objects.requireNonNull(name), binding, slot, type, TextureMode.IMAGE_BUFFER));
			return this;
		}
		
		public Builder withRasterizer(DrawMode draw, PolygonMode polygon, boolean cullBack) {
			rasterizer = new RasterizerState(Objects.requireNonNull(draw), Objects.requireNonNull(polygon), cullBack);
			return this;
		}
		
		public Builder withFormat(VertexLayout layout) {
			return withFormat(attributes.size(), layout);
		}
		
		public Builder withInstanceFormat(VertexLayout layout) {
			return withFormat(attributes.size(), layout, 1);
		}
		
		public Builder withInstanceFormat(VertexLayout layout, int instanceCount) {
			return withFormat(attributes.size(), layout, instanceCount);
		}
		
		public Builder withFormat(int bindingIndex, VertexLayout layout) {
			return withFormat(bindingIndex, layout, 0);
		}
		
		public Builder withFormat(int bindingIndex, VertexLayout layout, int instanceCount) {
			attributes.add(new VertexBinding(bindingIndex, Objects.requireNonNull(layout), instanceCount));
			return this;
		}
		
		public Builder withColorTarget(ColorTarget target) {
			this.colorTarget = Objects.requireNonNull(target);
			return this;
		}
		
		public Builder withDepthTarget(DepthTarget target) {
			this.depthTarget = Objects.requireNonNull(target);
			return this;
		}
		
		public ShaderPipeline build() {
			if(shaders.isEmpty()) throw new IllegalStateException("Shaders must be provided in a Shader Pipeline");
			if(shaders.containsKey(ShaderType.COMPUTE)) throw new IllegalStateException("Graphics Pipelines are not allowed to have Compute Shaders");
			return new ShaderPipeline(id, Collections.unmodifiableMap(shaders), attributes.unmodifiable(), uniforms.unmodifiable(), textures.unmodifiable(), Objects.requireNonNull(rasterizer, "A Rasterizer should be defined"), colorTarget, depthTarget);
		}
	}
}
