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

public class WFC
{
	public static Dictionary<string, Dictionary<Direction, List<string>>> adjacency = new Dictionary<string, Dictionary<Direction, List<string>>>();
	public static Random rand = new Random();
	public enum Direction
	{
		Backward,
		Forward,
		Left,
		Right,
		None,
		Up,
		Down
	}

	public static readonly Vector2I[] offsets =
	{
		new Vector2I(0, -1),
		new Vector2I(0, 1),
		new Vector2I(-1, 0),
		new Vector2I(1, 0)
	};

	public static readonly Direction[] dirs =
	{
		Direction.Backward,
		Direction.Forward,
		Direction.Left,
		Direction.Right
	};

	public static Dictionary<string, HashSet<Direction>> tileConnections = new Dictionary<string, HashSet<Direction>>
	{
		["t_right"] = new() { Direction.Backward, Direction.Forward, Direction.Right , Direction.Up},
		["t_left"] = new() { Direction.Backward, Direction.Forward, Direction.Left, Direction.Up },
		["t_up"] = new() { Direction.Left, Direction.Right, Direction.Backward, Direction.Up },
		["t_down"] = new() { Direction.Left, Direction.Right, Direction.Forward, Direction.Up },

		["end_up"] = new() { Direction.Backward, Direction.Up },
		["end_down"] = new() { Direction.Forward, Direction.Up },
		["end_left"] = new() { Direction.Left, Direction.Up },
		["end_right"] = new() { Direction.Right, Direction.Up },

		["straight_left_right"] = new() { Direction.Left, Direction.Right, Direction.Up },
		["straight_up_down"] = new() { Direction.Backward, Direction.Forward, Direction.Up },

		["corner_up_left"] = new() { Direction.Backward, Direction.Left, Direction.Up },
		["corner_up_right"] = new() { Direction.Backward, Direction.Right, Direction.Up },
		["corner_down_left"] = new() { Direction.Forward, Direction.Left, Direction.Up },
		["corner_down_right"] = new() { Direction.Forward, Direction.Right, Direction.Up },

		["junction"] = new() { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up},
		["gate"] = new() { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up, Direction.Down}
	};

	public static Dictionary<string, float> weights = new()
	{
		["junction"] = 3f,
		["t_up"] = 3f,
		["t_down"] = 3f,
		["t_left"] = 3f,
		["t_right"] = 3f,

		["straight_left_right"] = 2f,
		["straight_up_down"] = 2f,

		["corner_up_left"] = 0.7f,
		["corner_up_right"] = 0.7f,
		["corner_down_left"] = 0.7f,
		["corner_down_right"] = 0.7f,

		["end_up"] = 0.2f,
		["end_down"] = 0.1f,
		["end_left"] = 0.2f,
		["end_right"] = 0.3f,

		["gate"] = 0.0f
	};

	public static Direction Opposite(Direction dir)
	{
		return dir switch
		{
			Direction.Backward => Direction.Forward,
			Direction.Forward => Direction.Backward,
			Direction.Left => Direction.Right,
			Direction.Right => Direction.Left,
			Direction.Up => Direction.Down,
			Direction.Down => Direction.Up,
			_ => dir
		};
	}

	public static bool CanConnect(string a, string b, Direction direction, bool checkWalking)
	{
		var aDirs = tileConnections[a];
		var bDirs = tileConnections[b];

		bool aOpen = aDirs.Contains(direction);
		bool bOpen = bDirs.Contains(Opposite(direction));

		if (checkWalking) return aOpen && bOpen;
		return aOpen == bOpen;
	}



	public static void FillAdjacencies()
	{
		foreach (var tile in tileConnections.Keys)
		{
			adjacency[tile] = new Dictionary<Direction, List<string>>();

			foreach (Direction dir in Enum.GetValues(typeof(Direction)))
			{
				var valid = new List<string>();

				foreach (var other in tileConnections.Keys)
				{
					if (CanConnect(tile, other, dir, false))
						valid.Add(other);
				}

				adjacency[tile][dir] = valid;
			}
		}
	}

	public static bool IsWalkable(Tile tile)
	{
		var dirs = tileConnections[tile.collapsedMesh];
		return dirs.Count > 0 && !dirs.Contains(Direction.None);
	}

	public static bool CanWalk(Tile[,] layer, Vector2I from, Vector2I to, Direction dir)
	{
		var fromTile = layer[from.X, from.Y];
		var toTile = layer[to.X, to.Y];

		if (!IsWalkable(toTile))
			return false;

		return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true);
	}

	public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshhold)
	{
		bool result = false;
		HashSet<Vector2I> visited = new HashSet<Vector2I>();
		List<Vector2I> toCheck = new List<Vector2I>();
		Vector2I position;
		toCheck.Add(new Vector2I(1, 1));

		int safetyCounter = 0;
		while (true)
		{
			if (toCheck.Count <= 0) break;
			int index = rand.Next(toCheck.Count);
			position = toCheck[index];
			toCheck[index] = toCheck[^1];
			toCheck.RemoveAt(toCheck.Count - 1);
			if (!visited.Add(position)) continue;
			for (int i = 0; i < 4; i++)
			{
				var next = position + offsets[i];

				if (!InBounds(next, layer.GetLength(0)))
					continue;

				if (CanWalk(layer, position, next, dirs[i]))
				{
					toCheck.Add(next);
				}
			}
			safetyCounter++;
			if (safetyCounter > layer.Length * 2) break;
			if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshhold)
			{
				result = true;
				break;
			}

		}
		return result;
	}

	public static bool InBounds(Vector2I pos, int layerSize)
	{
		return pos.X > 0 &&
				   pos.Y > 0 &&
				   pos.X < layerSize &&
				   pos.Y < layerSize;
	}
}
