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 enum Direction
	{
		Backward,
		Forward,
		Left,
		Right,
		None,
		Up,
		Down
	}

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

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

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

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

		["end_up"] = new HashSet<Direction> { Direction.Backward, Direction.Up },
		["end_down"] = new HashSet<Direction> { Direction.Forward, Direction.Up },
		["end_left"] = new HashSet<Direction> { Direction.Left, Direction.Up },
		["end_right"] = new HashSet<Direction> { Direction.Right, Direction.Up },

		["straight_left_right"] = new HashSet<Direction> { Direction.Left, Direction.Right, Direction.Up },
		["straight_up_down"] = new HashSet<Direction> { Direction.Backward, Direction.Forward, Direction.Up },

		["corner_up_left"] = new HashSet<Direction> { Direction.Backward, Direction.Left, Direction.Up },
		["corner_up_right"] = new HashSet<Direction> { Direction.Backward, Direction.Right, Direction.Up },
		["corner_down_left"] = new HashSet<Direction> { Direction.Forward, Direction.Left, Direction.Up },
		["corner_down_right"] = new HashSet<Direction> { Direction.Forward, Direction.Right, Direction.Up },

		["junction"] = new HashSet<Direction> { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up },
		["gate"] = new HashSet<Direction> { Direction.Backward, Direction.Forward, Direction.Left, Direction.Right, Direction.Up, Direction.Down },

		["spawn"] = new HashSet<Direction> { Direction.Forward, Direction.Left },
	};

	public static Dictionary<string, float> weights = new Dictionary<string, float>()
	{
		["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,
		["spawn"] = 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)
	{
		HashSet<Direction> aDirs = tileConnections[a];
		HashSet<Direction> 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 (string tile in tileConnections.Keys)
		{
			adjacency[tile] = new Dictionary<Direction, List<string>>();

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

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

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

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

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

		if (!IsWalkable(toTile))
			return false;

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

	public static bool CanWalk3D(Vector3I from, Vector3I to)
	{
		Tile fromTile = GameData.map[from.Y].tiles[from.X, from.Z];
		Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z];

		if (fromTile == null || toTile == null)
			return false;

		if (from.Y != to.Y)
		{
			if (Math.Abs(from.Y - to.Y) != 1)
				return false;

			if (from.Y > to.Y)
			{
				return toTile.collapsedMesh == "gate";
			}
			else
			{
				return fromTile.collapsedMesh == "gate";
			}
		}

		int dx = to.X - from.X;
		int dz = to.Z - from.Z;

		if (Math.Abs(dx) + Math.Abs(dz) != 1)
			return false;

		Direction dir;

		if (dx == 1) dir = Direction.Right;
		else if (dx == -1) dir = Direction.Left;
		else if (dz == 1) dir = Direction.Forward;
		else if (dz == -1) dir = Direction.Backward;
		else return false;

		return CanWalk(
			GameData.map[from.Y].tiles,
			new Vector2I(from.X, from.Z),
			new Vector2I(to.X, to.Z),
			dir
		);
	}

	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 = GameData.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 < offsets2D.Length; i++)
			{
				Vector2I next = position + offsets2D[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;
	}

	public static List<string> GetBorderPossibilities(int x, int z)
	{
		bool left = x == 0;
		bool right = x == GameData.layerSize - 1;
		bool top = z == 0;
		bool bottom = z == GameData.layerSize - 1;

		if (left && top) return new List<string> { "corner_down_right" };
		if (left && bottom) return new List<string> { "corner_up_right" };
		if (right && top) return new List<string> { "corner_down_left" };
		if (right && bottom) return new List<string> { "corner_up_left" };

		if (top) return new List<string> { "straight_left_right", "t_down" };
		if (bottom) return new List<string> { "straight_left_right", "t_up" };
		if (left) return new List<string> { "straight_up_down", "t_right" };
		if (right) return new List<string> { "straight_up_down", "t_left" };

		return new List<string>();
	}
}
