/*
 * Copyright (c) 2020, John Campbell and other contributors.  All rights reserved.
 *
 * This file is part of Tectonicus. It is subject to the license terms in the LICENSE file found in
 * the top-level directory of this distribution.  The full list of project contributors is contained
 * in the AUTHORS file found in the same location.
 *
 */

package tectonicus.rasteriser;

import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import tectonicus.texture.SubTexture;

import java.util.ArrayList;

public class SubMesh
{
	public enum Rotation
	{
		None,
		Clockwise,
		AntiClockwise,
		Flip
	};
	
	private ArrayList<Vector3f> positions;
	private ArrayList<Vector2f> texCoords;
	private ArrayList<Vector4f> colours;
	
	public SubMesh()
	{
		positions = new ArrayList<>();
		texCoords = new ArrayList<>();
		colours = new ArrayList<>();
	}
	
	public void addVertex(Vector3f position, Vector4f colour, final float u, final float v)
	{
		positions.add(new Vector3f(position));
		texCoords.add(new Vector2f(u, v));
		colours.add(new Vector4f(colour));
	}
	
	public void addQuad(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector4f colour, SubTexture texture)
	{
		addVertex(p0, colour, texture.u0, texture.v0);
		addVertex(p1, colour, texture.u1, texture.v0);
		addVertex(p2, colour, texture.u1, texture.v1);
		addVertex(p3, colour, texture.u0, texture.v1);
	}
	
	public void addQuad(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector4f colour, Vector2f uv0, Vector2f uv1, Vector2f uv2, Vector2f uv3)
	{
		addVertex(p0, colour, uv0.x, uv0.y);
		addVertex(p1, colour, uv1.x, uv1.y);
		addVertex(p2, colour, uv2.x, uv2.y);
		addVertex(p3, colour, uv3.x, uv3.y);
	}
	
	public void addDoubleSidedQuad(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector4f colour, SubTexture texture)
	{
		// Clockwise
		addVertex(p0, colour, texture.u0, texture.v0);
		addVertex(p1, colour, texture.u1, texture.v0);
		addVertex(p2, colour, texture.u1, texture.v1);
		addVertex(p3, colour, texture.u0, texture.v1);
		
		// Anticlockwise
		addVertex(p0, colour, texture.u0, texture.v0);
		addVertex(p3, colour, texture.u0, texture.v1);
		addVertex(p2, colour, texture.u1, texture.v1);
		addVertex(p1, colour, texture.u1, texture.v0);
	}
	
	public void pushTo(Mesh mesh, final float xOffset, final float yOffset, final float zOffset, Rotation rotation, final float angleDeg)
	{
		pushTo(mesh, xOffset, yOffset, zOffset, rotation, angleDeg, Rotation.None, 0);
	}
	public void pushTo(Mesh mesh, final float xOffset, final float yOffset, final float zOffset, Rotation horizRotation, final float horizAngleDeg, Rotation vertRotation, final float vertAngleDeg)
	{
		Matrix4f transform = createTransform(horizRotation, horizAngleDeg, vertRotation, vertAngleDeg);
		
		for (int i=0; i<positions.size(); i++)
		{
			Vector3f pos = new Vector3f( positions.get(i) );
			Vector2f tex = texCoords.get(i);
			Vector4f col = colours.get(i);
			
			if (transform != null)
			{
				Vector4f dest = new Vector4f();
				transform.transform(new Vector4f(pos.x, pos.y, pos.z, 1.0f), dest);
				
				pos.x = dest.x / dest.w;
				pos.y = dest.y / dest.w;
				pos.z = dest.z / dest.w;
			}
			
			pos.x += xOffset;
			pos.y += yOffset;
			pos.z += zOffset;
			
			mesh.addVertex(pos, col, tex.x, tex.y);
		}
	}
	
	public void pushTo(SubMesh destMesh, final float xOffset, final float yOffset, final float zOffset, Rotation horizRotation, final float horizAngleDeg, Rotation vertRotation, final float vertAngleDeg)
	{
		Matrix4f transform = createTransform(horizRotation, horizAngleDeg, vertRotation, vertAngleDeg);
		
		for (int i=0; i<positions.size(); i++)
		{
			Vector3f pos = new Vector3f( positions.get(i) );
			
			if (transform != null)
			{
				Vector4f dest = new Vector4f();
				transform.transform(new Vector4f(pos.x, pos.y, pos.z, 1.0f), dest);
				
				pos.x = dest.x / dest.w;
				pos.y = dest.y / dest.w;
				pos.z = dest.z / dest.w;
			}
			
			destMesh.positions.add( new Vector3f(positions.get(i) ));
			destMesh.colours.add( new Vector4f(colours.get(i) ));
			destMesh.texCoords.add( new Vector2f(texCoords.get(i) ));
		}
	}
	
	public void pushTo(SubMesh dest, final float xOffset, final float yOffset, final float zOffset)
	{
		for (int i=0; i<positions.size(); i++)
		{
			dest.positions.add( new Vector3f(positions.get(i) ));
			dest.colours.add( new Vector4f(colours.get(i) ));
			dest.texCoords.add( new Vector2f(texCoords.get(i) ));
		}
	}
	
	private static Matrix4f createTransform(Rotation horizontalRotation, final float horizontalAngleDeg,
											Rotation verticalRotation, final float verticalAngleDeg)
	{
		if (horizontalRotation == Rotation.None && verticalRotation == Rotation.None)
			return null;
		
		float horizontalAngleInRads = horizontalAngleDeg / 360.0f * 2.0f * (float)Math.PI;
		if (horizontalRotation == Rotation.AntiClockwise)
			horizontalAngleInRads *= -1.0f;
		
		float verticalAngleInRads = verticalAngleDeg / 360.0f * 2.0f * (float)Math.PI;
		if (verticalRotation == Rotation.AntiClockwise)
			verticalAngleInRads *= -1.0f;
		
		Matrix4f trans0 = new Matrix4f();
		trans0.translate(new Vector3f(+0.5f, +0.5f, +0.5f));
		
		Matrix4f horizontalRotate = new Matrix4f();
		horizontalRotate.rotate(horizontalAngleInRads, new Vector3f(0, 1, 0));
		
		Matrix4f verticalRotate = new Matrix4f();
		verticalRotate.rotate(verticalAngleInRads, new Vector3f(0, 0, 1));
		
		Matrix4f trans1 = new Matrix4f();
		trans1.translate(new Vector3f(-0.5f, -0.5f, -0.5f));
		
		Matrix4f combinedRotate = new Matrix4f();
		horizontalRotate.mul(verticalRotate, combinedRotate);
		
		Matrix4f working = new Matrix4f();
		trans0.mul(combinedRotate, working);
		
		Matrix4f actual = new Matrix4f();
		working.mul(trans1, actual);
		
		
		
	/*	Matrix4f working = new Matrix4f();
		Matrix4f.mul(trans0, rotate, working);
		
		Matrix4f actual = new Matrix4f();
		Matrix4f.mul(working, trans1, actual);
	*/
		
		return actual;
	}
	
	
	public static void addBlock(SubMesh subMesh, final float x, final float y, final float z,
								final float width, final float height, final float depth,
								Vector4f colour, SubTexture sideTex, SubTexture topTex, SubTexture bottomTex)
	{
		final float uRange = sideTex.u1 - sideTex.u0;
		final float vRange = sideTex.v1 - sideTex.v0;
		
		
		// West
		subMesh.addQuad( new Vector3f(x, y+height, z), new Vector3f(x, y+height, z+depth),
						new Vector3f(x, y, z+depth),  new Vector3f(x, y, z), 
						colour, new Vector2f(sideTex.u0 + uRange * z,			sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u0 + uRange * (z + depth),		sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u0 + uRange * (z + depth),		sideTex.v1 - vRange * y),
	 					new Vector2f(sideTex.u0 + uRange * z,			sideTex.v1 - vRange * y));
		
		// North
		subMesh.addQuad( new Vector3f(x+width, y+height, z), new Vector3f(x, y+height, z),
						new Vector3f(x, y, z),  new Vector3f(x+width, y, z), 
						colour, new Vector2f(sideTex.u1 - uRange * (x + width),		sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u1 - uRange * x,			sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u1 - uRange * x,			sideTex.v1 - vRange * y),
	 					new Vector2f(sideTex.u1 - uRange * (x + width),		sideTex.v1 - vRange * y));
		
		// South
		subMesh.addQuad( new Vector3f(x, y+height, z+depth), new Vector3f(x+width, y+height, z+depth),
						new Vector3f(x+width, y, z+depth),  new Vector3f(x, y, z+depth), 
						colour, new Vector2f(sideTex.u1 - uRange * (x + width),		sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u1 - uRange * x,			sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u1 - uRange * x,			sideTex.v1 - vRange * y),
	 					new Vector2f(sideTex.u1 - uRange * (x + width),		sideTex.v1 - vRange * y));
		
		// East
		subMesh.addQuad(new Vector3f(x+width, y+height, z+depth), new Vector3f(x+width, y+height, z),
						new Vector3f(x+width, y, z), new Vector3f(x+width, y, z+depth), 
						colour, new Vector2f(sideTex.u0 + uRange * z,	sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u0 + uRange * (z + depth),		sideTex.v1 - vRange * (y + height)),
	 					new Vector2f(sideTex.u0 + uRange * (z + depth),		sideTex.v1 - vRange * y),
	 					new Vector2f(sideTex.u0 + uRange * z,			sideTex.v1 - vRange * y));
		
		if(topTex != null)
		{
			// Top
			subMesh.addQuad(new Vector3f(x, y+height, z), new Vector3f(x+width, y+height, z),
							new Vector3f(x+width, y+height, z+depth), new Vector3f(x, y+height, z+depth), 
							colour, new Vector2f(topTex.u0 + uRange * x,		topTex.v0 + vRange * z),
							new Vector2f(topTex.u0 + uRange * (x + width),	topTex.v0 + vRange * z),
							new Vector2f(topTex.u0 + uRange * (x + width),	topTex.v0 + vRange * (z + depth)),
							new Vector2f(topTex.u0 + uRange * x,		topTex.v0 + vRange * (z + depth)));
		}
		
		// Bottom
		subMesh.addQuad(new Vector3f(x, y, z), new Vector3f(x, y, z+depth),
						new Vector3f(x+width, y, z+depth), new Vector3f(x+width, y, z), 
						colour, new Vector2f(bottomTex.u0 + uRange * x,		bottomTex.v0 + vRange * z),
						new Vector2f(bottomTex.u0 + uRange * (x + width),	bottomTex.v0 + vRange * z),
						new Vector2f(bottomTex.u0 + uRange * (x + width),	bottomTex.v0 + vRange * (z + depth)),
						new Vector2f(bottomTex.u0 + uRange * x,		bottomTex.v0 + vRange * (z + depth)));
	}
	
	
	public static void addBlockSimple(SubMesh subMesh, final float x, final float y, final float z,
			final float width, final float height, final float depth,
			Vector4f colour, SubTexture sideTex, SubTexture topTex, SubTexture bottomTex)
	{
		// West
		subMesh.addQuad( new Vector3f(x, y+height, z), new Vector3f(x, y+height, z+depth),
			new Vector3f(x, y, z+depth),  new Vector3f(x, y, z), 
			colour, sideTex);
		
		// North
		subMesh.addQuad( new Vector3f(x+width, y+height, z), new Vector3f(x, y+height, z),
			new Vector3f(x, y, z),  new Vector3f(x+width, y, z), 
			colour, sideTex);
		
		// South
		subMesh.addQuad( new Vector3f(x, y+height, z+depth), new Vector3f(x+width, y+height, z+depth),
			new Vector3f(x+width, y, z+depth),  new Vector3f(x, y, z+depth), 
			colour, sideTex);
		
		// East
		subMesh.addQuad(new Vector3f(x+width, y+height, z+depth), new Vector3f(x+width, y+height, z),
			new Vector3f(x+width, y, z), new Vector3f(x+width, y, z+depth), 
			colour, sideTex);
		
		if(topTex != null)
		{
			// Top
			subMesh.addQuad(new Vector3f(x, y+height, z), new Vector3f(x+width, y+height, z),
							new Vector3f(x+width, y+height, z+depth), new Vector3f(x, y+height, z+depth), 
							colour, topTex);
		}
		
		if(bottomTex != null)
		{
			// Bottom
			subMesh.addQuad(new Vector3f(x, y, z), new Vector3f(x, y, z+depth),
							new Vector3f(x+width, y, z+depth), new Vector3f(x+width, y, z), 
							colour, bottomTex);
		}
	}

}
