using UnityEngine;
using System.Collections.Generic;

namespace Me.StefS.Utility
{
	//Counter clockwise order
	//Change this motherfucker to object
	//this it is said to box a lot, which is probably TRUE
	//explains the garbage
	public enum OrientationSide {
		Up = 0,
		Left = 1,
		Down = 2,
		Right = 3,
	}

	public static class OrientationSideExtensions
	{
		public static int ValuesCount = 4;

		private static int[] signum = {
			1,
			-1,
			-1,
			1,
		};

		//If in any future you put bidirectional map in this project, use it here
		private static OrientationSide[] opposites = {
			 OrientationSide.Down,
			 OrientationSide.Right,
			 OrientationSide.Up,
			 OrientationSide.Left,
		};

		private static float[] referentClockwiseOrientationAngles = {
			0f,
			270f,
			180f,
			90f,
		};

		private static Vector3[] sideToDirectionVectorMap = {
			1.Z (),
			-1.X (),
			-1.Z (),
			1.X (),
		};

		private static HashSet<OrientationSide> allValues = new HashSet<OrientationSide>((OrientationSide[])System.Enum.GetValues (typeof(OrientationSide)));

		private static bool isSideToRIghtDiagonalMapInitialized = false;
		private static IDictionary<OrientationSide, DiagonalOrientationSide> sideToRightDiagonalMap;

		public static OrientationSide Opposite(this OrientationSide side) {
			OrientationSide opposite = opposites[(int)side];
			return opposite;
		}

		public static bool IsValid(this OrientationSide side) {
			int sideInt = (int)side;
			bool isValid = sideInt >= 0 && sideInt < 4;
			return isValid;
		}

		public static IEnumerable<OrientationSide> GetValues() {
			return allValues;
		}
		
		public static float GetClockwiseAngleDifference(this OrientationSide from, OrientationSide till){
			if (!from.IsValid()) {
				throw new System.ArgumentOutOfRangeException("from");
			}

			if (!till.IsValid()) {
				throw new System.ArgumentOutOfRangeException("till");
			}

			float angle = (360 + referentClockwiseOrientationAngles [(int)from] - referentClockwiseOrientationAngles [(int)till]) % 360;
			return angle;
		}
		
		public static OrientationSide RightSide(this OrientationSide side) {
			if(!side.IsValid())
			{
				throw new System.ArgumentOutOfRangeException("side");
			}

			return (OrientationSide)((int)(side - 1 + 4) % 4);
		}

		public static OrientationSide LeftSide(this OrientationSide side) {
			if(!side.IsValid())
			{
				throw new System.ArgumentOutOfRangeException("side");
			}
			
			return (OrientationSide)((int)(side + 1) % 4);
		}

		public static Vector3 GetDirection(this OrientationSide side) {
			if (!side.IsValid()) {
				throw new System.ArgumentOutOfRangeException("side");
			}

			Vector3 direction = sideToDirectionVectorMap [(int)side];
			return direction;
		}

		public static bool IsLeftRight(this OrientationSide side){
			return !IsUpDown (side);
		}

		public static bool IsUpDown(this OrientationSide side)
		{
			if(!side.IsValid()){
				throw new System.ArgumentOutOfRangeException("side");
			}

			return side == OrientationSide.Down || side == OrientationSide.Up;
		}

		public static DiagonalOrientationSide RightDiagonal(this OrientationSide side){
			if(!side.IsValid()){
				throw new System.ArgumentOutOfRangeException("side");
			}

			if(!isSideToRIghtDiagonalMapInitialized) {
				InitializeSideToRightDiagonalMap();
			}

			return sideToRightDiagonalMap[side];
		}

		public static DiagonalOrientationSide LeftDiagonal(this OrientationSide side){
			if(!side.IsValid()){
				throw new System.ArgumentOutOfRangeException("side");
			}

			return side.LeftSide().RightDiagonal();
		}

		public static bool DoesFormRightAngleWith(this OrientationSide firstSide, OrientationSide secondSide){
			if(!firstSide.IsValid()){
				throw new System.ArgumentOutOfRangeException("firstSide");
			}

			if(!secondSide.IsValid()){
				throw new System.ArgumentOutOfRangeException("secondSide");
			}

			return firstSide != secondSide && firstSide.Opposite() != secondSide;
		}

		//Up means they are the same, rightSide means that firstSide.Right == second etc.
		public static OrientationSide Difference(this OrientationSide firstSide, OrientationSide secondSide){
			OrientationSide difference;
			if(firstSide == secondSide){
				difference = OrientationSide.Up;
			}else if(firstSide.RightSide () == secondSide){
				difference = OrientationSide.Right;
			}else if(firstSide.LeftSide () == secondSide){
				difference = OrientationSide.Left;
			}else{
				difference = OrientationSide.Down;
			}

			return difference;
		}

		public static OrientationSide AddTo(this OrientationSide firstSide, OrientationSide secondSide){
			OrientationSide added;
			switch(secondSide){
			case OrientationSide.Up:
				added = firstSide;
				break;
			case OrientationSide.Right:
				added = firstSide.RightSide ();
				break;
			case OrientationSide.Down:
				added = firstSide.Opposite ();
				break;
			case OrientationSide.Left:
				added = firstSide.LeftSide ();
				break;
				default:
				throw new System.InvalidOperationException("all cases should be included");
			}

			return added;
		}

		public static DiagonalOrientationSide AddHalfTo(this OrientationSide firstSide, OrientationSide secondSide){
			DiagonalOrientationSide added;
			if(secondSide.IsUpDown ()){
				throw new System.ArgumentOutOfRangeException ("secondSide", "only left or right can be half added");
			}
			if (secondSide == OrientationSide.Right){
				added = firstSide.RightDiagonal ();
			}else{
				added = firstSide.LeftDiagonal ();
			}

			return added;
		}

		public static DiagonalOrientationSide VectorAddTo(this OrientationSide firstSide, OrientationSide secondSide){
			if(!firstSide.IsValid()){
				throw new System.ArgumentOutOfRangeException("firstSide");
			}
			
			if(!secondSide.IsValid() && !firstSide.DoesFormRightAngleWith(secondSide)){
				throw new System.ArgumentOutOfRangeException("secondSide");
			}
			
			return firstSide.LeftDiagonal () == secondSide.RightDiagonal () ? firstSide.LeftDiagonal () : firstSide.RightDiagonal ();
		}

		public static int Signum(this OrientationSide side){
			return signum [(int)side];
		}

		static void InitializeSideToRightDiagonalMap ()
		{
			sideToRightDiagonalMap = new Dictionary<OrientationSide, DiagonalOrientationSide>();
			IEnumerator<DiagonalOrientationSide> diagonalIterator = DiagonalOrientationSideExtensions.GetValues().GetEnumerator();
			try{
				while(diagonalIterator.MoveNext()){
					DiagonalOrientationSide diagonal = diagonalIterator.Current;
					sideToRightDiagonalMap[diagonal.LeftSide()] = diagonal;
				}
			}finally{
				diagonalIterator.Dispose();
			}
		}
	}
}