#include "Deer/VoxelWorld.h"
#include "Deer/Voxels/Chunk.h"
#include "Deer/Application.h"

#include "Deer/Components.h"
#include "Deer/Voxels/VoxelWorldData.h"

#include "DeerRender/Render/Render.h"
#include "DeerRender/Render/RenderUtils.h"
#include "DeerRender/Render/Texture.h"
#include "DeerRender/Voxels/VoxelWorldRenderData.h"

#include "Deer/Log.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/matrix_inverse.hpp"

namespace Deer {
	void VoxelWorld::bakeAmbientLight(int minX, int maxX, int minZ, int maxZ) {
		int xPos = minX;
		for (; xPos <= maxX; xPos++) {
			int zPos = minZ;
			for (; zPos <= maxZ; zPos++) {
				LayerVoxel layer = readLayerVoxel(xPos, zPos);
				uint16_t maxHeight = layer.height;
				uint16_t minHeight = layer.ambient_light_height;

				if (minHeight > layer.height)
					minHeight = layer.height;

				uint16_t maxHeightSorroundings = maxHeight;
				for (int i = 0; i < 8; i++) {
					int checkX = xPos + LAYER_CHECK_DIRS(X_AXIS, i);
					int checkZ = zPos + LAYER_CHECK_DIRS(Y_AXIS, i);

					LayerVoxel layer = readLayerVoxel(checkX, checkZ);
					if (layer.height > maxHeightSorroundings)
						maxHeightSorroundings = layer.height;
				}

				maxHeightSorroundings++;
				if (maxHeightSorroundings >= CHUNK_SIZE_Y * worldProps.chunkSizeY)
					maxHeightSorroundings = CHUNK_SIZE_Y * worldProps.chunkSizeY - 1;

				ChunkID chunkID;
				ChunkVoxelID chunkVoxelID;

				// Depending if it's an edge we will add to queue to propagate the light
				bool isPositionEdge = 
					(xPos == minX) || (xPos == maxX) ||
					(zPos == minZ) || (zPos == maxZ);

				if (xPos == 0 || zPos == 0 || xPos == CHUNK_SIZE_X * worldProps.chunkSizeX - 1
					|| zPos == CHUNK_SIZE_Z * worldProps.chunkSizeZ - 1)
					isPositionEdge = false;

				// All light voxelsInfo under the max height must be put to 0 ambient light and above to 255
				// I think this is optional
				
				int yPos = minHeight;
				if (!isPositionEdge) {
					// Empty the ilumination
					for (; yPos < maxHeight; yPos++) {
						extractChunkCordinates(xPos, yPos, zPos, chunkID, chunkVoxelID);
						Chunk& chunk = chunks[worldProps.getWorldChunkID(chunkID)];
						VoxelLight& voxelLight = chunk.modLight(chunkVoxelID);
	
						voxelLight.ambient_light = 0;
					}
				} else {
					// Add to queue
					for (; yPos < maxHeight; yPos++) {
						extractChunkCordinates(xPos, yPos, zPos, chunkID, chunkVoxelID);
						Chunk& chunk = chunks[worldProps.getWorldChunkID(chunkID)];
						VoxelLight voxelLight = chunk.readLight(chunkVoxelID);
						
						if (voxelLight.ambient_light > 0)
							ambientLightPropagation.push(VoxelCordinates(xPos, yPos, zPos));
					}
				}

				// Fill with light voxelsInfo and add queue to update light propagation
				for (; yPos < maxHeightSorroundings; yPos++) {
					extractChunkCordinates(xPos, yPos, zPos, chunkID, chunkVoxelID);
					Chunk& chunk = chunks[worldProps.getWorldChunkID(chunkID)];
					VoxelLight& voxelLight = chunk.modLight(chunkVoxelID);

					voxelLight.ambient_light = 255;
					ambientLightPropagation.push(VoxelCordinates(xPos, yPos, zPos));
				}

				modLayerVoxel(xPos, zPos).ambient_light_height = CHUNK_SIZE_Y * worldProps.chunkSizeY;
			}
		}

		//Resolve ambient light propagation
		while (!ambientLightPropagation.empty()) {
			resolveNextAmbientLightPropagation();
		}
	}

	void VoxelWorld::bakeAmbientLightFromPoint(int x, int z) {
		int minX = x - 16;
		int maxX = x + 16;

		int minZ = z - 16;
		int maxZ = z + 16;

		minX = (minX < 0) ? 0 : minX;
		maxX = (maxX >= worldProps.chunkSizeX * CHUNK_SIZE_X) ? worldProps.chunkSizeX * CHUNK_SIZE_X - 1 : maxX;

		minZ = (minZ < 0) ? 0 : minZ;
		maxZ = (maxZ >= worldProps.chunkSizeZ * CHUNK_SIZE_Z) ? worldProps.chunkSizeZ * CHUNK_SIZE_Z - 1 : maxZ;

		bakeAmbientLight(minX, maxX, minZ, maxZ);
	}

	void VoxelWorld::resolveNextAmbientLightPropagation() {
		VoxelCordinates position = ambientLightPropagation.front();
		ambientLightPropagation.pop();

		LayerVoxel& layerVoxel = modLayerVoxel(position.x, position.z);

		if (layerVoxel.ambient_light_height > position.y)
			layerVoxel.ambient_light_height = position.y;

		VoxelLight currentLight = readLight(position);
		bool solidCheck[6] = { false };
		// Check for every simple dir
		for (int i = 0; i < 6; i++) {
			VoxelCordinates next(
				position.x + NORMAL_DIR(X_AXIS, i),
				position.y + NORMAL_DIR(Y_AXIS, i),
				position.z + NORMAL_DIR(Z_AXIS, i)
			);
			
			Voxel nextVoxel = readVoxel(next);
			solidCheck[i] = nextVoxel.isVoxelType();
			if (solidCheck[i])
				continue;

			VoxelLight& nextLight = modLight(next);
			int nextLightMinValue = currentLight.ambient_light - LIGHT_PROPAGATION_SIMPLE_FALL;

			if (nextLight.ambient_light < nextLightMinValue) {
				nextLight.ambient_light = nextLightMinValue;
				ambientLightPropagation.push(next);
			}
		}
		
		return;
		// TODO
		// Check for every complex dir
		for (int i = 0; i < 6; i++) {
			int cDir0 = LIGHT_PROPAGATION_COMPLEX_DIR(0, i);
			int cDir1 = LIGHT_PROPAGATION_COMPLEX_DIR(1, i);

			if (solidCheck[cDir0] || solidCheck[cDir1])
				continue;

			VoxelCordinates next(
				NORMAL_DIR(X_AXIS, cDir0) + NORMAL_DIR(X_AXIS, cDir1),
				NORMAL_DIR(Y_AXIS, cDir0) + NORMAL_DIR(Y_AXIS, cDir1),
				NORMAL_DIR(Z_AXIS, cDir0) + NORMAL_DIR(Z_AXIS, cDir1)
			);
			
			Voxel nextVoxel = readVoxel(next);
			if (nextVoxel.isVoxelType())
				continue;

			VoxelLight& nextLight = modLight(next);
			int nextLightMinValue = currentLight.ambient_light - LIGHT_PROPAGATION_COMPLEX_FALL;

			if (nextLight.ambient_light < nextLightMinValue) {
				nextLight.ambient_light = nextLightMinValue;
				ambientLightPropagation.push(next);
			}
		}
	}
}