using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static WFC;
public partial class Layer : Node3D
{
	Random rand = new Random();
	private Node3D decorationRoot;
	public Tile[,] tiles;
	int layerSize;
	Tile tile;
	int level;
	bool updateFailed = false;
	public bool hasContentGenerated = false;

	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		decorationRoot = new Node3D
		{
			Name = "Decorations"
		};
		AddChild(decorationRoot);
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
	}

	public void ClearDecorations()
	{
		foreach (var tile in tiles)
		{
			foreach (Node child in tile.ContentNode.GetChildren())
			{
				child.QueueFree();
			}
		}
	}

	public void SetupLayer(int layerSize, int level, Dictionary<string, MeshInstance3D> tileMeshes)
	{
		this.layerSize = layerSize;
		this.level = level;
		tiles = new Tile[layerSize, layerSize];
		GenerateBaseStructure(tileMeshes);
		int safetyCounter = 0;
		while (true)
		{
			if (GenerateLayer()) break;
			ResetLayer(tileMeshes);
			safetyCounter++;
			if (safetyCounter > 1000) break;
		}
		CreateTileNodes();
	}

	private void GenerateBaseStructure(Dictionary<string, MeshInstance3D> tileMeshes)
	{
		Vector3 position;
		float offsetX;
		float offsetY = level * 4 * -1;
		float offsetZ;
		for (int x = 0; x < layerSize; x++)
		{
			offsetX = x * 6;
			for (int y = 0; y < layerSize; y++)
			{
				offsetZ = y * 6;
				position = new Vector3(offsetX, offsetY, offsetZ);
				tile = new Tile();
				tile.SetMeshes(tileMeshes);
				tile.Position = position;
				tile.GridPosition = new Vector2I(x, y);
				tiles[x, y] = tile;
			}
		}
	}

	private void CreateTileNodes()
	{
		foreach (var tile in tiles)
		{
			var node = new Node3D
			{
				Position = tile.Position
			};
			decorationRoot.AddChild(node);

			tile.ContentNode = node;
		}
	}

	private void ResetLayer(Dictionary<string, MeshInstance3D> tileMeshes)
	{
		for (int x = 0; x < layerSize; x++)
		{
			for (int y = 0; y < layerSize; y++)
			{
				tiles[x, y].Reset(tileMeshes);
			}
		}
	}

	private bool GenerateBorder()
	{
		for (int x = 0; x < layerSize; x++)
		{
			for (int y = 0; y < layerSize; y++)
			{
				if (x == 0 || y == 0 || x == layerSize - 1 || y == layerSize - 1)
				{
					var tile = tiles[x, y];

					List<string> possibilities = new();

					if (x == 0 && y == 0)
					{
						possibilities.Add("corner_down_right");
					}
					else if (x == 0 && y == layerSize - 1)
					{
						possibilities.Add("corner_up_right");
					}
					else if (x == layerSize - 1 && y == 0)
					{
						possibilities.Add("corner_down_left");
					}
					else if (x == layerSize - 1 && y == layerSize - 1)
					{
						possibilities.Add("corner_up_left");
					}
					else if (y == 0)
					{
						possibilities.Add("straight_left_right");
						possibilities.Add("t_down");
					}
					else if (y == layerSize - 1)
					{
						possibilities.Add("straight_left_right");
						possibilities.Add("t_up");
					}
					else if (x == 0)
					{
						possibilities.Add("straight_up_down");
						possibilities.Add("t_right");
					}
					else if (x == layerSize - 1)
					{
						possibilities.Add("straight_up_down");
						possibilities.Add("t_left");
					}

					string result = tile.Collapse(possibilities[rand.Next(0, possibilities.Count)]);

					if (result == "ERR")
						return false;

					NewPropagate(new Vector2I(x, y));
				}
			}
		}
		return true;
	}

	private void GenerateNecessaryTiles()
	{
		int posX, posY;
		while (true)
		{
			posX = rand.Next(layerSize);
			posY = rand.Next(layerSize);

			if (tiles[posX, posY].collapsedMesh != null) continue;
			if (tiles[posX, posY].tileMeshes.ContainsKey("gate"))
			{
				tiles[posX, posY].Collapse("gate");
				NewPropagate(new Vector2I(posX, posY));
				break;
			}
		}
	}

	public bool GenerateLayer()
	{
		bool result = true;
		int safetyCounter = 0;
		if (!GenerateBorder())
		{
			return false;
		}

		GenerateNecessaryTiles();
		Vector2I position = GetSmallestPossibilities();
		while (true)
		{
			string keyword = tiles[position.X, position.Y].Collapse("");
			if (keyword == "ERR") return false;
			if (keyword != "")
			{
				NewPropagate(position);
				if (updateFailed) break;
				position = GetSmallestPossibilities();
				if (position == new Vector2(-100, -100))
				{
					break;
				}
				continue;
			}
			safetyCounter++;
			if (safetyCounter == layerSize * layerSize) return false;
		}
		if (updateFailed) return false;
		//Player has over 80% of tiles available without destroying walls => Results in about 95% success rate
		//Not necessarily needed but improves the overall generation percentage at a low resource cost
		if (!WFC.IsMapConnected(tiles, 0.8f)) return false;
		return result;
	}

	private void NewPropagate(Vector2I startPos)
	{
		Queue<Vector2I> queue = new Queue<Vector2I>();
		queue.Enqueue(startPos);

		while (queue.Count > 0)
		{
			Vector2I currentPos = queue.Dequeue();
			Tile currentTile = tiles[currentPos.X, currentPos.Y];

			// Use CURRENT state of tile
			var currentPossibilities = currentTile.collapsedMesh != null
				? new HashSet<string> { currentTile.collapsedMesh }
				: new HashSet<string>(currentTile.tileMeshes.Keys);

			for (int i = 0; i < dirs.Length; i++)
			{
				Vector2I newPos = currentPos + offsets[i];
				if (!InBounds(newPos, layerSize)) continue;

				Tile neighborTile = tiles[newPos.X, newPos.Y];

				HashSet<string> allowed = new HashSet<string>();

				foreach (string neighborOption in neighborTile.tileMeshes.Keys)
				{
					foreach (string item in currentPossibilities)
					{
						if (WFC.CanConnect(item, neighborOption, dirs[i], false))
						{
							allowed.Add(neighborOption);
							break;
						}
					}
				}

				int updateCount = neighborTile.Propagate(allowed);

				if (updateCount == int.MaxValue)
				{
					updateFailed = true;
					return;
				}

				// ONLY enqueue if something changed
				if (updateCount > 0)
				{
					queue.Enqueue(newPos);
				}
			}
		}
	}


	private Vector2I GetSmallestPossibilities()
	{
		Vector2I result = new Vector2I(-100, -100);
		int lowest = 100;
		int current;
		for (int x = 0; x < layerSize; x++)
		{
			for (int y = 0; y < layerSize; y++)
			{
				if (tiles[x, y].collapsedMesh != null) continue;
				current = tiles[x, y].tileMeshes.Count;
				if (current < lowest)
				{
					result = new Vector2I(x, y);
					lowest = current;
				}
			}
		}
		return result;
	}
}
