using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static GameData;

public partial class World : Node3D
{
	Random rand = new Random();
	public Dictionary<string, MeshInstance3D> tileMeshes;
	public Dictionary<string, MeshInstance3D> contentMeshes;
	public Dictionary<string, List<Placeholder>> tilePlaceholders;
	PackedScene layerPrefab = ResourceLoader.LoadLayerPrefab();
	private Dictionary<string, MultiMeshInstance3D> multiMeshes = new();
	private Dictionary<string, Mesh> meshLibrary = new();
	Layer[] map;
	Layer layerNode;

	private MultiMeshHandler multiMeshHandler;

	public override void _Ready()
	{
		WFC.FillAdjacencies();

		tileMeshes = ResourceLoader.LoadTiles();
		contentMeshes = ResourceLoader.LoadDecorations();

		tilePlaceholders = new Dictionary<string, List<Placeholder>>();

		foreach (var kvp in tileMeshes)
		{
			tilePlaceholders[kvp.Key] = new List<Placeholder>();
			foreach (Node3D child in kvp.Value.GetChildren())
			{
				tilePlaceholders[kvp.Key].Add(new Placeholder(child.Name, child.Transform));
			}
			meshLibrary[kvp.Key] = kvp.Value.Mesh;
			kvp.Value.QueueFree();
		}

		multiMeshes = CreateMultiMeshes(meshLibrary);
		multiMeshHandler = new MultiMeshHandler(multiMeshes);

		map = new Layer[ruinSize];
		GenerateWorld();

		HandleRenderData(BuildRenderData(0));
	}

	private Dictionary<string, MultiMeshInstance3D> CreateMultiMeshes(Dictionary<string, Mesh> meshLibrary)
	{
		var result = new Dictionary<string, MultiMeshInstance3D>();

		foreach (var kvp in meshLibrary)
		{
			var mm = new MultiMesh
			{
				Mesh = kvp.Value,
				TransformFormat = MultiMesh.TransformFormatEnum.Transform3D
			};

			var instance = new MultiMeshInstance3D
			{
				Multimesh = mm
			};

			AddChild(instance);

			result[kvp.Key] = instance;
		}

		return result;
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
		if (!canMove) return;
		if (Input.IsActionJustPressed("layer_up") && currentLayer > 0) currentLayer--;
		if (Input.IsActionJustPressed("layer_down") && currentLayer < ruinSize - 1) currentLayer++;
		if (currentLayer != visibleLayer)
		{
			map[visibleLayer].ClearDecorations();
			HandleRenderData(BuildRenderData(currentLayer));
			visibleLayer = currentLayer;
		}

		if (Input.IsActionJustPressed("spawn_robot") && robots.Count < maxRobotCount)
		{
			Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
			robot.Position = map[0].tiles[rand.Next(layerSize), rand.Next(layerSize)].Position;
			AddChild(robot);
			robots.Add(robot);
		}
	}

	private void GenerateWorld()
	{
		for (int layer = 0; layer < ruinSize; layer++)
		{
			layerNode = layerPrefab.Instantiate<Layer>();
			AddChild(layerNode);
			layerNode.SetupLayer(layerSize, layer, tileMeshes);
			map[layer] = layerNode;
		}
	}

	private List<TileRenderData> BuildRenderData(int layerIndex)
	{
		var result = new List<TileRenderData>();
		Layer layer = map[layerIndex];
		layer.ClearDecorations();

		for (int x = 0; x < layerSize; x++)
		{
			for (int y = 0; y < layerSize; y++)
			{
				Tile tile = layer.tiles[x, y];
				result.Add(new TileRenderData
				{
					Tile = tile,
					MeshKey = tile.collapsedMesh,
					Transform = new Transform3D(Basis.Identity, tile.Position),
					Placeholders = tilePlaceholders[tile.collapsedMesh]
				});
			}
		}

		if (!layer.hasContentGenerated)
		{
			DistributeTileContent(layer);
			layer.hasContentGenerated = true;
		}
		return result;
	}

	private void DistributeTileContent(Layer layer)
	{
		int currentDecoration = 0;
		int currentLight = 0;
		int currentResource = 0;

		int posX, posY;

		while(currentLight < layerSize * 3)
		{
			posX = rand.Next(layerSize);
			posY = rand.Next(layerSize);
			//Skip already placed lights and skip junction and gate as they do not contain lights
			if(layer.tiles[posX, posY].collapsedMesh == "junction" || layer.tiles[posX, posY].collapsedMesh == "gate") continue;
			if(layer.tiles[posX, posY].containsLight) continue;
			layer.tiles[posX, posY].containsLight = true;
			currentLight++;
		}

		while(currentDecoration < layerSize)
		{
			posX = rand.Next(layerSize);
			posY = rand.Next(layerSize);
			if(layer.tiles[posX, posY].containsDecoration) continue;
			layer.tiles[posX, posY].containsDecoration = true;
			currentDecoration++;
		}

		while(currentResource < layerSize)
		{
			posX = rand.Next(layerSize);
			posY = rand.Next(layerSize);
			if(layer.tiles[posX, posY].containsResource) continue;
			layer.tiles[posX, posY].containsResource = true;
			currentResource++;
		}
	}

	private void HandleRenderData(List<TileRenderData> renderData)
	{
		multiMeshHandler.Build(renderData);
		foreach (TileRenderData data in renderData)
		{
			data.Tile.SpawnContent(contentMeshes, data.Transform, data.Placeholders);
		}
	}
}
