//--// Settings //------------------------------------------------------------//

#include "/settings.glsl"

//--// Uniforms //------------------------------------------------------------//

uniform sampler2D tex;

//--// Camera uniforms

uniform vec3 cameraPosition;

//--// Shadow uniforms

uniform mat4 shadowModelViewInverse;

//--// Shared Includes //-----------------------------------------------------//

//--// Shared Functions //----------------------------------------------------//

#if defined STAGE_VERTEX
	//--// Vertex Inputs //---------------------------------------------------//

	#define attribute in
	attribute vec4 at_tangent;
	attribute vec2 mc_Entity; // I think this should be a vec2 now in 1.13, since data values are no longer a thing
	attribute vec2 mc_midTexCoord;

	//--// Vertex Outputs //--------------------------------------------------//

	out vec2 vertexTexCoord;
	out vec2 midCoord;

	out vec3 vertexNormal;
	out vec4 vertexTangent;

	out vec3 vertexColor;
	out float blockIdF;

	//--// Vertex Functions //------------------------------------------------//

	void main() {
		gl_Position = gl_Vertex;

		vertexTexCoord = gl_MultiTexCoord0.xy;
		midCoord = mc_midTexCoord;

		vertexNormal = gl_Normal;
		vertexTangent = at_tangent;

		vertexColor = gl_Color.rgb;
		blockIdF = mc_Entity.x;
	}
#elif defined STAGE_GEOMETRY
	//--// Geometry Inputs //-------------------------------------------------//

	layout (triangles) in;

	in vec2[] vertexTexCoord;
	in vec2[] midCoord;

	in vec3[] vertexNormal;
	in vec4[] vertexTangent;

	in vec3[] vertexColor;
	in float[] blockIdF;

	//--// Geometry Outputs //------------------------------------------------//

	#ifdef SHADOW_MAPPING
		layout (triangle_strip, max_vertices = 3 * OCTREE_LEVELS + 3) out;
	#else
		layout (points, max_vertices = OCTREE_LEVELS) out;
	#endif

	#ifdef SHADOW_MAPPING
		flat out int isVoxel;

		flat out vec3 tintColor;
		out vec2 texCoord;
	#endif

	flat out vec4[2] voxel;

	//--// Geometry Includes //-----------------------------------------------//

	#include "/include/utility.glsl"
	#include "/include/utility/voxelization.glsl"

	#ifdef SHADOW_MAPPING
	#include "/include/shadowDistortion.glsl"
	#endif

	//--// Geometry Functions //----------------------------------------------//

	vec3 FindQuadCenter() {
		vec3 m;
		m.xy = (midCoord[0] - vertexTexCoord[2]) * inverse(mat2(
			vertexTexCoord[0].x - vertexTexCoord[2].x, vertexTexCoord[1].x - vertexTexCoord[2].x,
			vertexTexCoord[0].y - vertexTexCoord[2].y, vertexTexCoord[1].y - vertexTexCoord[2].y
		));
		m.z = 1.0 - m.x - m.y;

		return mat3(gl_in[0].gl_Position.xyz, gl_in[1].gl_Position.xyz, gl_in[2].gl_Position.xyz) * m;
	}

	void main() {
		//return;
		// Discard things we can't voxelize.
		if (blockIdF[0] == 0.0) { return; }

		vec3 normal = mat3(shadowModelViewInverse) * normalize(gl_NormalMatrix * vertexNormal[0]);

		// Discard problematic faces of iron bars
		if (((blockIdF[0] >= 51.0 && blockIdF[0] <= 57.0) || (blockIdF[0] >= 80.0 && blockIdF[0] <= 88.0)) && abs(normal.y) > 0.9) {
			return;
		}

		//--// Location in voxel map

		vec3 pos = FindQuadCenter();
		     pos = mat3(gl_ModelViewMatrix) * pos + gl_ModelViewMatrix[3].xyz;
		     pos = mat3(shadowModelViewInverse) * pos + shadowModelViewInverse[3].xyz;
		vec3 voxelIdx = floor(VoxelSpaceFromSceneSpace(pos - normal / 32.0));

		if (!InsideVoxelmap(voxelIdx)) { return; }

		//--// Voxel properties

		int blockId = int(blockIdF[0]);

		vec3 bitangent = cross(vertexTangent[0].xyz, vertexNormal[0]) * (vertexTangent[0].w < 0.0 ? -1.0 : 1.0);
		vec3 vertPosU  = vertexTangent[0].xyz * mat3(gl_in[0].gl_Position.xyz, gl_in[1].gl_Position.xyz, gl_in[2].gl_Position.xyz);
		vec3 vertPosV  = bitangent            * mat3(gl_in[0].gl_Position.xyz, gl_in[1].gl_Position.xyz, gl_in[2].gl_Position.xyz);
		vec2 faceSize  = vec2(MaxOf(vertPosU) - MinOf(vertPosU), MaxOf(vertPosV) - MinOf(vertPosV));
		     faceSize /= vec2(length(vertexTangent[0].xyz), length(bitangent));

		vec2 minCoord = min(min(vertexTexCoord[0], vertexTexCoord[1]), vertexTexCoord[2]);
		vec2 maxCoord = max(max(vertexTexCoord[0], vertexTexCoord[1]), vertexTexCoord[2]);
		vec2 tileSize = abs(maxCoord - minCoord) / faceSize;
		vec2 tileResolution = round(tileSize * textureSize(tex, 0));
		ivec2 tileIdx = ivec2(midCoord[0] / tileSize);

		// (24) tintColor
		// (08/12) blockId - Support up to 256/4096 different IDs. Each ID refers to a different block shape.
		// (4) lightLevel - Block light level, max = emissive. Might not be used.
		// (4) tileSize - OptiFine ensures every texture is a power of two. I'll only support square textures, which means up to 2^15 for 4 bits which is already excessive.
		// (24) tileIdx - up to 4096x4096 tiles

		vec4[2] tempVoxel;
		tempVoxel[0] = vec4(vertexColor[0], 1.0 - clamp((blockIdF[0] + OCTREE_LEVELS) / 255.0, float(OCTREE_LEVELS) / 255.0, 1.0)); // tintColor, blockId
		tempVoxel[1] = vec4(log2(MaxOf(tileResolution)) / 15.0, PackUnorm2x4(floor(tileIdx / 256.0) / 15.0), fract(tileIdx / 256.0) * 256.0 / 255.0); // blockId(?), tileSize, tileIdx

		//--// Create the primitive(s)

		// Prioritize faces pointing more upwards
		float normalBias = 0.5 - 0.25 * normal.y;

		for (int level = 0; level < OCTREE_LEVELS; ++level) {
			#if OCTREE_LEVELS > 1
			vec2 shadowTexelCoord = CalculateVoxelmapCoord(floor(voxelIdx / float(1 << level)) * float(1 << level)) * 2.0 - 1.0;
			#else
			vec2 shadowTexelCoord = CalculateVoxelmapCoord(voxelIdx) * 2.0 - 1.0;
			#endif

			#ifdef SHADOW_MAPPING
			const vec2[3] vertOffs = vec2[3](
				vec2( 1,-1) / SHADOWTEX_RESOLUTION,
				vec2( 0, 1) / SHADOWTEX_RESOLUTION,
				vec2(-1,-1) / SHADOWTEX_RESOLUTION
			);
			for (int i = 0; i < 3; ++i) {
				isVoxel = 1;

				voxel = tempVoxel;
				if (level > 0) {
					voxel[0].a = 1.0 + (float(level) - OCTREE_LEVELS) / 255.0;
				}

				// compiler complains if I don't do this
				tintColor = vec3(1.0);
				texCoord = vec2(0.0);

				gl_Position = vec4(shadowTexelCoord, level == 0 ? normalBias : 0.9 + level*0.1/(OCTREE_LEVELS+1), 1.0);
				gl_Position.xy += vertOffs[i];
				EmitVertex();
			}
			#else
				voxel = tempVoxel;
				if (level > 0) {
					voxel[0].a = 1.0 + (float(level) - OCTREE_LEVELS) / 255.0;
				}

				gl_Position = vec4(shadowTexelCoord, level == 0 ? normalBias : 0.9 + level*0.1/(OCTREE_LEVELS+1), 1.0);
				gl_PointSize = 1.0;

				EmitVertex();
			#endif

			EndPrimitive();
		}
	}
#elif defined STAGE_FRAGMENT
	//--// Fragment Inputs //-------------------------------------------------//

	#if defined SHADOW_MAPPING
		flat in int isVoxel;

		flat in vec3 tintColor;
		in vec2 texCoord;
	#endif

	flat in vec4[2] voxel;

	//--// Fragment Outputs //------------------------------------------------//

	layout (location = 0) out vec4 fragData0;
	layout (location = 1) out vec4 fragData1;

	//--// Fragment Functions //----------------------------------------------//

	void main() {
		#if defined SHADOW_MAPPING
		if (isVoxel == 0) {
			fragData0 = texture(tex, texCoord);
			if (fragData0.a < 0.102) { discard; }
			fragData0.rgb *= tintColor;
			fragData1 = vec4(0.0);

			return;
		}
		#endif

		fragData0 = voxel[0];
		fragData1 = voxel[1];
	}
#endif
