using UnityEngine;
using ScrambleKit;

public class PlayerMove : MonoBehaviour
{
	public FXTrigger jumpFX;

	public float jumpVelocity = 6f;
	public float jumpFloatForce = 20f;
	public float jumpSquashMax = 3.4f;

	public float jumpFloatTime = 0.1f;
	public float jumpAirControl = 1f;
	public float walkingSpeed = 3f;
	public float walkingAccel = 25f;

	public float coyoteTime = 0.05f;
	public float preJumpTime = 0.1f;

	public LayerMask walkOnLayers;
	public float groundProbeWidth = 0.5f;

	// INPUTS, to be assigned by player input, or AI
	public Vector2Int moveInput { get; set; }
	public bool jumpHeld;

	public void TriggerJump()
	{
		lastJumpTriggerTime = Time.fixedTime;
	}
	private void ResetJumpTriggerAndCoyoteTime()
	{
		lastJumpTriggerTime = -999f;
		coyoteSince = -999f;
	}


	public string DEBUG;
	public string DEBUG_VERT;

	public enum MoveState
	{
		Idle,
		Walk,
		Jumping,
		Falling
	}
	public MoveState state = MoveState.Idle;
	private float lastSwitchTime;
	private float lastJumpTime;
	private float coyoteSince;
	private float lastJumpTriggerTime;

	private Rigidbody2D r2;
	//private Animator anim;
	private SpriteRenderer rend;

	void Start()
	{
		r2 = gameObject.GetComponent<Rigidbody2D>();
		//anim = GetComponent<Animator>();
		rend = GetComponent<SpriteRenderer>();
	}

	void FixedUpdate()
	{
		if (moveInput.x > 0)
			rend.flipX = false;
		else if (moveInput.x < 0)
			rend.flipX = true;

		bool grounded = IsGrounded();
		if (grounded)
		{
			coyoteSince = Time.fixedTime;
		}

		switch (state)
		{
			case MoveState.Idle:
				if (!grounded)
					EnterState(MoveState.Falling);
				else if (moveInput.x != 0)
				{
					EnterState(MoveState.Walk);
				}
				else if (JumpTriggeredRecently() && DidntJustJump())
				{
					EnterState(MoveState.Jumping);
				}
				else
				{
					StopWalking();
				}
				break;

			case MoveState.Jumping:
				if (!jumpHeld || JumpFloatTimeOver() || r2.velocity.y < -0.1f)
				{
					EnterState(MoveState.Falling);
				}
				else
				{
					ApplyJumpFloat();
					ApplyAirControl();
				}
				break;

			case MoveState.Falling:
				if (grounded)
				{
					EnterState(MoveState.Idle);
				}
				else if (StillInCoyoteTime() && JumpTriggeredRecently() && DidntJustJump())  // Coyote jump
				{
					EnterState(MoveState.Jumping);
				}
				else
				{
					ApplyAirControl();
				}
				break;

			case MoveState.Walk:
				if (!grounded)
					EnterState(MoveState.Falling);
				else if (JumpTriggeredRecently())
					EnterState(MoveState.Jumping);
				else if (moveInput.x == 0)
					EnterState(MoveState.Idle);
				else
				{
					Walk();
				}
				break;
		}
	}

	private void EnterState(MoveState newState)
	{
		lastSwitchTime = Time.fixedTime;
		MoveState oldState = state;
		state = newState;

		switch (state)
		{
			case MoveState.Idle:
				//anim.SetTrigger("Idle");
				break;
			case MoveState.Jumping:
				//anim.SetTrigger("Jump");
				jumpFX.Trigger(transform);
				ResetJumpTriggerAndCoyoteTime();
				DoJump();

				break;
			case MoveState.Falling:
				//anim.SetTrigger("Fall");
				SquashJump();
				break;
			case MoveState.Walk:
				//anim.SetTrigger("Run");
				break;
		}

	}

	private void DoJump()
	{
		float cancelNegative = Mathf.Max(0, -r2.velocity.y);  // So Coyote time gives a full jump when already falling

		Vector2 accel = new Vector2(0, jumpVelocity + cancelNegative);
		Vector2 force = r2.mass * accel / Time.fixedDeltaTime;
		r2.AddForce(force);
		lastJumpTime = Time.fixedTime;
	}

	private void ApplyJumpFloat()
	{
		Vector2 accel = new Vector2(0, jumpFloatForce);
		Vector2 force = r2.mass * accel;
		r2.AddForce(force);
	}

	private void SquashJump()
	{
		float squash = Mathf.Min(0, jumpSquashMax - r2.velocity.y);

		//Debug.Log("Squashing " + squash + " of " + r2.velocity.y + " from jump");
		DEBUG = "" + squash + " from " + r2.velocity.y;
		Vector2 accel = new Vector2(0, squash);
		Vector2 force = r2.mass * accel / Time.fixedDeltaTime;
		r2.AddForce(force);
	}


	private bool JumpTriggeredRecently()
	{
		return (Time.fixedTime - lastJumpTriggerTime) < preJumpTime;
	}

	private bool DidntJustJump()
	{
		return (Time.fixedTime - lastJumpTime) > (coyoteTime + 0.02f);
	}

	private bool StillInCoyoteTime()
	{
		return (Time.fixedTime - coyoteSince) < coyoteTime;
	}

	private bool JumpFloatTimeOver()
	{
		return (Time.fixedTime - lastJumpTime) > jumpFloatTime;
	}

	private void ApplyAirControl()
	{
		int dir = moveInput.x;
		float stopFactor = (dir == 0 || Mathf.Sign(dir) != Mathf.Sign(r2.velocity.x)) ? 1.25f : 1;
		Vector2 targetVel = new Vector2(walkingSpeed * dir, r2.velocity.y);
		r2.AccelTowardVelocityByAtMost(targetVel, walkingAccel * jumpAirControl * stopFactor);


		//float maxspeed = WalkingSpeed * dir;
		//float delta = maxspeed - r2.velocity.x;
		//float accel = delta * ((dir == 0) ? 0.5f : 1) * JumpAirControl;
		//Vector2 accelV = new Vector2(accel, 0);

		//Vector2 force = r2.mass * accelV / Time.fixedDeltaTime;
		//r2.AddForce(force);
	}

	private void Walk()
	{
		int dir = moveInput.x;
		float stopFactor = (dir == 0 || Mathf.Sign(dir) != Mathf.Sign(r2.velocity.x)) ? 2 : 1;
		Vector2 targetVel = new Vector2(walkingSpeed * dir, r2.velocity.y);
		r2.AccelTowardVelocityByAtMost(targetVel, walkingAccel * jumpAirControl * stopFactor);


		//Vector2 targetspeed = new Vector2(1, 0) * WalkingSpeed * MoveInput.x;
		//Vector2 deltaspeed = targetspeed - r2.velocity;
		//Vector2 force = r2.mass * deltaspeed / Time.fixedDeltaTime;
		//r2.AddForce(force);
	}

	private void StopWalking()
	{
		Vector2 accel = new Vector2(-r2.velocity.x, 0);
		Vector2 force = r2.mass * accel / Time.fixedDeltaTime;
		r2.AddForce(force);
	}

	private bool IsGrounded()
	{
		float len = 0.8f;

		Vector2 ctr = (Vector2)transform.position + new Vector2(0, -0.25f);
		Vector2 left = ctr + new Vector2(-groundProbeWidth, 0);
		Vector2 right = ctr + new Vector2(groundProbeWidth, 0);

		RaycastHit2D res1 = Physics2D.Raycast(left, -Vector2.up, len, walkOnLayers);
		RaycastHit2D res2 = Physics2D.Raycast(ctr, -Vector2.up, len, walkOnLayers);
		RaycastHit2D res3 = Physics2D.Raycast(right, -Vector2.up, len, walkOnLayers);

		Debug.DrawLine(left, left - Vector2.up * len);
		Debug.DrawLine(ctr, ctr - Vector2.up * len);
		Debug.DrawLine(right, right - Vector2.up * len);

		return (res1.collider != null) || (res2.collider != null) || (res3.collider != null);
	}

}
