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

namespace Gamelogic.Grids.Examples
{
	public class PathFindingPointyHexGrid : GridBehaviour<PointyHexPoint>
	{
		public PathMode pathMode;
		public SpriteCell pathPrefab;
		public GameObject pathRoot;

		[Header("Colors")] public Gradient weightGradient;

		public Color blockedColor = Color.black;
		public Color pathColor = ExampleUtils.Colors[6];
		public Color startColor = ExampleUtils.Colors[5];
		public Color goalColor = ExampleUtils.Colors[7];

		private PointyHexPoint start;
		private PointyHexPoint goal;
		private bool selectStart = true; //otherwise, select goal

		private PointyHexGrid<WalkableCell> walkableGrid;

		public override void InitGrid()
		{

			//We cast the grid and grid values here for convenience.
			//Casting like this clones the grid. Thus neighbor relations are 
			//not preserved (this is a design flaw as of Grids 1.8, to be fixed
			//in a future version). For now it means the actual pathfinding call
			//must use the original grid.
			walkableGrid = (PointyHexGrid<WalkableCell>) Grid.CastValues<WalkableCell, PointyHexPoint>();
			var imageMap = new ImageMap<PointyHexPoint>(new Rect(0.5f, 0.5f, 1, 1), Grid, Map.To2D());

			foreach (var point in walkableGrid)
			{
				walkableGrid[point].IsWalkable = true;
				var imagePoint = imageMap[point];

				var movementCost =
					+ 0.5f*Mathf.PerlinNoise(imagePoint.x, imagePoint.y)
					+ 0.25f*Mathf.PerlinNoise(imagePoint.x*2, imagePoint.y*2)
					+ 0.125f*Mathf.PerlinNoise(imagePoint.x*4, imagePoint.y*4)
					+ 0.0625f*Mathf.PerlinNoise(imagePoint.x*8, imagePoint.y*8)
					; //1 + Random.value*2;

				walkableGrid[point].MovementCost = movementCost;
			}

			var min = Grid.Min(p => walkableGrid[p].MovementCost);
			var max = Grid.Max(p => walkableGrid[p].MovementCost);

			foreach (var c in walkableGrid.Values)
			{
				c.MovementCost = 1 + 1*(c.MovementCost - min)/(max - min);
				c.Color = weightGradient.Evaluate((c.MovementCost - 1)/2);
			}

			start = walkableGrid.First();
			goal = walkableGrid.Last();

			UpdatePath();
		}

		//Returns the Euclidean distance (in world units)
		//between the given grid points
		private float EuclideanDistance(PointyHexPoint p, PointyHexPoint q)
		{
			float dX = Map[p].x - Map[q].x;
			float dY = Map[p].y - Map[q].y;

			float distance = Mathf.Sqrt(dX*dX + dY*dY);

			return distance;
		}

		public void OnLeftClick(PointyHexPoint clickedPoint)
		{
			ToggleCellWalkability(clickedPoint);
			UpdatePath();
		}

		public void OnRightClick(PointyHexPoint clickedPoint)
		{
			SetStartOrGoal(clickedPoint);
			UpdatePath();
		}

		private void ToggleCellWalkability(PointyHexPoint clickedPoint)
		{
			walkableGrid[clickedPoint].IsWalkable = !walkableGrid[clickedPoint].IsWalkable;

			var color = walkableGrid[clickedPoint].IsWalkable
				? weightGradient.Evaluate((walkableGrid[clickedPoint].MovementCost - 1)/2)
				: blockedColor;
			walkableGrid[clickedPoint].Color = color;
		}

		private void SetStartOrGoal(PointyHexPoint clickedPoint)
		{
			if (selectStart && clickedPoint != goal)
			{
				start = clickedPoint;
				selectStart = false;
			}
			else if (clickedPoint != start)
			{
				goal = clickedPoint;
				selectStart = true;
			}
		}

		public IEnumerable<PointyHexPoint> GetGridPath()
		{
			//We use the original grid here, and not the 
			//copy, to preserve neighbor relationships. Therefore, we
			//have to cast the cell in the lambda expression below.
			var path = Algorithms.AStar(
				walkableGrid,
				start,
				goal,
				(p, q) => p.DistanceFrom(q),
				c => c.IsWalkable,
				(p, q) => 1);

			return path;
		}

		public IEnumerable<PointyHexPoint> GetEuclideanPath()
		{
			var path = Algorithms.AStar(
				walkableGrid,
				start,
				goal,
				EuclideanDistance,
				c => c.IsWalkable,
				EuclideanDistance);

			return path;
		}

		public IEnumerable<PointyHexPoint> GetWeightedPath()
		{
			//We use the original grid here, and not the 
			//copy, to preserve neighbor relationships. Therefore, we
			//have to cast the cell in the lambda expression below.
			var path = Algorithms.AStar(
				walkableGrid,
				start,
				goal,
				(p, q) => p.DistanceFrom(q)*WalkableCell.MinCost,
				c => c.IsWalkable,
				GetMovementCost);

			return path;
		}

		/**
		Gets the cost of moving between the cells at the given grid points, 
		asuming cells are neighbors.
	*/

		private float GetMovementCost(PointyHexPoint p1, PointyHexPoint p2)
		{
			return (walkableGrid[p1].MovementCost +
			        walkableGrid[p2].MovementCost)/2;
		}

		private void UpdatePath()
		{
			if (Application.isPlaying)
			{
				pathRoot.transform.DestroyChildren();
			}
			else
			{
				pathRoot.transform.DestroyChildrenImmediate();
			}

			IEnumerable<PointyHexPoint> path = null;

			switch (pathMode)
			{
				case PathMode.GridPath:
					path = GetGridPath();
					break;
				case PathMode.EuclideanPath:
					path = GetEuclideanPath();
					break;
				case PathMode.WeightedPath:
					path = GetWeightedPath();
					break;
			}

			if (path == null)
			{
				return; //then there is no path between the start and goal.
			}

			foreach (var point in path)
			{
				var pathNode = Instantiate(pathPrefab);

				pathNode.transform.parent = pathRoot.transform;
				pathNode.transform.localScale = Vector3.one*0.5f;
				pathNode.transform.localPosition = Map[point];

				if (point == start)
				{
					pathNode.Color = startColor;
				}
				else if (point == goal)
				{
					pathNode.Color = goalColor;
				}
				else
				{
					pathNode.Color = pathColor;
				}
			}
		}
	}
}