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

namespace Gamelogic.Grids.Examples
{
	public class FieldOfViewBehaviour : GridBehaviour<PointyHexPoint>
	{
		public float angleInDegrees = 60;

		//public Color visibleNearColor = Color.white;
		//public Color visibleFarColor = Color.white;

		public Gradient visibilityGradient;

		public Color invisibleColor = Color.black;
		public Color obstacleColor = Color.black;

		public Color centerColor = ExampleUtils.Colors[3];
		public Color lookAtColor = ExampleUtils.Colors[1];

		public float maxDistance = 5;

		private PointyHexPoint center = PointyHexPoint.Zero;
		private PointyHexPoint lookAt = PointyHexPoint.NorthEast + PointyHexPoint.NorthWest;

		private PointyHexGrid<bool> obstacles; 

		public override void InitGrid()
		{
			obstacles = (PointyHexGrid<bool>) Grid.CloneStructure(p => Random.value < 0.1f);
		}

		public void OnLeftClick(PointyHexPoint clickedPoint)
		{
			if (center == lookAt)
			{
				return;
			}

			center = clickedPoint;
			UpdateVisiblePoints();
		}

		public void OnRightClick(PointyHexPoint clickedPoint)
		{
			if (center == lookAt)
			{
				return;
			}

			lookAt = clickedPoint;
			UpdateVisiblePoints();
		}

		private void UpdateVisiblePoints()
		{
			UpdateVisibility();
		}

		private void UpdateVisibility()
		{
			var visibility = GetVisibility();

			Grid.Apply(c => c.Color = invisibleColor);

			var max = visibility.Values.Max();

			Debug.Log(max);

			foreach (var point in Grid)
			{
				if (obstacles[point])
				{
					Grid[point].Color = obstacleColor;
				}
				else
				{
					var t = visibility[point]/(float) max;
					Grid[point].Color = visibilityGradient.Evaluate(t);
				}
			}

			Grid[center].Color = centerColor;
		}

		private void UpdateFOV()
		{
			var visiblePoints = GetFOV();

			Grid.Apply(c => c.Color = invisibleColor);

			foreach (var point in obstacles.WhereCell(c=>c))
			{
				Grid[point].Color = obstacleColor;
			}

			foreach (var point in visiblePoints)
			{
				var t = point.DistanceFrom(center) / maxDistance;
				Grid[point].Color = visibilityGradient.Evaluate(t);
			}

			Grid[center].Color = centerColor;
			//Grid[lookAt].Color = lookAtColor;
		}

		public void UpdateViewCone()
		{
			var cone = GetViewCone(
				Grid,
				Map,
				center,
				lookAt,
				angleInDegrees,
				Vector3.back);

			Grid.Apply(c => c.Color = invisibleColor);

			foreach (var point in cone)
			{
				var t = point.DistanceFrom(center)/maxDistance;
				Grid[point].Color = visibilityGradient.Evaluate(t);
			}

			Grid[center].Color = centerColor;
			Grid[lookAt].Color = lookAtColor;
		}

		public static IEnumerable<PointyHexPoint> GetViewCone(
			IGrid<TileCell, PointyHexPoint> grid,
			IMap3D<PointyHexPoint> map,
			PointyHexPoint center,
			PointyHexPoint lookAt,
			float angleInDegrees,
			Vector3 up
			)
		{
			var centerWorld = map[center];
			var lookatWorld = map[lookAt];
			var forwardWorld = (lookatWorld - centerWorld);

			var rotation1 = Quaternion.AngleAxis(angleInDegrees/2, up);
			var rotation2 = Quaternion.AngleAxis(-angleInDegrees/2, up);

			var u1 = rotation1*forwardWorld;
			var u2 = rotation2*forwardWorld;

			var perp1 = Quaternion.AngleAxis(-90, up);
			var perp2 = Quaternion.AngleAxis(90, up);

			var w1 = perp1*(u1);
			var w2 = perp2*(u2);

			return grid.Where(p => IsInCone(centerWorld + u1, w1, centerWorld + u2, w2, map[p]));
		}

		public static bool IsInCone(
			Vector3 halfplanePoint1,
			Vector3 halfplaneDirection1,
			Vector3 halfplanePoint2,
			Vector3 halfplaneDirection2,
			Vector3 point)
		{
			return
				IsInHalfplane(halfplanePoint1, halfplaneDirection1, point) &&
				IsInHalfplane(halfplanePoint2, halfplaneDirection2, point);
		}

		public static bool IsInHalfplane(Vector3 halfplanePoint, Vector3 halfplaneDirection, Vector3 point)
		{
			return Vector3.Dot(point - halfplanePoint, halfplaneDirection) >= 0;
		}

		private List<PointyHexPoint> GetFOV()
		{
			return Grid
				.Where(p => Grid.GetNeighbors(p).Count() < 6)
				.Select(point => Map.GetLine(center, point))
				.SelectMany(line => line.TakeWhile(linePoint => !obstacles[linePoint]))
				.Distinct()
				.ToList();
		}

		private IGrid<int, PointyHexPoint> GetVisibility()
		{
			var visibility = Grid.CloneStructure(0);

			var visiblePoints = Grid
				.Where(p => Grid.GetNeighbors(p).Count() < 6)
				.Select(point => Map.GetLine(center, point))
				.SelectMany(line => line.TakeWhile(point => !obstacles[point]));

			foreach (var point in visiblePoints)
			{
				if (point != center)
				{
					visibility[point]++;
				}
			}

			return visibility;
		}
	}
}