using UnityEngine;
using UnityEngine.EventSystems;

namespace Me.StefS.Utility
{
	public static class Vector3Extensions
	{
		public static DiagonalOrientationSide GetMatchingQuadrantDiagonal(this Vector3 vector){
			DiagonalOrientationSide diagonal;
			if(vector.z < 0){
				if(vector.x < 0){
					diagonal = DiagonalOrientationSide.DownLeft;
				}else{
					diagonal = DiagonalOrientationSide.DownRight;
				}
			}else{
				if(vector.x < 0){
					diagonal = DiagonalOrientationSide.UpLeft;
				}else{
					diagonal = DiagonalOrientationSide.UpRight;
				}
			}

			return diagonal;
		}

		public static OrientationSide GetClosestOrientationSide(this Vector3 vector){
			DiagonalOrientationSide quadrant = vector.GetMatchingQuadrantDiagonal ();
			OrientationSide side = Mathf.Abs (vector.z) > Mathf.Abs (vector.x)
				? quadrant.VerticalComponent ()
				: quadrant.HorisontalComponent ();

			return side;
		}

		public static Vector3 VectorTo (this Vector3 vectorFrom, Vector3 vectorTo)
		{
			return vectorTo - vectorFrom;
		}

		public static Vector3 VectorTowardsWithLength(this Vector3 vectorFrom, Vector3 vectorTowards, float length)
		{
			return (vectorTowards - vectorFrom).normalized * length;
		}

		public static float AngleWith (this Vector3 vectorFrom, Vector3 vectorTo)
		{
			return Vector3.Angle (vectorFrom, vectorTo);
		}

		public static Vector3 AddTo (this Vector3 expandedVector, Vector3 expandingByVector)
		{
			return expandedVector + expandingByVector;
		}

		public static float Dot(this Vector3 vectorFrom, Vector3 vectorTo){
			return Vector3.Dot (vectorFrom, vectorTo);
		}

		public static bool IsDistanceBelow(this Vector3 vectorFrom, Vector3 vectorTo, float distance){
			return vectorFrom.VectorTo (vectorTo).sqrMagnitude < distance * distance;
		}

		public static bool IsCloserToThan(this Vector3 position, Vector3 closerPosition, Vector3 furtherPosition){
			return VectorTo (position, closerPosition).sqrMagnitude
				< VectorTo (position, furtherPosition).sqrMagnitude;
		}

		public static bool IsLessThan90DigreesDifference(this Vector3 vectorFrom, Vector3 vectorTo){
			return Vector3.Dot (vectorFrom, vectorTo) > 0;
		}

		public static bool IsOppositlyDirectedTo(this Vector3 vectorFrom, Vector3 vectorTo){
			return Vector3.Dot (vectorFrom, vectorTo).ApproximatelyEquals (-1f, 0.001f);
		}

		public static bool IsBetween(this Vector3 vector, Vector3 vectorBetweenFrom, Vector3 vectorBetweenTo){
			return vectorBetweenFrom.VectorTo (vector).IsLessThan90DigreesDifference (
				vectorBetweenFrom.VectorTo (vectorBetweenTo));
		}

		public static Vector3 Left(this Vector3 vectorFrom){
			if(vectorFrom.y != 0){
				Debug.LogError ("position , this is 2d function that ommits the y");
			}

			return new Vector3 (-vectorFrom.z, 0, vectorFrom.x);
		}

		public static Vector3 Right(this Vector3 vectorFrom){
			if(vectorFrom.y != 0){
				Debug.LogError ("position , this is 2d function that ommits the y");
			}

			return new Vector3 (vectorFrom.z, 0, -vectorFrom.x);
		}

		public static float GetAxisAlignedComponent(this Vector3 vector, OrientationSide axis){
			float component;
			switch(axis){
			case OrientationSide.Up:
				component = vector.z;
				break;
			case OrientationSide.Right:
				component = vector.x;
				break;
			case OrientationSide.Down:
				component = vector.z;
				break;
			case OrientationSide.Left:
				component = vector.x;
				break;
			default:
				throw new System.InvalidOperationException ("All cases should be included");
			}

			return component;
		}

		public static float GetAxisAlignedComponentMagnitude(this Vector3 vector, OrientationSide axis){
			float componentMagnitude;
			switch(axis){
			case OrientationSide.Up:
				componentMagnitude = vector.z;
				break;
			case OrientationSide.Right:
				componentMagnitude = vector.x;
				break;
			case OrientationSide.Down:
				componentMagnitude = -vector.z;
				break;
			case OrientationSide.Left:
				componentMagnitude = -vector.x;
				break;
			default:
				throw new System.InvalidOperationException ("All cases should be included");
			}

			return componentMagnitude;
		}

		public static bool BelongsToHalfPlaneWithinSegment(this Vector3 position, Vector3 segmentFrom, Vector3 segmentTo, Vector3 inDirection){
			//Finish argument check
			///if(position.y != 0 || segmentFrom.y != 0 || segmentTo.y != 0 || inDirection.y !=0){
			//	throw new System.ArgumentOutOfRangeException ("position","this is 2d function that ommits the y");
			//}

			bool belongsToHalfPlane = position.BelongsToHalfPlane (segmentFrom, segmentTo, inDirection);
			if(!belongsToHalfPlane){
				return false;
			}

			Vector3 fromToDirection = segmentFrom.VectorTo (segmentTo);
			bool isWithinBeginingOfSegment = position.BelongsToHalfPlane (
				segmentFrom + fromToDirection.Left (),
				segmentFrom + fromToDirection.Right (),
				fromToDirection);
			
			if(!isWithinBeginingOfSegment){
				return false;
			}

			Vector3 toFromDirection = segmentTo.VectorTo (segmentFrom);
			bool isWithinEndOfSegment = position.BelongsToHalfPlane (
				segmentTo + toFromDirection.Left (),
				segmentTo + toFromDirection.Right (),
				toFromDirection);
			
			if(!isWithinEndOfSegment){
				return false;
			}

			return true;
		}


		public static bool BelongsToHalfPlane(this Vector3 position, Vector3 segmentFrom, Vector3 segmentTo, Vector3 inDirection){
			//Finish argument check
			///if(position.y != 0 || segmentFrom.y != 0 || segmentTo.y != 0 || inDirection.y !=0){
			//	throw new System.ArgumentOutOfRangeException ("position","this is 2d function that ommits the y");
			//}

			bool belongsToHalfPlane = ((segmentFrom + segmentTo) / 2f).VectorTo (position)
				.IsLessThan90DigreesDifference (inDirection);
			return belongsToHalfPlane;
		}


		public static Vector3 XoZ(this Vector3 vector){
			return new Vector3 (vector.x, 0, vector.z);
		}

		public static Vector3 oYZ(this Vector3 vector){
			return new Vector3 (0, vector.y, vector.z);
		}

		public static Vector3 XYo(this Vector3 vector){
			return new Vector3 (vector.x, vector.y, 0);
		}

		public static Vector3 OnLineDistanceAway(this Vector3 fromPosition, Vector3 lineOrigin, OrientationSide lineDirection, float distance){
			float squaredDistance = distance * distance;
			if(lineDirection.IsUpDown ()){
				float xDistance = Mathf.Abs (fromPosition.x - lineOrigin.x);
				float zDistance = xDistance > distance ? 0 : Mathf.Sqrt (squaredDistance - Mathf.Pow (xDistance,2));
				Signum signum = lineDirection == OrientationSide.Up
					? Signum.Plus
					: Signum.Minus;
				return lineOrigin.x.X() + ((int)signum * zDistance + fromPosition.z).Z ();
			}else{
				float zDistsance = Mathf.Abs (fromPosition.z - lineOrigin.z);
				float xDistance = zDistsance > distance ? 0 : Mathf.Sqrt (squaredDistance - Mathf.Pow (zDistsance,2));
				Signum signum = lineDirection == OrientationSide.Right
					? Signum.Plus
					: Signum.Minus;
				return lineOrigin.z.Z() + ((int)signum * xDistance + fromPosition.x).X ();
			}
		}
	}
}