using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using MoreMountains.Tools;

namespace MoreMountains.CorgiEngine
{
[RequireComponent(typeof(BoxCollider2D))]

// DISCLAIMER : this controller's been built from the ground up for the Corgi Engine. It takes clues and inspirations from various methods and articles freely 
// available online. Special thanks to @prime31 for his talent and patience, Yoann Pignole, Mysteriosum and Sebastian Lague, among others for their great articles
// and tutorials on raycasting. If you have questions or suggestions, feel free to contact me at unitysupport@reuno.net

/// <summary>
/// The character controller that handles the character's gravity and collisions.
/// It requires a Collider2D and a rigidbody to function.
/// </summary>
public class CorgiController : MonoBehaviour 
{
	/// the various states of our character
	public CorgiControllerState State { get; protected set; }
	/// the initial parameters
	public CorgiControllerParameters DefaultParameters;
	/// the current parameters
	public CorgiControllerParameters Parameters{get{return _overrideParameters ?? DefaultParameters;}}
	
	[Space(10)]	
	[Header("Collision Masks")]
	/// The layer mask the platforms are on
	public LayerMask PlatformMask=0;
	/// The layer mask the moving platforms are on
	public LayerMask MovingPlatformMask=0;
	/// The layer mask the one way platforms are on
	public LayerMask OneWayPlatformMask=0;
	/// The layer mask the moving one way platforms are on
	public LayerMask MovingOneWayPlatformMask=0;
	/// gives you the object the character is standing on
	public GameObject StandingOn { get; protected set; }	
	/// the current velocity of the character
	public Vector2 Speed { get{ return _speed; } }
	/// the value of the forces applied at one point in time 
	public Vector2 ForcesApplied { get; protected set; }
	
	[Space(10)]	
	[Header("Raycasting")]
	/// the number of rays cast horizontally
	public int NumberOfHorizontalRays = 8;
	/// the number of rays cast vertically
	public int NumberOfVerticalRays = 8;
	/// a small value added to all raycasts to accomodate for edge cases	
	public float RayOffset=0.05f; 
	
	public Vector3 ColliderCenter {get
		{
			Vector3 colliderCenter = Vector3.Scale(transform.localScale, _boxCollider.offset);
			return colliderCenter;
		}}
	public Vector3 ColliderPosition {get
		{
			Vector3 colliderPosition = transform.position + ColliderCenter;
			return colliderPosition;
		}}
	public Vector3 ColliderSize {get
		{
			Vector3 colliderSize = Vector3.Scale(transform.localScale, _boxCollider.size);
			return colliderSize;
		}}
	public Vector3 BottomPosition {get
		{
			Vector3 colliderBottom = new Vector3(ColliderPosition.x,ColliderPosition.y - (ColliderSize.y / 2),ColliderPosition.z);
			return colliderBottom;
		}}
	public float Friction { get
		{
			return _friction;
		}}

    // parameters override storage
    protected CorgiControllerParameters _overrideParameters;
    // private local references			
    protected Vector2 _speed;
    protected float _friction=0;
    protected float _fallSlowFactor;
    protected Vector2 _externalForce;
    protected Vector2 _newPosition;
    protected Transform _transform;
    protected BoxCollider2D _boxCollider;
    protected GameObject _lastStandingOn;
    protected LayerMask _platformMaskSave;
	protected PathMovement _movingPlatform=null;
    protected float _movingPlatformCurrentGravity;
	protected bool _gravityActive=true;

    protected const float _largeValue=500000f;
    protected const float _smallValue=0.0001f;
    protected const float _obstacleHeightTolerance=0.05f;
	protected const float _movingPlatformsGravity=-500;

    protected Vector2 _originalColliderSize;
    protected Vector2 _originalColliderOffset;

    // rays parameters
    protected Rect _rayBoundsRectangle;

    protected List<RaycastHit2D> _contactList;

    /// <summary>
    /// initialization
    /// </summary>
    protected virtual void Awake()
	{
		// we get the various components
		_transform=transform;
		_boxCollider = (BoxCollider2D)GetComponent<BoxCollider2D>();
		_originalColliderSize = _boxCollider.size;
		_originalColliderOffset = _boxCollider.offset;
		
		// we test the boxcollider's x offset. If it's not null we trigger a warning.
		if (_boxCollider.offset.x!=0)
		{
			Debug.LogWarning("The boxcollider for "+gameObject.name+" should have an x offset set to zero. Right now this may cause issues when you change direction close to a wall.");
		}

		// raycast list and state init
		_contactList = new List<RaycastHit2D>();
		State = new CorgiControllerState();
		
		// we add the edge collider platform and moving platform masks to our initial platform mask so they can be walked on	
		_platformMaskSave = PlatformMask;	
		PlatformMask |= OneWayPlatformMask;
		PlatformMask |= MovingPlatformMask;
		PlatformMask |= MovingOneWayPlatformMask;
		
		State.Reset();
		SetRaysParameters();
	}

    /// <summary>
    /// Use this to add force to the character
    /// </summary>
    /// <param name="force">Force to add to the character.</param>
    public virtual void AddForce(Vector2 force)
	{
		_speed += force;	
		_externalForce += force;
	}
	
	/// <summary>
	///  use this to set the horizontal force applied to the character
	/// </summary>
	/// <param name="x">The x value of the velocity.</param>
	public virtual void AddHorizontalForce(float x)
	{
		_speed.x += x;
		_externalForce.x += x;
	}
	
	/// <summary>
	///  use this to set the vertical force applied to the character
	/// </summary>
	/// <param name="y">The y value of the velocity.</param>
	public virtual void AddVerticalForce(float y)
	{
		_speed.y += y;
		_externalForce.y += y;
	}
	
	/// <summary>
	/// Use this to set the force applied to the character
	/// </summary>
	/// <param name="force">Force to apply to the character.</param>
	public virtual void SetForce(Vector2 force)
	{
		_speed = force;
		_externalForce = force;	
	}
	
	/// <summary>
	///  use this to set the horizontal force applied to the character
	/// </summary>
	/// <param name="x">The x value of the velocity.</param>
	public virtual void SetHorizontalForce (float x)
	{
		_speed.x = x;
		_externalForce.x = x;
	}
	
	/// <summary>
	///  use this to set the vertical force applied to the character
	/// </summary>
	/// <param name="y">The y value of the velocity.</param>
	public virtual void SetVerticalForce (float y)
	{
		_speed.y = y;
		_externalForce.y = y;
		
	}

    /// <summary>
    /// This is called every frame
    /// </summary>
    protected virtual void Update()
	{	
		EveryFrame();
	}

	/// <summary>
	/// Every frame, we apply the gravity to our character, then check using raycasts if an object's been hit, and modify its new position 
    /// accordingly. When all the checks have been done, we apply that new position. 
	/// </summary>
	protected virtual void EveryFrame()
	{
		_contactList.Clear();

		if (_gravityActive)
		{
			_speed.y += (Parameters.Gravity + _movingPlatformCurrentGravity) * Time.deltaTime;
		}
				
		if (_fallSlowFactor!=0)
		{
			_speed.y*=_fallSlowFactor;
		}

		// we initialize our newposition, which we'll use in all the next computations			
		_newPosition=Speed * Time.deltaTime;
					
		State.WasGroundedLastFrame = State.IsCollidingBelow;
		State.WasTouchingTheCeilingLastFrame = State.IsCollidingAbove;
		State.Reset(); 

		// we initialize our rays
		SetRaysParameters();

		HandleMovingPlatforms();

		// we store our current speed for use in moving platforms mostly
		ForcesApplied = _speed;

		// we cast rays on all sides to check for slopes and collisions
		CastRaysToTheSides();
		CastRaysBelow();	
		CastRaysAbove();

		// we move our transform to its next position
		_transform.Translate(_newPosition,Space.World);			

		SetRaysParameters();	

		// we compute the new speed
		if (Time.deltaTime > 0)
		{
			_speed = _newPosition / Time.deltaTime;	
		}	

		// we apply our slope speed factor based on the slope's angle
		if (State.IsGrounded)
		{
			_speed.x *= Parameters.SlopeAngleSpeedFactor.Evaluate(State.BelowSlopeAngle * Mathf.Sign(_speed.y));
		}

		if (!State.OnAMovingPlatform)				
		{
			// we make sure the velocity doesn't exceed the MaxVelocity specified in the parameters
			_speed.x = Mathf.Clamp(_speed.x,-Parameters.MaxVelocity.x,Parameters.MaxVelocity.x);
			_speed.y = Mathf.Clamp(_speed.y,-Parameters.MaxVelocity.y,Parameters.MaxVelocity.y);
		}

		
		// we change states depending on the outcome of the movement
		if( !State.WasGroundedLastFrame && State.IsCollidingBelow )
			State.JustGotGrounded=true;
			
		if (State.IsCollidingLeft || State.IsCollidingRight || State.IsCollidingBelow || State.IsCollidingRight)
		{
			OnCorgiColliderHit();
		}		


		
		_externalForce.x=0;
		_externalForce.y=0;
	}

	/// <summary>
	/// If the CorgiController is standing on a moving platform, we match its speed
	/// </summary>
	protected virtual void HandleMovingPlatforms()
	{
		if (_movingPlatform!=null)			
		{

			if (!float.IsNaN(_movingPlatform.CurrentSpeed.x) && !float.IsNaN(_movingPlatform.CurrentSpeed.y) && !float.IsNaN(_movingPlatform.CurrentSpeed.z))
			{
				_transform.Translate(_movingPlatform.CurrentSpeed*Time.deltaTime);
			}

			if ( (Time.timeScale==0) || float.IsNaN(_movingPlatform.CurrentSpeed.x) || float.IsNaN(_movingPlatform.CurrentSpeed.y) || float.IsNaN(_movingPlatform.CurrentSpeed.z) )
			{
				return;
			}

			if ((Time.deltaTime<=0))
			{
				return;
			}

			State.OnAMovingPlatform=true;

			GravityActive(false);

			_movingPlatformCurrentGravity=_movingPlatformsGravity;

			_newPosition.y = _movingPlatform.CurrentSpeed.y*Time.deltaTime;		

			_speed = - _newPosition / Time.deltaTime;	
			SetRaysParameters();
		}
	}

	/// <summary>
	/// Disconnects the CorgiController from its current moving platform.
	/// </summary>
	public virtual void DetachFromMovingPlatform()
	{
		State.OnAMovingPlatform=false;
		_movingPlatform=null;
		_movingPlatformCurrentGravity=0;
	}

    /// <summary>
    /// Casts rays to the sides of the character, from its center axis.
    /// If we hit a wall/slope, we check its angle and move or not according to it.
    /// </summary>
    protected virtual void CastRaysToTheSides() 
	{			
		float movementDirection=1;	
		if ((_speed.x < 0) || (_externalForce.x<0))
			movementDirection = -1;
		
		float horizontalRayLength = Mathf.Abs(_speed.x*Time.deltaTime) + _rayBoundsRectangle.width/2 + RayOffset*2;
		
		Vector2 horizontalRayCastFromBottom=new Vector2(_rayBoundsRectangle.center.x,
		                                                _rayBoundsRectangle.yMin+_obstacleHeightTolerance);										
		Vector2 horizontalRayCastToTop=new Vector2(	_rayBoundsRectangle.center.x,
		                                           _rayBoundsRectangle.yMax-_obstacleHeightTolerance);				
		
		RaycastHit2D[] hitsStorage = new RaycastHit2D[NumberOfHorizontalRays];	
				
		for (int i=0; i<NumberOfHorizontalRays;i++)
		{	
			Vector2 rayOriginPoint = Vector2.Lerp(horizontalRayCastFromBottom,horizontalRayCastToTop,(float)i/(float)(NumberOfHorizontalRays-1));
			
			if ( State.WasGroundedLastFrame && i == 0 )			
				hitsStorage[i] = MMDebug.RayCast (rayOriginPoint,movementDirection*(Vector2.right),horizontalRayLength,PlatformMask,Color.red,Parameters.DrawRaycastsGizmos);	
			else
				hitsStorage[i] = MMDebug.RayCast (rayOriginPoint,movementDirection*(Vector2.right),horizontalRayLength,PlatformMask & ~OneWayPlatformMask & ~MovingOneWayPlatformMask,Color.red,Parameters.DrawRaycastsGizmos);			
			
			if (hitsStorage[i].distance >0)
			{						
				float hitAngle = Mathf.Abs(Vector2.Angle(hitsStorage[i].normal, Vector2.up));		
				
				State.LateralSlopeAngle = hitAngle	;					
				
				if (hitAngle > Parameters.MaximumSlopeAngle)
				{												
					if (movementDirection < 0)		
						State.IsCollidingLeft=true;
					else
						State.IsCollidingRight=true;						
					
					State.SlopeAngleOK=false;
					
					if (movementDirection<=0)
					{
						_newPosition.x = -Mathf.Abs(hitsStorage[i].point.x - horizontalRayCastFromBottom.x) 
							+ _rayBoundsRectangle.width/2 
								+ RayOffset*2;
					}
					else
					{						
						_newPosition.x = Mathf.Abs(hitsStorage[i].point.x - horizontalRayCastFromBottom.x) 
							- _rayBoundsRectangle.width/2 
								- RayOffset*2;						
					}			

					// if we're in the air, we prevent the character from being pushed back.
					if (!State.IsGrounded)
					{
						_newPosition.x=0;
					}		
					
					_contactList.Add(hitsStorage[i]);
					_speed = new Vector2(0, _speed.y);
					break;
				}
			}						
		}
		
		
	}

    /// <summary>
    /// Every frame, we cast a number of rays below our character to check for platform collisions
    /// </summary>
    protected virtual void CastRaysBelow()
	{
		_friction=0;

		if (_newPosition.y < -_smallValue)
		{
			State.IsFalling=true;
		}
		else
		{
			State.IsFalling = false;
		}
		
		if ((Parameters.Gravity > 0) && (!State.IsFalling))
			return;
		
		float rayLength = _rayBoundsRectangle.height/2 + RayOffset ; 	

		if (State.OnAMovingPlatform)
		{
			rayLength*=2;
		}	

		if (_newPosition.y<0)
		{
			rayLength+=Mathf.Abs(_newPosition.y);
		}			
		
		Vector2 verticalRayCastFromLeft=new Vector2(_rayBoundsRectangle.xMin+_newPosition.x,
		                                            _rayBoundsRectangle.center.y+RayOffset);	
		Vector2 verticalRayCastToRight=new Vector2(	_rayBoundsRectangle.xMax+_newPosition.x,
		                                           _rayBoundsRectangle.center.y+RayOffset);					
		
		RaycastHit2D[] hitsStorage = new RaycastHit2D[NumberOfVerticalRays];
		float smallestDistance=_largeValue; 
		int smallestDistanceIndex=0; 						
		bool hitConnected=false; 		
		
		for (int i=0; i<NumberOfVerticalRays;i++)
		{			
			Vector2 rayOriginPoint = Vector2.Lerp(verticalRayCastFromLeft,verticalRayCastToRight,(float)i/(float)(NumberOfVerticalRays-1));
			
			if ((_newPosition.y>0) && (!State.WasGroundedLastFrame))
				hitsStorage[i] = MMDebug.RayCast (rayOriginPoint,-(Vector2.up),rayLength,PlatformMask & ~OneWayPlatformMask & ~MovingOneWayPlatformMask,Color.blue,Parameters.DrawRaycastsGizmos);	
			else
				hitsStorage[i] = MMDebug.RayCast (rayOriginPoint,-(Vector2.up),rayLength,PlatformMask,Color.blue,Parameters.DrawRaycastsGizmos);					
			
			if ((Mathf.Abs(hitsStorage[smallestDistanceIndex].point.y - verticalRayCastFromLeft.y)) <  _smallValue)
			{
				break;
			}		
			
			if (hitsStorage[i])
			{
				hitConnected=true;
				State.BelowSlopeAngle = Vector2.Angle( hitsStorage[i].normal, Vector2.up )  ;
				if (hitsStorage[i].distance<smallestDistance)
				{
					smallestDistanceIndex=i;
					smallestDistance = hitsStorage[i].distance;
				}
			}								
		}
		if (hitConnected)
		{
			
			StandingOn=hitsStorage[smallestDistanceIndex].collider.gameObject;
		
			// if the character is jumping onto a (1-way) platform but not high enough, we do nothing
			if (
				!State.WasGroundedLastFrame 
				&& (smallestDistance<_rayBoundsRectangle.size.y/2) 
				&& (
					StandingOn.layer==LayerMask.NameToLayer("OneWayPlatforms")  
					||
					StandingOn.layer==LayerMask.NameToLayer("MovingOneWayPlatforms") 
					) 
				)
			{
				State.IsCollidingBelow=false;
				return;
			}
		
			State.IsFalling=false;			
			State.IsCollidingBelow=true;
										

			// if we're applying an external force (jumping, jetpack...) we only apply that
			if (_externalForce.y>0)
			{
				_newPosition.y = _speed.y * Time.deltaTime;
				State.IsCollidingBelow = false;
			}
			// if not, we just adjust the position based on the raycast hit
			else
			{
				_newPosition.y = -Mathf.Abs(hitsStorage[smallestDistanceIndex].point.y - verticalRayCastFromLeft.y) 
				+ _rayBoundsRectangle.height/2 
					+ RayOffset;
			}
			
			if (!State.WasGroundedLastFrame && _speed.y>0)
			{
				_newPosition.y += _speed.y * Time.deltaTime;
			}				
			
			if (Mathf.Abs(_newPosition.y)<_smallValue)
				_newPosition.y = 0;

			// we check if whatever we're standing on applies a friction change
			if (hitsStorage[smallestDistanceIndex].collider.GetComponent<SurfaceModifier>()!=null)
			{
				_friction=hitsStorage[smallestDistanceIndex].collider.GetComponent<SurfaceModifier>().Friction;
			}
			
			// we check if the character is standing on a moving platform
			PathMovement movingPlatform = hitsStorage[smallestDistanceIndex].collider.GetComponent<PathMovement>();

			if (movingPlatform!=null && State.IsGrounded)
			{
				_movingPlatform=movingPlatform;
			}
		}
		else
		{
			State.IsCollidingBelow=false;
			if(State.OnAMovingPlatform)
			{
				DetachFromMovingPlatform();
			}
		}	
	}

    /// <summary>
    /// If we're in the air and moving up, we cast rays above the character's head to check for collisions
    /// </summary>
    protected virtual void CastRaysAbove()
	{			
		if (_newPosition.y<0)
			return;
		
		float rayLength = State.IsGrounded?RayOffset : _newPosition.y*Time.deltaTime;
		rayLength+=_rayBoundsRectangle.height/2;
		
		bool hitConnected=false; 
		
		Vector2 verticalRayCastStart=new Vector2(_rayBoundsRectangle.xMin+_newPosition.x,
		                                         _rayBoundsRectangle.center.y);	
		Vector2 verticalRayCastEnd=new Vector2(	_rayBoundsRectangle.xMax+_newPosition.x,
		                                       _rayBoundsRectangle.center.y);	
		
		RaycastHit2D[] hitsStorage = new RaycastHit2D[NumberOfVerticalRays];
		float smallestDistance=_largeValue; 
		
		for (int i=0; i<NumberOfVerticalRays;i++)
		{							
			Vector2 rayOriginPoint = Vector2.Lerp(verticalRayCastStart,verticalRayCastEnd,(float)i/(float)(NumberOfVerticalRays-1));
			hitsStorage[i] = MMDebug.RayCast (rayOriginPoint,(Vector2.up),rayLength,PlatformMask & ~OneWayPlatformMask & ~MovingOneWayPlatformMask,Color.green,Parameters.DrawRaycastsGizmos);	

			if (hitsStorage[i])
			{
				hitConnected=true;
				if (hitsStorage[i].distance<smallestDistance)
				{
					smallestDistance = hitsStorage[i].distance;
				}
			}					
		}	

		if (hitConnected)
		{
			_newPosition.y = smallestDistance - _rayBoundsRectangle.height/2   ;
			
			if ( (State.IsGrounded) && (_newPosition.y<0) )
			{
				_newPosition.y=0;
			}
						
			State.IsCollidingAbove=true;
			
			if (!State.WasTouchingTheCeilingLastFrame)
			{
				_newPosition.x = 0;
				_newPosition.y = -0.02f;
				_speed = Vector2.zero;
			}
		}	
	}

    /// <summary>
    /// Creates a rectangle with the boxcollider's size for ease of use and draws debug lines along the different raycast origin axis
    /// </summary>
    public virtual void SetRaysParameters() 
	{		
		
		_rayBoundsRectangle = new Rect(_boxCollider.bounds.min.x,
		                               _boxCollider.bounds.min.y,
		                               _boxCollider.bounds.size.x,
		                               _boxCollider.bounds.size.y);	


		Debug.DrawLine(new Vector2(_rayBoundsRectangle.center.x,_rayBoundsRectangle.yMin),new Vector2(_rayBoundsRectangle.center.x,_rayBoundsRectangle.yMax),Color.yellow);  
		Debug.DrawLine(new Vector2(_rayBoundsRectangle.xMin,_rayBoundsRectangle.center.y),new Vector2(_rayBoundsRectangle.xMax,_rayBoundsRectangle.center.y),Color.yellow);
		
		
		
	}
	
	
	/// <summary>
	/// Disables the collisions for the specified duration
	/// </summary>
	/// <param name="duration">the duration for which the collisions must be disabled</param>
	public virtual IEnumerator DisableCollisions(float duration)
	{
		// we turn the collisions off
		CollisionsOff();
		// we wait for a few seconds
		yield return new WaitForSeconds (duration);
		// we turn them on again
		CollisionsOn();
	}

	/// <summary>
	/// Disables the collisions with one way platforms for the specified duration
	/// </summary>
	/// <param name="duration">the duration for which the collisions must be disabled</param>
	public virtual IEnumerator DisableCollisionsWithOneWayPlatforms(float duration)
	{
		// we turn the collisions off
		CollisionsOffWithOneWayPlatforms ();
		// we wait for a few seconds
		yield return new WaitForSeconds (duration);
		// we turn them on again
		CollisionsOn();
	}

	/// <summary>
	/// Disables the collisions with moving platforms for the specified duration
	/// </summary>
	/// <param name="duration">the duration for which the collisions must be disabled</param>
	public virtual IEnumerator DisableCollisionsWithMovingPlatforms(float duration)
	{
		// we turn the collisions off
		CollisionsOffWithMovingPlatforms ();
		// we wait for a few seconds
		yield return new WaitForSeconds (duration);
		// we turn them on again
		CollisionsOn();
	}



	/// <summary>
	/// Resets the collision mask with the default settings
	/// </summary>
	public virtual void CollisionsOn()
	{
		PlatformMask=_platformMaskSave;
		PlatformMask |= OneWayPlatformMask;
		PlatformMask |= MovingPlatformMask;
		PlatformMask |= MovingOneWayPlatformMask;
	}

	/// <summary>
	/// Turns all collisions off
	/// </summary>
	public virtual void CollisionsOff()
	{
		PlatformMask=0;
	}

	/// <summary>
	/// Disables collisions only with the one way platform layers
	/// </summary>
	public virtual void CollisionsOffWithOneWayPlatforms()
	{

		PlatformMask -= OneWayPlatformMask;
		PlatformMask -= MovingOneWayPlatformMask;
	}

	/// <summary>
	/// Disables collisions only with moving platform layers
	/// </summary>
	public virtual void CollisionsOffWithMovingPlatforms()
	{
		PlatformMask -= MovingPlatformMask;
		PlatformMask -= MovingOneWayPlatformMask;
	}

	/// <summary>
	/// Resets all overridden parameters.
	/// </summary>
	public virtual void ResetParameters()
	{
		_overrideParameters = DefaultParameters;
	}

	/// <summary>
	/// Slows the character's fall by the specified factor.
	/// </summary>
	/// <param name="factor">Factor.</param>
	public virtual void SlowFall(float factor)
	{
		_fallSlowFactor=factor;
	}

	/// <summary>
    /// Activates or desactivates the gravity for this character only.
    /// </summary>
    /// <param name="state">If set to <c>true</c>, activates the gravity. If set to <c>false</c>, turns it off.</param>	   
	public virtual void GravityActive(bool state)
	{
		if (state)
		{
			_gravityActive = true;
		}
		else
		{
			_gravityActive = false;
		}
	}

	public virtual void ResizeCollider(Vector2 newSize)
	{
		float newYOffset =_originalColliderOffset.y -  (_originalColliderSize.y - newSize.y)/2 ;

		_boxCollider.size = newSize;
		_boxCollider.offset = newYOffset*Vector3.up;
		SetRaysParameters();
	}

	public virtual void ResetColliderSize()
	{
		_boxCollider.size = _originalColliderSize;
		_boxCollider.offset = _originalColliderOffset;
		SetRaysParameters();
	}

	public virtual bool CanGoBackToOriginalSize()
	{
		// if we're already at original size, we return true
		if (_boxCollider.size == _originalColliderSize)
		{
			return true;
		}
		float headCheckDistance = _originalColliderSize.y*transform.localScale.y ;
		bool headCheck = MMDebug.RayCast(_boxCollider.bounds.min+(Vector3.up*_smallValue),Vector2.up,headCheckDistance,PlatformMask,Color.cyan,true);
		return headCheck;
	}

    // Events


    /// <summary>
    /// triggered when the character's raycasts collide with something 
    /// </summary>
    protected virtual void OnCorgiColliderHit() 
	{
		foreach (RaycastHit2D hit in _contactList )
		{			
			Rigidbody2D body = hit.collider.attachedRigidbody;
			if (body == null || body.isKinematic)
				return;
						
			Vector3 pushDir = new Vector3(_externalForce.x, 0, 0);
						
			body.velocity = pushDir.normalized * Parameters.Physics2DPushForce;		
		}		
	}

    /// <summary>
    /// triggered when the character enters a collider
    /// </summary>
    /// <param name="collider">the object we're colliding with.</param>
    protected virtual void OnTriggerEnter2D(Collider2D collider)
	{
		
		CorgiControllerPhysicsVolume2D parameters = collider.gameObject.GetComponent<CorgiControllerPhysicsVolume2D>();
		if (parameters == null)
			return;
		// if the object we're colliding with has parameters, we apply them to our character.
		_overrideParameters = parameters.ControllerParameters;
	}

    /// <summary>
    /// triggered while the character stays inside another collider
    /// </summary>
    /// <param name="collider">the object we're colliding with.</param>
    protected virtual void OnTriggerStay2D( Collider2D collider )
	{
	}

    /// <summary>
    /// triggered when the character exits a collider
    /// </summary>
    /// <param name="collider">the object we're colliding with.</param>
    protected virtual void OnTriggerExit2D(Collider2D collider)
	{		
		CorgiControllerPhysicsVolume2D parameters = collider.gameObject.GetComponent<CorgiControllerPhysicsVolume2D>();
		if (parameters == null)
			return;
		
		// if the object we were colliding with had parameters, we reset our character's parameters
		_overrideParameters = null;
	}				
}

}