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

/*
--------------------------------------
    Project: Programing assessment
    Standard: 91906 (AS3.7) v.1
    School: Tauranga Boys' College
    Author: Rauputu Noah Phizacklea
    Date: August 2024
    Unity: 2021.3.18f
---------------------------------------
*/

public class PlayerMovement : MonoBehaviour
{
    [SerializeField] private Transform groundCheck; // Transform to check if the player is grounded
    [SerializeField] private LayerMask groundLayer; // Layer mask for ground detection
    [SerializeField] private Rigidbody2D myRigidbody; // Reference to the Rigidbody2D component
    [SerializeField] private Transform wallCheck; // Transform to check if the player is against a wall
    [SerializeField] private LayerMask wallLayer; // Layer mask for wall detection
    [SerializeField] private StaminaBar staminaBar; // Reference to the stamina bar UI

    public Animator myAnimator; // Reference to the Animator component

    private float horizontal; // Horizontal movement input
    private float speed = 8f; // Movement speed
    private float jumpingPower = 16f; // Jumping power
    private bool isFacingRight = true; // Whether the player is facing right
    private bool canRoll = true; // Whether the player can roll
    private bool isRolling; // Whether the player is currently rolling
    private float rollingPower = 24f; // Power of the roll
    private float rollingTime = 0.2f; // Duration of the roll
    private float rollingCooldown = 1f; // Cooldown time before rolling again
    private bool isWallSliding; // Whether the player is sliding down a wall
    private float wallSlidingSpeed = 2f; // Speed at which the player slides down a wall
    private bool isWallJumping; // Whether the player is performing a wall jump
    private float wallJumpingDirection; // Direction of the wall jump
    private float wallJumpingTime = 0.2f; // Time during which wall jumping is possible
    private float wallJumpingCounter; // Counter to track wall jump time
    private float wallJumpingDuration = 0.4f; // Duration of the wall jump
    private Vector2 wallJumpingPower = new Vector2(8f, 16f); // Power of the wall jump
    private bool doubleJump; // Whether the player can double jump
    private float coyoteTime = 0.2f; // Time after leaving the ground where jumping is still allowed
    private float coyoteTimeCounter; // Counter for coyote time
    private float jumpBufferTime = 0.2f; // Time to allow jump input after leaving the ground
    private float jumpBufferCounter; // Counter for jump buffer time
    public int rollCost = 30; // Stamina cost for rolling
    private PlayerStamina playerStamina; // Reference to the PlayerStamina component

    Vector2 moveInput; // Movement input vector
    CapsuleCollider2D myBodyCollider; // Reference to the CapsuleCollider2D component

    private bool isMovementDisabled; // Whether player movement is currently disabled

    // Original collider size and offset
    private Vector2 originalColliderSize;
    private Vector2 originalColliderOffset;

    // Rolling collider size and offset
    private Vector2 rollingColliderSize = new Vector2(0.5f, 0.3f); // Adjust as necessary
    private Vector2 rollingColliderOffset = new Vector2(0f, 0.4f); // Adjust as necessary
    AudioManager audioManager; // Reference to the AudioManager component

    void Awake() 
    {
        // Initialize audio manager
        audioManager = GameObject.FindGameObjectWithTag("Audio").GetComponent<AudioManager>();
    }

    void Start()
    {
        // Initialize components and variables
        myAnimator = GetComponent<Animator>();
        myBodyCollider = GetComponent<CapsuleCollider2D>();
        playerStamina = GetComponent<PlayerStamina>();

        originalColliderSize = myBodyCollider.size; // Save original collider size
        originalColliderOffset = myBodyCollider.offset; // Save original collider offset
    }

    void Update()
    {
        // Skip update if movement is disabled or player is rolling
        if (isMovementDisabled || isRolling)
        {
            return;
        }

        // Update animator and rolling capability based on ground state
        if (!isGrounded())
        {
            myAnimator.SetBool("isJumping", true);
            canRoll = false;
        }

        // Get horizontal input for movement
        horizontal = Input.GetAxisRaw("Horizontal");

        // Reset double jump if grounded and jump button not pressed
        if (isGrounded() && !Input.GetButton("Jump"))
        {
            doubleJump = false;
        }

        // Update state if grounded
        if (isGrounded())
        {
            canRoll = true;
            myAnimator.SetBool("isJumping", false);
            coyoteTimeCounter = coyoteTime; // Reset coyote time
        }
        else
        {
            coyoteTimeCounter -= Time.deltaTime; // Decrease coyote time
        }

        // Handle jump input buffering
        if (Input.GetButtonDown("Jump"))
        {
            jumpBufferCounter = jumpBufferTime; // Start jump buffer timer
        }
        else
        {
            jumpBufferCounter -= Time.deltaTime; // Decrease jump buffer timer
        }

        // Handle roll input and stamina usage
        if (Input.GetKeyDown(KeyCode.LeftShift) && canRoll && playerStamina.UseStamina(rollCost))
        {
            StartCoroutine(Roll()); // Start rolling coroutine
        }

        // Handle jumping with buffer and coyote time
        if (jumpBufferCounter > 0f && (coyoteTimeCounter > 0f || doubleJump))
        {
            myRigidbody.velocity = new Vector2(myRigidbody.velocity.x, jumpingPower); // Apply jump force
            jumpBufferCounter = 0f; // Reset jump buffer
            doubleJump = !doubleJump; // Toggle double jump
        }

        // Modify jump if jump button released early
        if (Input.GetButtonUp("Jump") && myRigidbody.velocity.y > 0f)
        {
            myRigidbody.velocity = new Vector2(myRigidbody.velocity.x, myRigidbody.velocity.y * 0.5f); // Reduce jump height
            coyoteTimeCounter = 0f; // Reset coyote time
        }

        // Handle wall sliding and wall jumping
        WallSlide();
        WallJump();

        // Flip player sprite based on movement direction
        if (!isWallJumping)
        {
            Flip();
        }

        // Update running animation based on movement
        UpdateRunningAnimation();
    }

    void FixedUpdate()
    {
        // Ensure rolling animation overrides running animation
        if (myAnimator.GetBool("isRolling"))
        {
            myAnimator.SetBool("isRunning", false);
        }

        // Skip FixedUpdate if movement is disabled or player is rolling
        if (isMovementDisabled || isRolling)
        {
            return;
        }

        // Apply movement velocity if not wall jumping
        if (!isWallJumping)
        {
            myRigidbody.velocity = new Vector2(horizontal * speed, myRigidbody.velocity.y);
        }
    }

    // Check if player is grounded
    private bool isGrounded()
    {
        return Physics2D.OverlapCircle(groundCheck.position, 0.2f, groundLayer);
    }

    // Check if player is against a wall
    private bool isWalled()
    {
        return Physics2D.OverlapCircle(wallCheck.position, 0.2f, wallLayer);
    }

    // Handle wall sliding mechanics
    private void WallSlide()
    {
        if (isWalled() && !isGrounded() && horizontal != 0f)
        {
            isWallSliding = true;
            myRigidbody.velocity = new Vector2(myRigidbody.velocity.x, Mathf.Clamp(myRigidbody.velocity.y, -wallSlidingSpeed, float.MaxValue));
        }
        else
        {
            isWallSliding = false;
        }
    }

    // Handle wall jumping mechanics
    private void WallJump()
    {
        if (isWallSliding)
        {
            isWallJumping = false;
            wallJumpingDirection = -transform.localScale.x;
            wallJumpingCounter = wallJumpingTime; // Reset wall jumping timer

            CancelInvoke(nameof(StopWallJumping)); // Cancel previous wall jump invocations
        }
        else
        {
            wallJumpingCounter -= Time.deltaTime; // Decrease wall jumping timer
        }

        // Perform wall jump if jump button pressed and wall jump time is available
        if (Input.GetButtonDown("Jump") && wallJumpingCounter > 0f)
        {
            isWallJumping = true;
            myRigidbody.velocity = new Vector2(wallJumpingDirection * wallJumpingPower.x, wallJumpingPower.y); // Apply wall jump force
            wallJumpingCounter = 0f; // Reset wall jumping timer

            // Flip player sprite based on wall jump direction
            if (transform.localScale.x != wallJumpingDirection)
            {
                isFacingRight = !isFacingRight;
                Vector3 localScale = transform.localScale;
                localScale.x *= -1f;
                transform.localScale = localScale;
            }

            Invoke(nameof(StopWallJumping), wallJumpingDuration); // Stop wall jumping after duration
        }
    }

    // Stop wall jumping
    private void StopWallJumping()
    {
        isWallJumping = false;
    }

    // Flip player sprite based on movement direction
    private void Flip()
    {
        if (isFacingRight && horizontal < 0f || !isFacingRight && horizontal > 0f)
        {
            isFacingRight = !isFacingRight;
            Vector3 localScale = transform.localScale;
            localScale.x *= -1f;
            transform.localScale = localScale;
        }
    }

    // Handle rolling mechanic
    private IEnumerator Roll()
    {
        audioManager.PlaySFX(audioManager.rollSFX); // Play roll sound effect
        canRoll = false;
        isRolling = true;
        float orgGravity = myRigidbody.gravityScale; // Save original gravity scale
        myRigidbody.gravityScale = 0f; // Disable gravity during roll
        myRigidbody.velocity = new Vector2(transform.localScale.x * rollingPower, 0f); // Apply roll force
        myAnimator.SetBool("isRolling", true); // Set rolling animation

        // Adjust collider size and offset for rolling
        myBodyCollider.size = rollingColliderSize;
        myBodyCollider.offset = rollingColliderOffset;
        Physics2D.IgnoreLayerCollision(gameObject.layer, LayerMask.NameToLayer("Enemy"), true); // Ignore collisions with enemies

        yield return new WaitForSeconds(rollingTime); // Wait for roll duration

        // Restore original collider size and offset
        myBodyCollider.size = originalColliderSize;
        myBodyCollider.offset = originalColliderOffset;
        Physics2D.IgnoreLayerCollision(gameObject.layer, LayerMask.NameToLayer("Enemy"), false); // Resume collisions with enemies

        myRigidbody.gravityScale = orgGravity; // Restore original gravity scale
        isRolling = false;
        myAnimator.SetBool("isRolling", false); // Stop rolling animation

        yield return new WaitForSeconds(rollingCooldown); // Wait for roll cooldown
        canRoll = true;
    }

    // Update running animation based on horizontal speed
    private void UpdateRunningAnimation()
    {
        bool playerHasHorizontalSpeed = Mathf.Abs(horizontal) > Mathf.Epsilon;
        myAnimator.SetBool("isRunning", playerHasHorizontalSpeed);
    }

    // Disable player movement
    public void DisableMovement()
    {
        isMovementDisabled = true;
        myRigidbody.velocity = Vector2.zero; // Stop player movement
        myAnimator.SetBool("isRunning", false); // Stop running animation
        myAnimator.SetBool("isJumping", false); // Stop jumping animation
        myAnimator.SetBool("isRolling", false); // Stop rolling animation
    }

    // Enable player movement
    public void EnableMovement()
    {
        isMovementDisabled = false;
    }
}