using ExtraTools;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PhysicsObject : MonoBehaviour
{
    [SerializeField]
    private float minValY = 0.5f;
    [SerializeField]
    private float gravity = 20;
    public float terminalVelocity = -25;
    [SerializeField]
    private ContactFilter2D filter;
    public Vector2 targetVelocity;
    public bool useGravity = true;

    [SerializeField]
    private Rigidbody2D rigid;

    private Vector2 _velocity;
    public Vector2 Velocity => _velocity;
    private float minDist = 0.01f;
    private RaycastHit2D[] hits = new RaycastHit2D[8];
    private Vector2 groundNormal = Vector2.up;

    [SerializeField]
    private bool debug = false;

    private void Move(Vector2 move, bool yMovement)
    {
        float dist = move.magnitude;
        if (dist <= minDist) return;

        //hits = rigid.Cast(move, dist, QueryTriggerInteraction.Ignore);
        int count = rigid.Cast(move, filter, hits, dist);
        for (int i = 0; i < count; i++)
        {
            Vector2 norm = hits[i].normal;
            if (debug)
                Debug.DrawRay(hits[i].point, norm, Color.red, 3);

            if (LevelController.Instance.Gravity.y != 0 && norm.y > minValY && yMovement)
            {
                groundNormal = norm;
                norm.x = 0;
            }
            else if (LevelController.Instance.Gravity.x != 0 && norm.x > minValY && yMovement)
            {
                groundNormal = norm;
                norm.y = 0;
            }

            float projection = Vector3.Dot(_velocity, norm);
            if (projection < 0)
                _velocity -= projection * norm;

            float modifiedDist = hits[i].distance - minDist;
            if (modifiedDist < dist)
            {
                dist = modifiedDist;
                if (debug)
                    Debug.DrawRay(hits[i].point, -hits[i].normal, Color.green, 3);
            }
            //dist = modifiedDist < dist ? modifiedDist : dist;
        }

        rigid.position += move.normalized * dist;
    }

    public void Teleport(Vector2 pos)
    {
        if (!isActiveAndEnabled)
            transform.position = pos;
        else
            rigid.position = pos;
    }

    #region Mono
    
    private void OnDisable()
    {
        rigid.velocity = Vector2.zero;
        targetVelocity = Vector2.zero;
    }
    
    private void Awake()
    {
        if (!rigid)
        {
            Debug.LogWarning(string.Format("Rigidbody component of {0} is not set! Searching for one...", name));
            if (!transform.SetComponent(out rigid, true))
            {
                enabled = false;
                return;
            }
        }
    }

    private void FixedUpdate()
    {
        if (LevelController.Instance.Gravity.y != 0 && (targetVelocity.y != 0 || !useGravity))
            _velocity.y = targetVelocity.y;
        else if (LevelController.Instance.Gravity.x != 0 && (targetVelocity.x != 0 || !useGravity))
            _velocity.x = targetVelocity.x;


        if (useGravity)
            _velocity += gravity * LevelController.Instance.Gravity * Time.fixedDeltaTime;

        if (LevelController.Instance.Gravity.y != 0 && Mathf.Abs(_velocity.y) > terminalVelocity)
            _velocity.y = terminalVelocity * Mathf.Sign(_velocity.y);
        else if (LevelController.Instance.Gravity.x != 0 && Mathf.Abs(_velocity.x) > terminalVelocity)
            _velocity.x = terminalVelocity * Mathf.Sign(_velocity.x);

        Vector3 deltaPos = _velocity * Time.fixedDeltaTime;
        if (LevelController.Instance.Gravity.y != 0)
        {
            _velocity.x = targetVelocity.x;
            Vector3 moveAlongGround = new Vector3(groundNormal.y + Mathf.Abs(groundNormal.x), 0);
            Vector3 move = moveAlongGround * deltaPos.x;
            Move(move, false);
            move = Mathf.Sign(LevelController.Instance.Gravity.y) * LevelController.Instance.Gravity * deltaPos.y;

            Move(move, true);
        }
        else if (LevelController.Instance.Gravity.x != 0)
        {
            _velocity.y = targetVelocity.y;
            Vector3 moveAlongGround = new Vector3(0, groundNormal.x + Mathf.Abs(groundNormal.y));
            Vector3 move = moveAlongGround * deltaPos.y;
            Move(move, false);
            move = Mathf.Sign(LevelController.Instance.Gravity.x) * LevelController.Instance.Gravity * deltaPos.x;

            Move(move, true);
        }



        targetVelocity = Vector3.zero;

        Events.Instance.playerPhysicsUpdated.Invoke();
    }

    private void Reset()
    {
        transform.EnsureComponent(out rigid);
    }
    #endregion
}
