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


[RequireComponent (typeof (BoxCollider2D))]
public class Controller2D : MonoBehaviour {

	public LayerMask collisionMask; 

	const float skinWidth = .015f; 
	public int horizontalRayCount = 4;
	public int verticalRayCount = 4;

	float maxClimbAngle = 80; 	//defining maximum slope angle that the Playerobject can climb
	float maxDescendAngle = 75; 	//defining maximum slope angle that the Playerobject can descend

	float horizontalRaySpacing; 
	float verticalRaySpacing; 

	BoxCollider2D collider2D;
	RaycastOrigins raycastOrigins; 
	public CollisionInfo collisions; 


	// Use this for initialization
	void Start () {
		collider2D = GetComponent<BoxCollider2D> ();
		CalculateRaySpacing (); 			//calculates if we changed the amount of horizontal or vertical rays

	}

	public void Move(Vector3 velocity) {
		UpdateRaycastOrigins ();
		collisions.Reset (); 
		collisions.velocityOld = velocity; 

		if (velocity.y < 0) {
			DescendSlope (ref velocity);
		}
		if (velocity.x != 0) {
			HorizontalCollisions (ref velocity);
		}
		if (velocity.y != 0) {
			VerticalCollisions (ref velocity);
		}
		//Code over this (over the tranform.Translate)^ handles the collisions and that will modify the velocities that we don't go through something. (?)
		transform.Translate (velocity); //Moving it with the modified velocity.
	}


	void HorizontalCollisions(ref Vector3 velocity) { 	
		float directionX = Mathf.Sign (velocity.x);		//direction of y-velocity 
		float rayLength = Mathf.Abs(velocity.x)/*absoluttverdien av velocity.y, forcing the value to be positive*/ + skinWidth;

		//forloop
		for (int i = 0; i < horizontalRayCount; i++) {
			//See what direction we move in. If moving down, we want the rays to start in the bottomLeft corner. If moving up, want rays to start in the topLeft corner.
			Vector2 rayOrigin = (directionX == -1) ? raycastOrigins.bottomLeft : raycastOrigins.bottomRight;	//explanation: (if directionX is equal to -1, aka we're moving left).. ?=if that's true, then.. we're setting rayOrigin equal to rayastOrigins.bottomLeft.. : = otherwise (if we're moving up).. we'll set it to raycastOrigins.bottomRight;
			rayOrigin += Vector2.up * (horizontalRaySpacing * i); 
			RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.right * directionX, rayLength, collisionMask); 	//if raycast hits something we want it to go up (?)

			Debug.DrawRay (rayOrigin, Vector2.right * directionX * rayLength, Color.red);

			//if our raycast hits something, the y velocity will be set equal to the amount that we have to move to get from our current position to the point at which the ray intersected with an obstacle (setting the ray distance)
			if (hit) {

				float slopeAngle = Vector2.Angle (hit.normal, Vector2.up); 	//To handle slopes: Getting/checking the angle of the surface that we've hit.	(For explanation of equation see youtube tutorial Episode 04, at 1.06)

				if (i == 0 && slopeAngle <= maxClimbAngle) {			//If i = 0, meaning this is the bottom-most ray. 
					if (collisions.descendingSlope) {
						collisions.descendingSlope = false; 
						velocity = collisions.velocityOld; 
					}
					//print (slopeAngle);		//printout of the slopeAngle shown in console window in unity engine. We know that this is working so we can remove it, but leaving the code here for future use. 
					float distanceToSlopeStart = 0;	//fixing SLOPECLIMBING problems (Playerobject starts climbing slope before acctually hitting it, making it float slightly above the surface of the slope)
					if (slopeAngle != collisions.slopeAngleOld) {		//if starting to climb a new slope...
						distanceToSlopeStart = hit.distance-skinWidth; 
						velocity.x -= distanceToSlopeStart * directionX; 
					}
					ClimbSlope(ref velocity, slopeAngle);
					velocity.x += distanceToSlopeStart * directionX; 
				}

						
				if (!collisions.climbingSlope || slopeAngle > maxClimbAngle) { 	//if we are climbing the slope we don't need to check the rest of the rays because it might interfere with our slope climbing. Only time we want to check the other rays is if we're not climbing the slope. 
					velocity.x = (hit.distance - skinWidth) * directionX; 
					rayLength = hit.distance; 			//Changing the raylength once we hit something, so that all the rays only react to the hit object closest to the box. Detailed explanation: See youtube video Episode 02 at about 8.50)

					if (collisions.climbingSlope) {
						velocity.y = Mathf.Tan (collisions.slopeAngle * Mathf.Deg2Rad) * Mathf.Abs (velocity.x);
					}

					collisions.left = directionX == -1; 	//if hit we set the collider depending on the direction. This: If we hit something an we're going left, then collisions.left will be equal to true
					collisions.right = directionX == 1; 
				}
			}
		}
	}


	void VerticalCollisions(ref Vector3 velocity) {		//meaning of ref: Usually when you pass a variable into a method, it creates a copy of that variable. But in this instance, it's not passing a reference of the variable ("Vector 3 velocity"), so any change inside of this method that we make to velocity/the veriable, it will also change the variable we type ref beside)
		float directionY = Mathf.Sign (velocity.y);		//direction of y-velocity
		float rayLength = Mathf.Abs(velocity.y)/*absoluttverdien av velocity.y, forcing the value to be positive*/ + skinWidth;

		//forloop
		for (int i = 0; i < verticalRayCount; i++) {
			//See what direction we move in. If moving down, we want the rays to start in the bottomLeft corner. If moving up, want rays to start in the topLeft corner.
			Vector2 rayOrigin = (directionY == -1) ? raycastOrigins.bottomLeft : raycastOrigins.topLeft;	//explanation: (if directionY is equal to -1, aka we're moving down).. ?=if that's true, then.. we're setting rayOrigin equal to rayastOrigins.bottomLeft.. : = otherwise (if we're moving up).. we'll set it to raycastOrigins.topLeft;
			rayOrigin += Vector2.right * (verticalRaySpacing * i + velocity.x); 
			RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.up * directionY, rayLength, collisionMask); 	//if raycast hits something we want it to go up (?)

			Debug.DrawRay (rayOrigin, Vector2.up * directionY * rayLength, Color.red);

			//if our raycast hits something, the y velocity will be set equal to the amount that we have to move to get from our current position to the point at which the ray intersected with an obstacle (setting the ray distance)
			if (hit) {
				if (hit.collider.tag == "Through") {
					if (directionY == 1 || hit.distance == 0) {
						continue; 	//means skip everything here and go to the next Ray in a loop 
					}
				}

				velocity.y = (hit.distance - skinWidth) * directionY; 
				rayLength = hit.distance; 			//Changing the raylength once we hit something, so that all the rays only react to the hit object closest to the box. Detailed explanation: See youtube video Episode 02 at about 8.50)

				if (collisions.climbingSlope) {
					velocity.x = velocity.y / Mathf.Tan (collisions.slopeAngle * Mathf.Deg2Rad) * Mathf.Sign (velocity.x); 
				}

				collisions.below = directionY == -1; 
				collisions.above = directionY == 1; 
			}
		}

		if (collisions.climbingSlope) {		//fixing CLIMBINGSLOPE issues
			float directionX = Mathf.Sign (velocity.x); 
			rayLength = Mathf.Abs (velocity.x) + skinWidth;
			Vector2 rayOrigin = ((directionX == -1) ? raycastOrigins.bottomLeft : raycastOrigins.bottomRight) + Vector2.up * velocity.y;
			RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.right * directionX, rayLength, collisionMask); 

			if (hit) {
				float slopeAngle = Vector2.Angle (hit.normal, Vector2.up); 
				if (slopeAngle != collisions.slopeAngle) {
					velocity.x = (hit.distance - skinWidth) * directionX; 
					collisions.slopeAngle = slopeAngle; 
				}
			}
		}
	}

	void ClimbSlope(ref Vector3 velocity, float slopeAngle) {
		float moveDistance = Mathf.Abs (velocity.x);  		//for explanation of equation see episode 04 at 4.00. Using the Pythagorean equation/theorem.
		float climbVelocityY =  Mathf.Sin(slopeAngle * Mathf.Deg2Rad) * moveDistance;		//instead of directly setting velocity.y = Mathf.Sin(slopeAngle * Mathf.Deg2Rad) * moveDistance; because need to fix SLOPECLIMBING problems (can't jump)

		if (velocity.y <= climbVelocityY) {
			velocity.y = climbVelocityY;
			velocity.x = Mathf.Cos (slopeAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign (velocity.x); 
			collisions.below = true; 	//fixing slopeclimbing problems
			collisions.climbingSlope = true; 
			collisions.slopeAngle = slopeAngle; 
		}
	}

	void DescendSlope (ref Vector3 velocity) {
		float directionX = Mathf.Sign (velocity.x); 
		Vector2 rayOrigin = (directionX == -1) ? raycastOrigins.bottomRight : raycastOrigins.bottomLeft;
		RaycastHit2D hit = Physics2D.Raycast (rayOrigin, -Vector2.up, Mathf.Infinity, collisionMask); 

		if (hit) {
			float slopeAngle = Vector2.Angle (hit.normal, Vector2.up); 
			if (slopeAngle != 0 && slopeAngle <= maxDescendAngle) {
				if (Mathf.Sign (hit.normal.x) == directionX) {
					if (hit.distance - skinWidth <= Mathf.Tan (slopeAngle * Mathf.Deg2Rad) * Mathf.Abs (velocity.x)) {
						float moveDistance = Mathf.Abs (velocity.x);
						float descendVelocityY =  Mathf.Sin(slopeAngle * Mathf.Deg2Rad) * moveDistance;
						velocity.x = Mathf.Cos (slopeAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign (velocity.x);
						velocity.y -= descendVelocityY; 

						collisions.slopeAngle = slopeAngle;
						collisions.descendingSlope = true; 
						collisions.below = true; 
					}
				}
			}
		}
	}

	void UpdateRaycastOrigins () {
		Bounds bounds = collider2D.bounds; 
		//We want to shrink those bounds so that it's inset by skin width. Shrunken in on all sides by skinWidth. Makes the ray fire from a little bit inside of player and not just underneath (I don't remember the reaso for doing that but we do that)
		bounds.Expand (skinWidth * -2); 

		raycastOrigins.bottomLeft = new Vector2 (bounds.min.x, bounds.min.y); 
		raycastOrigins.bottomRight = new Vector2 (bounds.max.x, bounds.min.y);
		raycastOrigins.topLeft = new Vector2 (bounds.min.x, bounds.max.y);
		raycastOrigins.topRight = new Vector2 (bounds.max.x, bounds.max.y);
	}

	void CalculateRaySpacing() {
		Bounds bounds = collider2D.bounds; 
		bounds.Expand (skinWidth * -2); 

		//Making sure horizontalRayCount and verticalRayCount are greater than or equal to 2, since when we're firing them there needs to be one in each corner (making sure at least two fired in the horizontal and vertical directions)
		horizontalRayCount = Mathf.Clamp(horizontalRayCount, 2, int.MaxValue); 
		verticalRayCount = Mathf.Clamp(verticalRayCount, 2, int.MaxValue); 

		//calculating the spacing between each ray. 		(explanation of equation: If the horizontalRayCount was equal to 2, we'd be dividing bounds.size.y by 1, meaning that the spacing inbetween our two rays would be the entire length of the bounds.y)
		horizontalRaySpacing = bounds.size.y / (horizontalRayCount - 1);
		verticalRaySpacing = bounds.size.x / (verticalRayCount - 1);

	}

	//Setup work to easily get any of the corners of our BoxCollider
	//Creating struct to store these positions
	struct RaycastOrigins { 
		public Vector2 topLeft, topRight;
		public Vector2 bottomLeft, bottomRight; 
	}

	public struct CollisionInfo {		//finding out where the collisions are taking place 
		public bool above, below;
		public bool left, right; 

		public bool climbingSlope; 
		public bool descendingSlope; 
		public float slopeAngle, slopeAngleOld; 
		public Vector3 velocityOld; 

		public void Reset () {			//to reset all the above bools 
			above = below = false; 
			left = right = false; 
			climbingSlope = false; 
			descendingSlope = false; 

			slopeAngleOld = slopeAngle; 
			slopeAngle = 0; 
		}
	}

	// Update is called once per frame
	//void Update () {
		
	//}
}
