package speiger.src.coreengine.rendering.tesselation.buffer;

import java.nio.ByteBuffer;
import java.util.List;

import org.lwjgl.system.MemoryUtil;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.utils.ObjectLists;
import speiger.src.coreengine.rendering.tesselation.format.VertexElement;
import speiger.src.coreengine.rendering.tesselation.format.VertexFormat;
import speiger.src.coreengine.rendering.utils.values.GLMode;

public class VertexBuilder implements IVertexBuilder, AutoCloseable {
	GLMode mode;
	ByteBuffer buffer;
	VertexFormat format;
	int index = 0;
	int vertecies;
	int totalStoredBytes;
	int vertexBytes;
	int elementBytes;
	int lastVertecies = 0;
	List<BufferDrawCall> drawCalls;
	
	public VertexBuilder(int size) {
		this(MemoryUtil.memAlloc(size));
	}
	
	public VertexBuilder(ByteBuffer buffer) {
		this.buffer = buffer;
	}
	
	public void close() {
		MemoryUtil.memFree(buffer);
		buffer = null;
	}
	
	public VertexBuilder start(GLMode mode, VertexFormat format) {
		this.mode = mode;
		this.format = format;
		return this;
	}
	
	public boolean is(GLMode mode, VertexFormat format) { return this.mode == mode && this.format == format; }
	public GLMode getMode() { return mode; }
	public VertexFormat getFormat() { return format; }
	
	private void ensureCapacity(int newValue) {
		if(totalStoredBytes + newValue >= buffer.capacity()) {
			int oldSize = buffer.capacity();
			int newSize = Math.max(oldSize + (oldSize >> 1), oldSize + newValue);
			if(buffer.isDirect()) {
				buffer = MemoryUtil.memRealloc(buffer, newSize);
			}
			else {
				ByteBuffer newBuffer = ByteBuffer.allocate(newSize);
				newBuffer.put(buffer).position(0);
				buffer = newBuffer;
			}
		}
	}
	
	public VertexBuilder reset() {
		index = 0;
		vertecies = 0;
		elementBytes = 0;
		totalStoredBytes = 0;
		vertexBytes = 0;
		lastVertecies = 0;
		buffer.rewind();
		drawCalls = null;
		return this;
	}
	
	@Override
	public IVertexBuffer endVertex() {
		if(index != 0) throw new IllegalArgumentException("Format is not completed");
		vertecies++;
		if(mode == GLMode.LINES || mode == GLMode.LINE_STRIP) {
			buffer.put(totalStoredBytes, buffer, totalStoredBytes - vertexBytes, vertexBytes);
			vertecies++;
		}
		vertexBytes = 0;
		//TODO ensure capacity too!
		return this;
	}
	
	public VertexBuilder endDrawCall(boolean store) {
		if(store) {
			if(drawCalls == null) drawCalls = new ObjectArrayList<>();
			drawCalls.add(new BufferDrawCall(mode, lastVertecies, vertecies));			
		}
		lastVertecies = vertecies;
		return this;
	}
	
	@Override
	public List<BufferDrawCall> getDrawCalls() {
		return drawCalls == null ? ObjectLists.empty() : drawCalls; 
	}
	
	@Override
	public VertexElement current() {
		return format.get(index);
	}
	
	@Override
	public IVertexBuilder nextElement() {
		index = (index + 1) % format.size();
		totalStoredBytes += elementBytes;
		vertexBytes += elementBytes;
		elementBytes = 0;
		return this;
	}
	
	@Override
	public int size() { return vertecies; }
	public int last() { return lastVertecies; }
	public boolean hasData() { return vertecies > lastVertecies; }
	
	@Override
	public IVertexBuilder put(int offset, byte value) {
		buffer.put(totalStoredBytes + offset, value);
		elementBytes+=1;
		return this;
	}
	
	@Override
	public IVertexBuilder putShort(int offset, short value) {
		buffer.putShort(totalStoredBytes + offset, value);
		elementBytes+=2;
		return this;
	}
	
	@Override
	public IVertexBuilder putInt(int offset, int value) {
		buffer.putInt(totalStoredBytes + offset, value);
		elementBytes+=4;
		return this;
	}
	
	@Override
	public IVertexBuilder putFloat(int offset, float value) {
		buffer.putFloat(totalStoredBytes + offset, value);
		elementBytes+=4;
		return this;
	}
	
	public VertexBuilder putBulk(byte[] source) {
		ensureCapacity(source.length + format.totalOffset());
		buffer.position(totalStoredBytes).put(source).position(0);
		vertecies += source.length / format.totalOffset();
		totalStoredBytes += source.length;
		return this;
	}
	
	public VertexBuilder putBulk(ByteBuffer source) {
		ensureCapacity(source.limit() + format.totalOffset());
		buffer.position(totalStoredBytes).put(source).position(0);
		vertecies += source.limit() / format.totalOffset();
		totalStoredBytes += source.limit();
		return this;
	}
	
	public VertexBuilder copy(ByteBuffer buffer) {
		buffer.put(buffer.position(), this.buffer, 0, totalStoredBytes);
		return this;
	}
	
	public byte[] getBytes() {
		byte[] data = new byte[totalStoredBytes];
		buffer.get(0, data);
		return data;
	}
}
