package speiger.src.coreengine.platform.graphics.api.vertex.builder;

import java.nio.ByteBuffer;
import java.util.Objects;

import org.lwjgl.system.MemoryUtil;

import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.platform.graphics.api.shader.states.DrawMode;
import speiger.src.coreengine.platform.graphics.api.vertex.VertexLayout;
import speiger.src.coreengine.platform.graphics.api.vertex.VertexLayout.Element;

public class VertexBuilder implements IVertexBuffer, AutoCloseable {
	boolean autoAlign;
	boolean noFree = false;
	DrawMode mode;
	ByteBuffer buffer;
	VertexLayout layout;
	int vertecies;
	int index;
	
	int totalStoredBytes;
	int vertexBytes;
	int elementBytes;
	int lastVertecies = 0;
	
	public VertexBuilder(int size) {
		this(MemoryUtil.memAlloc(size));
	}
	
	public VertexBuilder(ByteBuffer buffer) {
		this.buffer = buffer;
	}
	
	public VertexBuilder withAutoAlign() {
		this.autoAlign = true;
		return this;
	}
	
	public VertexBuilder withoutFree() {
		noFree = true;
		return this;
	}
	
	public VertexBuilder start(DrawMode mode, VertexLayout layout) {
		if(index != 0) throw new IllegalStateException("Can't change Layout mid Writing");
		if(autoAlign && this.layout != layout) {
			totalStoredBytes = MathUtils.roundAlign(totalStoredBytes, layout.bytes());
			ensureCapacity(totalStoredBytes + (layout.bytes() * mode.primitiveLength()));
		}
		this.mode = Objects.requireNonNull(mode);
		this.layout = Objects.requireNonNull(layout);
		return this;
	}
	
	public VertexBuilder reset() {
		index = 0;
		vertecies = 0;
		elementBytes = 0;
		totalStoredBytes = 0;
		vertexBytes = 0;
		lastVertecies = 0;
		return this;
	}
	
	public boolean hasVertecies() { return lastVertecies < vertecies; }
	public boolean is(DrawMode mode, VertexLayout layout) { return this.mode == mode && this.layout == layout; }
	public DrawMode mode() { return mode; }
	public VertexLayout getFormat() { return layout; }
	public long memoryPointer() { return MemoryUtil.memAddress(buffer); }
	public int totalStoredBytes() { return totalStoredBytes; }
	
	@Override
	public void close() {
		if(buffer == null || !buffer.isDirect()) return;
		if(!noFree) MemoryUtil.memFree(buffer);
		buffer = null;
	}
	
	@Override
	public Element current() {
		return layout.get(index);
	}
	
	@Override
	public BufferResult finish() {
		if(autoAlign) {
			int count = vertecies - lastVertecies;
			lastVertecies = vertecies;
			int vertecies = totalStoredBytes / layout.bytes();
			return new BufferResult(mode, vertecies - count, vertecies);
		}
		BufferResult result = new BufferResult(mode, lastVertecies, vertecies);
		lastVertecies = vertecies;
		return result;
	}
	
	@Override
	public VertexBuilder next() {
		index = (index + 1) % layout.size();
		totalStoredBytes += elementBytes;
		vertexBytes += elementBytes;
		elementBytes = 0;
		if(index == 0) {
			vertecies++;
			vertexBytes = 0;
			ensureCapacity(totalStoredBytes + (layout.bytes() * mode.primitiveLength()));
		}
		return this;
	}
	
	@Override
	public VertexBuilder put(int byteOffset, byte value) {
		buffer.put(totalStoredBytes + byteOffset, value);
		elementBytes+=1;
		return this;
	}
	
	@Override
	public VertexBuilder putShort(int byteOffset, short value) {
		buffer.putShort(totalStoredBytes + byteOffset, value);
		elementBytes+=2;
		return this;
	}
	
	@Override
	public VertexBuilder putInt(int byteOffset, int value) {
		buffer.putInt(totalStoredBytes + byteOffset, value);
		elementBytes+=4;
		return this;
	}
	
	@Override
	public VertexBuilder putFloat(int byteOffset, float value) {
		buffer.putFloat(totalStoredBytes + byteOffset, value);
		elementBytes+=4;
		return this;
	}
	
	@Override
	public int vertecies() {
		return vertecies;
	}
	
	public VertexBuilder putBulk(byte[] source) {
		if(index != 0) throw new IllegalStateException("Can't do bulk iterations when mid Building");
		ensureCapacity(source.length + layout.bytes());
		buffer.position(totalStoredBytes).put(source).position(0);
		vertecies += source.length / layout.bytes();
		totalStoredBytes += source.length;
		return this;
	}
	
	public VertexBuilder putBulk(ByteBuffer source) {
		if(index != 0) throw new IllegalStateException("Can't do bulk iterations when mid Building");
		ensureCapacity(source.limit() + layout.bytes());
		buffer.position(totalStoredBytes).put(source).position(0);
		vertecies += source.limit() / layout.bytes();
		totalStoredBytes += source.limit();
		return this;
	}
	
	public VertexBuilder copy(ByteBuffer buffer) {
		buffer.put(buffer.position(), this.buffer, 0, totalStoredBytes);
		return this;
	}
	
	public byte[] bytes() {
		byte[] data = new byte[totalStoredBytes];
		buffer.get(0, data);
		return data;
	}
	
	private void ensureCapacity(int newValue) {
		if(totalStoredBytes + newValue >= buffer.capacity()) {
			int oldSize = buffer.capacity();
			int newSize = Math.max(oldSize + (oldSize >> 1), oldSize + newValue);
			buffer = buffer.isDirect() ? MemoryUtil.memRealloc(buffer, newSize) : ByteBuffer.allocate(newSize).put(buffer).position(0);
		}
	}
}