package speiger.src.coreengine.rendering.tesselation;

import java.nio.FloatBuffer;

import speiger.src.coreengine.math.vector.floats.Vec2f;
import speiger.src.coreengine.math.vector.floats.Vec3f;
import speiger.src.coreengine.rendering.models.DrawCall;
import speiger.src.coreengine.rendering.tesselation.VertexElement.ElementUsage;

public class Tesselator implements IVertexBuilder
{
	boolean drawing = false;
	int drawType = 0;
	
	VertexList currentList;
	VertexElement currentElement;
	int currentIndex = 0;
	
	Vec3f offset = Vec3f.newMutable();
	Vec2f effects = Vec2f.newMutable(1F);
	
	FloatBuffer buffer;
	int vertexes = 0;
	
	public Tesselator(int capacity)
	{
		buffer = FloatBuffer.allocate(capacity);
	}
	
	public Tesselator(FloatBuffer buffer)
	{
		this.buffer = buffer;
	}
	
	public Tesselator begin(int glType, VertexList list)
	{
		if(drawing)
		{
			throw new RuntimeException("Already Drawing");
		}
		drawType = glType;
		drawing = true;
		currentList = list;
		currentElement = list.getElement(0);
		currentIndex = 0;
		buffer.clear();
		vertexes = 0;
		return this;
	}
	
	public Tesselator changeGLType(int glType)
	{
		if(!drawing)
		{
			throw new RuntimeException("Can not change type");
		}
		drawType = glType;
		return this;
	}
	
	public Tesselator resetVertexes()
	{
		vertexes = 0;
		return this;
	}
			
	public Tesselator setBrightness(float value)
	{
		effects.setX(value);
		return this;
	}
	
	public Tesselator setVisibilty(float value)
	{
		effects.setY(value);
		return this;
	}
	
	public float getBrightness()
	{
		return effects.getX();
	}
	
	public float getVisibility()
	{
		return effects.getY();
	}
	
	public Tesselator resetEffects()
	{
		effects.set(Vec2f.ONE);
		return this;
	}
	
	public Tesselator offset(float x, float y, float z)
	{
		offset.add(x, y, z);
		return this;
	}
	
	public Tesselator setOffset(float x, float y, float z)
	{
		offset.set(x, y, z);
		return this;
	}
	
	@Override
	public Tesselator pos(float x, float y)
	{
		validateElement(ElementUsage.POS, 2);
		buffer.put(x + offset.getX());
		buffer.put(y + offset.getY());
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator pos(float x, float y, float z)
	{
		validateElement(ElementUsage.POS, 3);
		buffer.put(x + offset.getX());
		buffer.put(y + offset.getY());
		buffer.put(z + offset.getZ());
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator color3f(float r, float g, float b)
	{
		validateElement(ElementUsage.COLOR, 3);
		buffer.put(r * effects.getX());
		buffer.put(g * effects.getX());
		buffer.put(b * effects.getX());
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator color4f(float r, float g, float b, float a)
	{
		validateElement(ElementUsage.COLOR, 4);
		buffer.put(r * effects.getX());
		buffer.put(g * effects.getX());
		buffer.put(b * effects.getX());
		buffer.put(a * effects.getY());
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator tex(float u, float v)
	{
		validateElement(ElementUsage.UV, 2);
		buffer.put(u);
		buffer.put(v);
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator normal(float x, float y, float z)
	{
		validateElement(ElementUsage.NORMAL, 3);
		buffer.put(x);
		buffer.put(y);
		buffer.put(z);
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator custom(float x)
	{
		validateElement(ElementUsage.CUSTOM, 1);
		buffer.put(x);
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator custom(float x, float y)
	{
		validateElement(ElementUsage.CUSTOM, 2);
		buffer.put(x);
		buffer.put(y);
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator custom(float x, float y, float z)
	{
		validateElement(ElementUsage.CUSTOM, 3);
		buffer.put(x);
		buffer.put(y);
		buffer.put(z);
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator custom(float x, float y, float z, float w)
	{
		validateElement(ElementUsage.CUSTOM, 4);
		buffer.put(x);
		buffer.put(y);
		buffer.put(z);
		buffer.put(w);
		nextElement();
		return this;
	}
	
	@Override
	public Tesselator endVertex()
	{
		vertexes++;
		return this;
	}
	
	public Tesselator finishData()
	{
		drawing = false;
		buffer.flip();
		return this;
	}
	
	public float[] getData()
	{
		float[] data = new float[currentList.getOffsets() * vertexes];
		buffer.get(data);
		return data;
	}
	
	public DrawCall getDrawCall(int texture)
	{
		float[] data = new float[currentList.getOffsets() * vertexes];
		buffer.get(data);
		return new DrawCall(drawType, texture, data, vertexes);
	}
	
	public boolean isDrawing()
	{
		return drawing;
	}
	
	public boolean hasTexture()
	{
		return currentList.hasType(ElementUsage.UV);
	}
	
	public int getGLDrawType()
	{
		return drawType;
	}
	
	public void validateElement(ElementUsage usage, int componentSize)
	{
		if(currentElement.getUsage() == usage && currentElement.getSize() == componentSize && drawing)
		{
			return;
		}
		throw new RuntimeException("Invalid Argument, Drawing: "+drawing+", Element: "+usage+", Expected: "+currentElement.getUsage()+", Size: "+componentSize+", Expected Size: "+currentElement.getSize());
	}
	
	protected void nextElement()
	{
		currentIndex = (currentIndex + 1) % currentList.getElementCount();
		currentElement = currentList.getElement(currentIndex);
	}

	public int getVertexCount()
	{
		return vertexes;
	}
}
