using System.Collections.Generic;
using UnityEngine;

namespace ScrambleKit.Move.Platformer
{
    [RequireComponent(typeof(MoveCore))]
    public class PlatformerJumpMove : MonoBehaviour
    {

        public float LaunchSpeed = 10;
        public float BoostAccelPerSec = 50;
        public float BoostTimeAllowed = 0.25f;
        public float LetGoSpeedCap = 2f;
        public float MinLetGoTime = 0.1f;
        public float GhostJumpLeewayTime = 0.1f;

        private const string MOVEUP = "MoveUp";

        private MoveCore core;
        private bool jumping;
        private float lastJumpedTime;

        void FixedUpdate()
        {
            if (jumping)
                CheckLanding();         
            
            if(!jumping)
                CheckNewJump();

            if (jumping)
            {
                CheckBoost();
                CheckLetGo();
            }
        }

        private void CheckLanding()
        {
            float minLandingTime = 0.1f;  // prevents detecting ground as we lift off, and instantly terminating jump
            if (JumpTime() >= minLandingTime && core.IsTouchingGround())
                jumping = false;
        }

        private void CheckBoost()
        {
            if (StillInMinBoostTime() || (JumpTime() <= BoostTimeAllowed && core.input.CheckHeld(MOVEUP)))
                ApplyBoost();
        }

        private void CheckLetGo()
        {
            if (!core.input.CheckHeld(MOVEUP) && !StillInMinBoostTime())
                ApplyLetGo();
        }

        private bool StillInMinBoostTime()
        {
            return Time.fixedTime - lastJumpedTime <= MinLetGoTime;
        }

        private void CheckNewJump()
        {
            if (!core.RecentlyTouchedGround(GhostJumpLeewayTime))
                return;

            if (core.input.ConsumeTrigger(MOVEUP))
                StartJump();
        }

        private float JumpTime()
        {
            return Time.fixedTime - lastJumpedTime;
        }

        public void StartJump()
        {
            jumping = true;
            lastJumpedTime = Time.fixedTime;

            StopDownwardMotion();

            Vector2 accel = LaunchSpeed * Vector2.up;
            Vector2 force = core.rb2.mass * accel;
            core.rb2.AddForce(force, ForceMode2D.Impulse);
        }

        private void StopDownwardMotion()
        {
            if (core.rb2.velocity.y < 0)
            {
                Vector2 accel = -core.rb2.velocity.y * Vector2.up;
                Vector2 force = core.rb2.mass * accel;
                core.rb2.AddForce(force, ForceMode2D.Impulse);
            }
        }

        private void ApplyBoost()
        {
            Vector2 accel = BoostAccelPerSec * Vector2.up;
            Vector2 force = core.rb2.mass * accel;
            core.rb2.AddForce(force);
        }

        private void ApplyLetGo()
        {
            if (core.rb2.velocity.y > LetGoSpeedCap)
            {
                float overCap = core.rb2.velocity.y - LetGoSpeedCap;
                Vector2 accel = overCap * Vector2.down;
                Vector2 force = core.rb2.mass * accel;
                core.rb2.AddForce(force, ForceMode2D.Impulse);
            }
        }

        public void Start()
        {
            core = GetComponent<MoveCore>();
        }

        void AddBindingNames(HashSet<string> bindingNames)
        {
            bindingNames.Add(MOVEUP);
        }
    }
}