﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class MyCharacter : MonoBehaviour
{
    /// <summary>
    /// Movement speed
    /// </summary>
    [Header("Movement Controls")]
    public float speed = 12;
    /// <summary>
    /// Jump upward speed
    /// </summary>
    public float jumpSpeed = 5;
    /// <summary>
    /// Variable height jump tuning
    /// </summary>
    public float jumpDuration = 0.2f;
    /// <summary>
    /// Make it feel more like Mario
    /// </summary>
    public float extraGravity = 100;
    public PhysicMaterial movingMaterial;
    public PhysicMaterial stationaryMaterial;
    public Collider movementCollider;

    /// <summary>
    /// Layers that are considered ground
    /// </summary>
    [Header("Ground Checking")]
    public LayerMask groundMask;
    /// <summary>
    /// Offset groundcheck towards the feet
    /// </summary>
    public Vector3 groundOffset = Vector3.down;
    /// <summary>
    /// A bit of radius to the groundcheck
    /// </summary>
    public float groundCheckRadius = 0.1f;

    /// <summary>
    /// cache the local rigidbody
    /// </summary>
    Rigidbody rb;

    /// <summary>
    /// Keep track of the platform we're on
    /// </summary>
    Rigidbody currentPlatform;

    /// <summary>
    /// Uh...just in case!
    /// </summary>
    Vector3 startingPosition;

    /// <summary>
    /// Character just jumped, ignore platform velocity for one FixedUpdate in case Physics tolerances didn't register
    /// the OnCollisionExit event yet.  This is very common.
    /// </summary>
    bool jumped = false;

    /// <summary>
    /// Keep track of jumping state for variable height jumps
    /// </summary>
    bool jumping = false;
    float jumpStartTime;

    /// <summary>
    /// Cached jump button value because FixedUpdate and Update aren't in the same time frame
    /// </summary>
    bool shouldJump = false;

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        startingPosition = transform.position;

        //Enforce this because everyone who thinks 50fps is enough for platforming Physics is wrong.
        Time.fixedDeltaTime = 0.01f; //set 100fps physics frame rate
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (currentPlatform == null)
        {
            //Make sure it has a rigidbody
            if (collision.collider.attachedRigidbody != null)
            {
                //Kind of optional, but make sure the thing we're tracking is actually a Moving Platform
                //There are a lot of ways to do this...  IE: GameObject Tag's and what not.
                if (collision.collider.attachedRigidbody.GetComponent<MovingPlatform>() != null)
                {
                    //make sure we're on top of the platform
                    if (collision.contacts[0].normal.y > 0.8f)
                    {
                        currentPlatform = collision.collider.attachedRigidbody;
                    }

                }
            }
        }
    }

    private void OnCollisionExit(Collision collision)
    {
        // null the current platform if it matches the thing we just stopped touching
        if (collision.collider.attachedRigidbody == currentPlatform)
        {
            currentPlatform = null;
        }
    }

    private void Update()
    {
        //cache the jump input during Update because old input manager
        if (Input.GetButtonDown("Jump"))
            shouldJump = true;
        if (Input.GetButtonUp("Jump"))
        {
            shouldJump = false;
            jumping = false;
        }

    }

    private void FixedUpdate()
    {
        //Kill floor :)
        if(rb.position.y < -10)
        {
            rb.velocity = Vector3.zero;
            rb.position = startingPosition;
        }

        Vector3 baseVelocity = Vector3.zero;
        if (currentPlatform != null && !jumped)
        {
            baseVelocity = currentPlatform.velocity;
        }
            
        else
            baseVelocity.y = rb.velocity.y;


        Vector3 characterVelocity = Vector3.zero;

        //basic character input direction
        characterVelocity.x = Input.GetAxis("Horizontal");
        characterVelocity.z = Input.GetAxis("Vertical");
        if (characterVelocity.magnitude > 1)
            characterVelocity.Normalize();

        characterVelocity *= speed;

        jumped = false;
        bool grounded = CheckGround();
        if (grounded)
        {
            if (shouldJump)
            {
                characterVelocity.y = jumpSpeed;
                jumped = true;
                jumping = true;
                shouldJump = false;
            }

            //set this until not grounded so we have a consistent jump height
            if (jumping)
            {
                jumpStartTime = Time.time;
            }
        }
        else
        {
            if (jumping)
            {
                //variable height maximum input duration
                if((Time.time - jumpStartTime) < jumpDuration)
                {
                    //up we go!
                    characterVelocity.y = jumpSpeed;
                }
                else
                {
                    //dont maintain jump velocity anymore
                    jumping = false;
                }
            }
        }

        //set the physics material appropriately to prevent wall-clinging
        if (!grounded || characterVelocity.x != 0)
            movementCollider.sharedMaterial = movingMaterial;
        else
            movementCollider.sharedMaterial = stationaryMaterial;

        //Combine aggregate velocity here
        Vector3 velocity = baseVelocity + characterVelocity;

        //TODO: add any other velocity modifiers here IE: Wind, bouyancy, so on
        
        //Apply velocity to rigidbody
        rb.velocity = velocity;

        //Apply additional fall force, ignoring mass (like real gravity!)
        rb.AddForce(Physics.gravity.normalized * extraGravity, ForceMode.Acceleration);
    }

    /// <summary>
    /// Perform rudimentary ground check
    /// </summary>
    /// <returns></returns>
    bool CheckGround()
    {
        return Physics.CheckSphere(transform.TransformPoint(groundOffset), groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore);
    }

    /// <summary>
    /// Gizmos!
    /// </summary>
    private void OnDrawGizmosSelected()
    {
        //Show the state of the groundcheck and physics material
        if(movementCollider != null)
        {
            Gizmos.color = movementCollider.sharedMaterial == movingMaterial ? Color.green : Color.red;
            if (CheckGround())
            {
                Gizmos.DrawWireCube(transform.TransformPoint(groundOffset), Vector3.one * groundCheckRadius);
            }
            else
            {
                Gizmos.DrawWireSphere(transform.TransformPoint(groundOffset), groundCheckRadius);
            }
        }
        
        //Show the platform velocity imparted on the controller
        if (currentPlatform != null)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawLine(transform.position, transform.position + currentPlatform.velocity);
        }

    }
}
