using System.Linq;
using UnityEngine;
using System.Collections.Generic;

namespace Gamelogic.Grids.Examples
{
	public class DirectionsHex : GLMonoBehaviour
	{

		public const int gridSide = 8;
		public Vector2 cellDimensions;
		public GameObject characterPrefab;
		public DirectionalCell cellPrefab;

		public GameObject gridRoot;


		private GameObject character;

		private PointyHexGrid<DirectionalCell> grid;
		private IMap<PointyHexPoint> map;

		private bool isMoving;
		private bool gameOver;

		private PointyHexPoint currentPoint;
		private PointyHexPoint targetPoint;

		private const float lerpTime = 0.25f;
		private const float snapDistance = 5f;
		private float elapsedTime = 0f;

		public void Start()
		{
			Reset();
		}

		public void Update()
		{
			if (gameOver) return;

			if (!isMoving)
			{
				CheckPlayerInput();
			}
			else
			{
				MovementUpdate();
			}
		}

		public void Reset()
		{
			gridRoot.transform.DestroyChildren();
			BuildGrid();
			BuildMap();
			CreateCells();
			InitializeGame();
		}

		private void BuildGrid()
		{
			grid = PointyHexGrid<DirectionalCell>.Hexagon(gridSide);
		}

		private void BuildMap()
		{
			map = new PointyHexMap(cellDimensions)
				.WithWindow(ExampleUtils.ScreenRect)
				.AlignMiddleCenter(grid)
				.Scale(1.1f);
		}

		private void CreateCells()
		{
			foreach (var point in grid)
			{
				Vector3 worldPosition = map[point];

				var cell = Instantiate(cellPrefab);
				grid[point] = cell;

				cell.transform.parent = gridRoot.transform;
				cell.transform.localScale = Vector3.one;
				cell.transform.localPosition = worldPosition;
			}
		}

		private void InitializeGame()
		{
			isMoving = false;
			gameOver = false;
			elapsedTime = 0;


			var wallsGrid = PointyHexGrid<bool>.Hexagon(gridSide);

			foreach (var point in grid)
			{
				if (point.GetColor2_4() == 0)
					grid[point].Color = Color.red;
			}

			var walls = MazeAlgorithms.GenerateMazeWalls<bool>(wallsGrid).ToList();
			var startNode = walls.First();

			InitMazeGrid(wallsGrid, walls);

			var paths = wallsGrid.Where(p => wallsGrid[p]);

			var bestNode = paths.First();
			int bestLength = -1;

			foreach (var point in paths)
			{
				var path = Algorithms.AStar<bool, PointyHexPoint>(
					wallsGrid,
					startNode,
					point,
					(p, q) => p.DistanceFrom(q),
					cell => cell);

				/*
			if (path != null && path.Count > bestLength)
			{
				bestLength = path.Count;
				bestNode = point;
			}
			*/

			}

			var goalNode = bestNode;

			InitGrid(startNode, goalNode, wallsGrid);
			InitCharacter(startNode);

			currentPoint = startNode;
			grid[goalNode].SetGoalNode();
			grid[startNode].SetStartNode();
		}

		private static void InitMazeGrid(PointyHexGrid<bool> wallsGrid, IEnumerable<PointyHexPoint> openings)
		{
			foreach (var point in wallsGrid)
			{
				wallsGrid[point] = point.GetColor2_4() == 0;
			}

			foreach (var point in openings)
			{
				wallsGrid[point] = true;
			}
		}

		private void InitGrid(PointyHexPoint startNode, PointyHexPoint goalNode, IGrid<bool, PointyHexPoint> wallsGrid)
		{
			foreach (var point in wallsGrid)
			{
				if (wallsGrid[point])
				{
					grid[point].SetDecisionNode();
				}
				else
				{
					grid[point].SetStartNode();
				}
			}
			//return;
			var path = Algorithms.AStar<bool, PointyHexPoint>(wallsGrid, startNode, goalNode, (p, q) => p.DistanceFrom(q), p => p);

			var previous = startNode;

			foreach (var node in path)
			{
				grid[previous].SetDirection(node - previous);
				previous = node;
			}

			foreach (var point in grid)
			{
				if (point != startNode && point != goalNode)
				{
					if (point.GetColor2_4() != 0 || Random.value < 0.5f)
					{
						if (!path.Contains(point))
						{
							grid[point].SetRandomDirection();
						}
					}
					else
					{
						grid[point].SetDecisionNode();
					}
				}
			}

			foreach (var point in grid.GetNeighbors(goalNode))
			{
				if (point != startNode)
				{
					if (grid[point].Direction == PointyHexPoint.Zero)
					{
						grid[point].SetDirection(goalNode - point);
					}
				}
			}

			EliminateLoops();

			foreach (var point in grid)
			{
				if (point != startNode && point != goalNode && Random.value < 0.3f)
				{
					//grid[point].SetColor(Color.black);
				}
			}
		}

		private void EliminateLoops()
		{
			foreach (var point in grid)
			{
				if (grid[point].Direction == PointyHexPoint.Zero)
				{
					continue;
				}

				var path = new List<PointyHexPoint>();

				var currentPoint = point;

				while (true)
				{
					path.Add(currentPoint);
					var nextPoint = currentPoint + grid[currentPoint].Direction;

					if (!grid.Contains(nextPoint)) break;
					if (grid[nextPoint].Direction == PointyHexPoint.Zero) break;

					if (path.Contains(nextPoint)) //We have a loop!
					{
						grid[currentPoint].SetDecisionNode();
						break;
					}

					currentPoint = nextPoint;
				}


			}
		}

		private void InitCharacter(PointyHexPoint startNode)
		{
			character = Instantiate(characterPrefab);
			character.transform.parent = gridRoot.transform;
			character.transform.localScale = Vector3.one;
			character.transform.localPosition = map[startNode];
		}

		private void CheckPlayerInput()
		{
			if (Input.GetMouseButtonDown(0))
			{
				var worldPosition = ExampleUtils.ScreenToWorld(gridRoot, Input.mousePosition);
				var clickedPoint = map[worldPosition];

				if (grid.Contains(clickedPoint) && grid.GetNeighbors(currentPoint).Contains(clickedPoint))
				{
					targetPoint = clickedPoint;
					isMoving = true;
				}
			}
		}

		private void MovementUpdate()
		{
			elapsedTime += Time.deltaTime;

			if (Vector3.Distance(map[targetPoint], character.transform.localPosition) > snapDistance)
			{
				MoveCharacter();
			}
			else
			{
				elapsedTime = 0;
				currentPoint = targetPoint;
				character.transform.localPosition = map[currentPoint];

				//grid[currentPoint].UpdateColor();

				if (grid[currentPoint].Direction != PointyHexPoint.Zero)
				{
					SetNewTargetPoint();
				}
				else
				{
					CheckGameCompleted();
					isMoving = false;
				}
			}
		}

		private void MoveCharacter()
		{
			var currentTimeStep = elapsedTime/lerpTime;

			Vector2 newPosition;

			newPosition.x = Mathf.Lerp(map[currentPoint].x, map[targetPoint].x, currentTimeStep);
			newPosition.y = Mathf.Lerp(map[currentPoint].y, map[targetPoint].y, currentTimeStep);
			character.transform.localPosition = newPosition;
		}

		private void SetNewTargetPoint()
		{
			var newPoint = currentPoint + grid[currentPoint].Direction;
			if (grid.Contains(newPoint))
			{
				targetPoint = newPoint;
			}
			else
			{
				isMoving = false;
			}
		}

		private void CheckGameCompleted()
		{
			if (grid[currentPoint].IsGoal)
			{
				gameOver = true;

			}
		}
	}
}