using UnityEngine;
using ScrambleKit.Move;
using ScrambleKit.FX;

public class PlayerMovement : 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 JumpHeight = 4f;
	public float CoyoteTime = 0.05f;
	public float PreJumpTime = 0.1f;

	public LayerMask WalkOnLayers;
	public float groundProbeWidth = 05f;

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

	public float LastStandingHeight { get; private set; }

	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 jumpUntilHeight;
	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 (DigGame.GameOver)
			return;

		if (MoveInput.x > 0)
			rend.flipX = false;
		else if (MoveInput.x < 0)
			rend.flipX = true;

		bool grounded = IsGrounded();
		if (grounded)
		{
			LastStandingHeight = transform.position.y;
			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 =  transform.position;
		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);
	}

}
