using System;
using UnityEngine;

public class playerC0ntroller : MonoBehaviour
{

    private Rigidbody2D body;
    private BoxCollider2D hbox;
    private String SelectedPlayer;

    bool isJumping;

    Vector2 awayFromWall;
    bool gravityLockActive;
    bool isFacingRight = true;
    float playerHealth;

    //Input Variables
    private KeyCode hop, up, down, left, right, dash, forward;
    private String currentDirection;
    private Vector2 directionHeld;
    private bool inputLock;

    //Wall Friction 
    float wallStallTimer, wallFricTimer, wallFricStrength = 4;

    //Timers
    float coyoteTime, wallCoyoteTime, jumpBufferTimer, inputLockTimer, dashCooldown, dashActiveTimer, turnaroundWindow;

    //Upgrade Stats
    public float vitalityLevel, healthModifier, regenerationRate, armorModifier;
    public float powerLevel, damageModifier, unloadSpeedModifier;
    public float agilityLevel, movementSpeedModifier, jumpPowerModifier, jumpChargeCap;

    //Method Variables
    float jumpPower = 30f;
    float dashCharges, jumpCharges;

    private void Start()
    {
        body = GetComponent<Rigidbody2D>();
        hbox = GetComponent<BoxCollider2D>();

        #region Character Archetype Controls
        switch (SelectedPlayer)
        {
            case "Cade":
            case "Sloane":
            default:
                hop = KeyCode.W;
                up = KeyCode.W;
                down = KeyCode.S;
                left = KeyCode.A;
                right = forward = KeyCode.D;
                dash = KeyCode.LeftShift;
                break;

            case "Leo":
            case "Gamma":
                hop = KeyCode.Space;
                up = KeyCode.UpArrow;
                down = KeyCode.DownArrow;
                left = KeyCode.LeftArrow;
                right = forward = KeyCode.RightArrow;
                dash = KeyCode.LeftShift;
                break;
        }
        #endregion

    }

    private void Update()
    {

        ManageTimers();

        playerHealth = GameObject.Find("GameData").GetComponent<GameData>().CurrentHealth;

        if (playerHealth > 0)
        {

        }



    }

    
//Methods Used By Other Methods
    private bool isGrounded()
    {
        Vector2 groundCastOrigin = new Vector2(hbox.bounds.center.x, hbox.bounds.min.y);
        Vector2 groundCastSize = new Vector2(hbox.bounds.size.x, 0.01f);
        int checkLayers = (1 << 6) | (1 << 9); //Makes A Bitmask That Will Check For Collisions On Both Solid and Semiolid Ground Layers, Respectively

        RaycastHit2D groundHit = Physics2D.BoxCast(groundCastOrigin, groundCastSize, 0f, Vector2.down, .05f, checkLayers); 
        RaycastHit2D doubleCheck = Physics2D.BoxCast(groundCastOrigin, groundCastSize, 0f, Vector2.up, .05f, 1 << 9); //Check if Player is Inside a Semisolid Layer
        
        if (doubleCheck.collider != null) return false;
        return groundHit.collider != null;
    }
    public bool isWallSlide()
    {
        if (directionHeld.x == -1 * directionAwayFromWall().x && body.linearVelocityY <= 0 && !isGrounded())
        {

            if (wallFricTimer >= 0 && body.linearVelocityY < 0)
            {
                body.linearVelocityY *= Mathf.Exp(-wallFricStrength * Time.deltaTime); //Friction On Wall Before Coming To A Stop
                if (Math.Abs(body.linearVelocityY) < .25f) body.linearVelocityY = 0;

                wallStallTimer = .15f;
            }

            wallCoyoteTime = .1f;
            return true;
        }
        else
        {
            wallFricTimer = (body.linearVelocityY - 0.1f) / -20; //As Velocity Approaches -20 Timer Approaches 1 Sec, .1 Sec Buffer Stops It From Being 0
            return false;
        }
    }  
    private Vector2 directionAwayFromWall()
    {
        Vector2 wallCastOrigin = new Vector2(hbox.bounds.center.x, hbox.bounds.center.y);
        Vector2 wallCastSize = new Vector2(hbox.bounds.size.x + .01f, hbox.bounds.size.y);

        Collider2D wallHit = Physics2D.OverlapBox(wallCastOrigin, wallCastSize, 0f, 1 << 6); //Check for collisions on layer 6 (solidGround)

        return (wallHit != null) ? wallHit.Distance(hbox).normal : default;
    }
    public void Flip()
    {
        transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y);
        forward = (forward == left) ? right : left;
        //        ^if this is true^  ^then^  ^else^
    }
    public void CountDown(ref float timer)
    //                   ^ref^ makes this function modify the referenced timer instead of assigning the timer to a placeholder 
    {
        if (timer > 0) timer -= Time.deltaTime;
    }


//Methods Run Every Frame
    public void ManageInputs()
    {
        inputLock = (inputLockTimer > 0 || dashActiveTimer > 0) ? false : true;

        if (!inputLock)
        {
            directionHeld.x = (Input.GetKey(right) ? 1 : 0) - (Input.GetKey(left) ? 1 : 0);
            directionHeld.y = (Input.GetKey(up) ? 1 : 0) - (Input.GetKey(down) ? 1 : 0);
        }

        if (directionHeld.y == 1) currentDirection = "up";
        else if (directionHeld.y == -1) currentDirection = "down";
        else if (directionHeld.x == -1 && left != forward || directionHeld.x == 1 && right != forward) currentDirection = "backward";
        else if (directionHeld.x == -1 && left == forward || directionHeld.x == 1 && right == forward) currentDirection = "forward";
        if (directionHeld == default) currentDirection = "neutral"; //"default" acts as a vector with values (0, 0)

        if (Input.GetKeyDown(hop)) jumpBufferTimer = .2f;
    }


    public void ManagePlayerOrientation()
    {

        if (directionHeld.x != 0 && (directionHeld.x > 0) != isFacingRight && isGrounded()) Flip();


    }


    public void ManageTimers()
    {
        CountDown(ref coyoteTime);
        CountDown(ref wallCoyoteTime);
        CountDown(ref jumpBufferTimer);
        CountDown(ref inputLockTimer);
       
        if (isGrounded() || dashCooldown > .05f) CountDown(ref dashCooldown);
        
        CountDown(ref dashActiveTimer);
        CountDown(ref turnaroundWindow);
        CountDown(ref wallStallTimer);
        CountDown(ref wallFricTimer);
    }

    public void ManageSpeedCaps()
    {
        bool speedClampingActive = (dashActiveTimer > 0) ? false : true;

        if (speedClampingActive)
        {
            body.linearVelocityX = Mathf.Clamp(body.linearVelocity.x, -10 - movementSpeedModifier, 10 + movementSpeedModifier); //Clamp X Velocity

            float terminalVelocity;
            if (directionHeld.y == -1) terminalVelocity = -30;
            else if (isWallSlide()) terminalVelocity = -12;
            else terminalVelocity = -20;

            body.linearVelocityY = Mathf.Clamp(body.linearVelocity.y, terminalVelocity, 10 + jumpPowerModifier); // Clamp Y Velocity
        }


    }

    public void ManageGravity()
    {
        body.gravityScale = dashActiveTimer > 0 || wallStallTimer > 0 && isWallSlide() ? 0 : body.gravityScale;
        gravityLockActive = dashActiveTimer > 0 || wallStallTimer > 0 && isWallSlide();

        if (!gravityLockActive)
        {
            if (directionHeld.y == -1) body.gravityScale = 8;
            else if (isWallSlide()) body.gravityScale = 2;
            else body.gravityScale = 6;
        }

    }



    public void ManageGroundResetting()
    {
        if (isGrounded())
        {
            jumpCharges = jumpChargeCap;
            dashCharges = 1;
            coyoteTime = .1f;

        }

    }

  

    public void InterpretInputs()
    {
        #region Walking & Friction
        float turnAround = isGrounded() ? 8 : 6;
        if (turnaroundWindow > 0) turnAround *= 5;
        float moveSpeed = 10;

        body.linearVelocityX += moveSpeed * directionHeld.x * turnAround * Time.deltaTime;


        if (directionHeld.x == 0)
        {
            float fric = (Math.Abs(body.linearVelocityX) > 3f) ? .97f : .94f;
            body.linearVelocityX = body.linearVelocityX * fric;

            if (Math.Abs(body.linearVelocity.x) < .4f) body.linearVelocityX = 0;
        }
        #endregion

        
        
 

        #region Jumping
        if (jumpBufferTimer > 0) {

            isJumping = true;

        //Normal Jump
            if (coyoteTime > 0) body.linearVelocityY = jumpPower;

        //Wall Jump
            else if (wallCoyoteTime > 0)
            {
                body.linearVelocity = new Vector2(moveSpeed * awayFromWall.x, 20f);

                directionHeld.x = (awayFromWall.x > 0) ? 1 : -1;
                inputLockTimer = 0.15f;
            }

        //Air Jump
            else if (jumpCharges > 0)
            {
                body.linearVelocityY = jumpPower * .8f;
                turnaroundWindow = .1f;
                jumpCharges--;
            }

        //Wrapping Up Logic
            else isJumping = false;
            if (isJumping) jumpBufferTimer = 0;
        }

        //Jump Capping
        if (!Input.GetKey(hop) && body.linearVelocityY > 0 && isJumping) body.linearVelocityY = body.linearVelocityY * .9f;
        
        //isJumping Update
        if (isJumping && body.linearVelocityY <= 0) isJumping = false;

        #endregion

        #region Dashing
        if (Input.GetKeyDown(dash) && dashCooldown <= 0 && dashCharges > 0)
        {
            dashCooldown = 1;
            dashActiveTimer = .15f;

            float dashPower = (directionHeld.x * directionHeld.y != 0) ? 60 : 0.707f * 60;

            if (directionHeld == default) directionHeld.x = (forward == right) ? 1 : -1;

            body.linearVelocity = new Vector2(directionHeld.x * dashPower, directionHeld.y * 2 * dashPower);
        }

        #endregion
    }



}
