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

namespace Gamelogic.Grids.Examples
{
	public class MatchMakerCairo : GLMonoBehaviour, IResetable
	{
		public GameObject gridRoot;
		public GameObject uiRoot;

		public MatchMakerCell matchMakerCellPrefab;
		public GameObject boardPiecePrefab;

		public GameObject selectedOverlay;
		public GameObject warningOverlayPrefab;

		public TextMesh counterText;
		public GameObject gameOverMessage;

		private CairoGrid<MatchMakerCell> grid;

		private readonly Vector2 cellDimensions = new Vector2(490, 490)*.22f;
		private IMap<CairoPoint> map;

		private const int amountOfColors = 4;
		private const int amountOfStartPieces = 0;
		private const int piecesPerTurn = 4;

		private bool pointIsSelected;
		private CairoPoint selectedPoint;

		private bool inputAllowed;
		private bool gameOver;
		private int counter;

		public void Start()
		{
			Reset();
		}



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

			if (inputAllowed)
			{
				HandleUserInput();
			}
		}





		public void Reset()
		{
			counter = 0;
			gameOver = false;
			gameOverMessage.SetActive(false);
			selectedOverlay.SetActive(false);

			gridRoot.transform.DestroyChildren();

			BuildGrid();
			BuildMap();
			CreateCells();

			InitGame();
		}

		private void BuildGrid()
		{
			grid = CairoGrid<MatchMakerCell>
				.BeginShape()
				.Hexagon(3)
				.EndShape();
		}

		private void BuildMap()
		{
			map = new CairoMap(cellDimensions)
				.WithWindow(ExampleUtils.ScreenRect)
				.AlignMiddleCenter(grid)
				.AnchorCellMiddleCenter();
		}

		private void CreateCells()
		{
			foreach (CairoPoint point in grid)
			{
				var mmCell = Instantiate(matchMakerCellPrefab);

				mmCell.transform.parent = gridRoot.transform;
				mmCell.transform.localScale = Vector3.one;
				mmCell.transform.localPosition = map[point];
				mmCell.SetAngle(point.I*90f + 180f);
				mmCell.Color = ExampleUtils.Colors[0];
				mmCell.OccupantColor = -1;

				grid[point] = mmCell;
			}
		}

		private void InitGame()
		{
			gameOver = false;

			pointIsSelected = false;
			CreatePieces(amountOfStartPieces);
			EndOfTurn();

			inputAllowed = true;
		}

		private void CreatePieces(int amountToCreate)
		{
			List<CairoPoint> openSpaces = grid.Where(point => grid[point].OccupantColor == -1).ToList();

			amountToCreate = Mathf.Min(openSpaces.Count, amountToCreate);

			for (int i = 0; i < amountToCreate; i++)
			{
				var randomSpot = openSpaces.RandomItem();
				CreatePieceAt(randomSpot);
				openSpaces.Remove(randomSpot);
			}
		}

		private void CreatePieceAt(CairoPoint point)
		{
			int chosenColor = UnityEngine.Random.Range(1, amountOfColors + 1);

			var piece = Instantiate(boardPiecePrefab);
			piece.GetComponentsInChildren<SpriteRenderer>().First(x => x.name == "Sprite").color =
				ExampleUtils.Colors[chosenColor];
			piece.transform.parent = gridRoot.transform;
			piece.transform.localScale = Vector3.one;
			piece.transform.localPosition = map[point];

			grid[point].OccupantColor = chosenColor;
			grid[point].Occupant = piece;
		}

		private void RemovePieceAt(CairoPoint point)
		{
			Destroy(grid[point].Occupant);
			grid[point].OccupantColor = -1;
		}

		private void HandleUserInput()
		{
			//handles the user input
			if (Input.GetMouseButtonDown(0))
			{
				CairoPoint clickedPoint = map[GridBuilderUtils.ScreenToWorld(gridRoot, Input.mousePosition)];

				if (grid.Contains(clickedPoint))
				{
					if (pointIsSelected)
					{
						if (clickedPoint == selectedPoint)
						{
							Deselect();
						}
						else
						{
							StartCoroutine(grid[clickedPoint].OccupantColor == -1
								? MovePiece(selectedPoint, clickedPoint, MoveFinished)
								: FlashWarning(clickedPoint));
						}
					}
					else
					{
						if (grid[clickedPoint].OccupantColor != -1)
							Select(clickedPoint);
					}
				}
			}
		}

		private void MoveFinished()
		{
			EndOfTurn();
			inputAllowed = true;
		}

		private void Select(CairoPoint point)
		{
			pointIsSelected = true;
			selectedPoint = point;

			selectedOverlay.GetComponent<SpriteCell>().SetAngle(selectedPoint.I*90f + 180f);
			selectedOverlay.transform.localPosition = grid[selectedPoint].transform.localPosition;
			selectedOverlay.SetActive(true);
		}

		private void Deselect()
		{
			selectedOverlay.SetActive(false);
			pointIsSelected = false;
		}

		private void EndOfTurn()
		{
			counter++;
			counterText.text = counter.ToString();

			var pointsToRemove = new List<CairoPoint>();

			foreach (CairoPoint point in GetPointsFlatMatch().Where(point => !pointsToRemove.Contains(point)))
			{
				pointsToRemove.Add(point);
			}

			foreach (CairoPoint point in GetPointsPointyMatch().Where(point => !pointsToRemove.Contains(point)))
			{
				pointsToRemove.Add(point);
			}

			if (pointsToRemove.Count > 0)
			{
				foreach (CairoPoint point in pointsToRemove)
				{
					RemovePieceAt(point);
				}
			}
			else
			{
				CreatePieces(piecesPerTurn);
			}
			if (OpenSpacesLeft() == 0)
			{
				GameOver();
			}
		}

		private IEnumerable<CairoPoint> GetPointsFlatMatch()
		{
			var pointsToReturn = new List<CairoPoint>();

			foreach (CairoPoint point in grid)
			{
				if (point.I == 3 && grid[point].OccupantColor != -1)
				{
					bool matchFound = true;
					var pointsToMark = new List<CairoPoint>();
					CairoPoint point1 = point;
					foreach (
						CairoPoint neighbour in grid.GetAllNeighbors(point).Where(neighbour => neighbour.BasePoint != point1.BasePoint))
					{
						if (!grid.Contains(neighbour) || grid[point].OccupantColor != grid[neighbour].OccupantColor)
						{
							matchFound = false;
						}
						else
						{
							pointsToMark.Add(neighbour);
						}
					}

					if (matchFound)
					{
						pointsToReturn.Add(point);
						pointsToReturn.AddRange(pointsToMark);
					}
				}
			}

			return pointsToReturn;
		}

		private IEnumerable<CairoPoint> GetPointsPointyMatch()
		{
			var pointsToReturn = new List<CairoPoint>();

			foreach (CairoPoint point in grid)
			{
				if (point.I == 0 && grid[point].OccupantColor != -1)
				{
					bool matchFound = true;
					var pointsToMark = new List<CairoPoint>();

					foreach (CairoPoint neighbour in grid.GetAllNeighbors(point))
					{
						if (neighbour.BasePoint == point.BasePoint)
						{
							if (!grid.Contains(neighbour) || grid[point].OccupantColor != grid[neighbour].OccupantColor)
							{
								matchFound = false;
							}
							else
							{
								pointsToMark.Add(neighbour);
							}
						}
					}

					if (matchFound)
					{
						pointsToReturn.Add(point);
						pointsToReturn.AddRange(pointsToMark);
					}
				}
			}
			return pointsToReturn;
		}

		private IEnumerator MovePiece(CairoPoint start, CairoPoint dest, Action moveOver)
		{
			var pathPoints = Algorithms.AStar(
				grid,
				start,
				dest,
				(x, y) => 1f,
				x => x.IsAccessible(),
				(x, y) => 1f);

			if (pathPoints == null)
			{
				yield break;
			}

			var path = pathPoints.ToList();

			inputAllowed = false;
			Deselect();
			GameObject pieceToMove = grid[start].Occupant;

			inputAllowed = false;

			for (int i = 0; i < path.Count() - 1; i++)
			{
				pieceToMove.transform.localPosition = map[path[i + 1]];
				yield return new WaitForSeconds(0.1f);
			}

			grid[dest].OccupantColor = grid[start].OccupantColor;
			grid[dest].Occupant = grid[start].Occupant;

			grid[start].Occupant = null;
			grid[start].OccupantColor = -1;

			inputAllowed = true;

			moveOver();
		}

		private IEnumerator FlashWarning(CairoPoint point)
		{
			var warningOverlay = Instantiate(warningOverlayPrefab);

			warningOverlay.transform.parent = gridRoot.transform;
			warningOverlay.transform.localScale = Vector3.one;
			warningOverlay.transform.localPosition = map[point];
			warningOverlay.GetComponent<SpriteCell>().SetAngle(point.I*90f + 180f);
			yield return new WaitForSeconds(0.1f);

			warningOverlay.SetActive(false);
			yield return new WaitForSeconds(0.05f);

			warningOverlay.SetActive(true);
			yield return new WaitForSeconds(0.05f);

			warningOverlay.SetActive(false);
			yield return new WaitForSeconds(0.05f);

			warningOverlay.SetActive(true);
			yield return new WaitForSeconds(0.1f);

			Destroy(warningOverlay);
		}

		private int OpenSpacesLeft()
		{
			return grid.Count(point => grid[point].OccupantColor == -1);
		}

		private void GameOver()
		{
			gameOverMessage.SetActive(true);
			inputAllowed = false;



			gameOver = true;
		}
	}
}