/*
 * @license Private Source License v1.0.0
 * @copyright 2023 Reinhardt Weyers
 *
 * @author Reinhardt Weyers
 *
 * @version 0.1.0
 * 
 * Created On: 04/12/25
 * Last edited: 04/12/25
 */

using UnityEngine;

namespace RetroDev.Newton3D
{
    public static class Newton3D
    {
        /// <summary>
        /// Apply a force to a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="force">The force to be applied.</param>
        public static void ApplyForce(Rigidbody rigidbody, Vector3 force)
        {
            rigidbody.AddForce(force);
        }

        /// <summary>
        /// Set the velocity of a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="velocity">The new velocity.</param>
        public static void SetVelocity(Rigidbody rigidbody, Vector3 velocity)
        {
            rigidbody.linearVelocity = velocity;
        }

        /// <summary>
        /// Simulate a jump by applying an upward force to a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="jumpForce">The force applied for the jump.</param>
        public static void Jump(Rigidbody rigidbody, float jumpForce)
        {
            rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }

        /// <summary>
        /// Set constraints on a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="freezePositionX">Freeze or unfreeze the X-axis position.</param>
        /// <param name="freezePositionY">Freeze or unfreeze the Y-axis position.</param>
        /// <param name="freezePositionZ">Freeze or unfreeze the Z-axis position.</param>
        /// <param name="freezeRotationX">Freeze or unfreeze rotation around the X-axis.</param>
        /// <param name="freezeRotationY">Freeze or unfreeze rotation around the Y-axis.</param>
        /// <param name="freezeRotationZ">Freeze or unfreeze rotation around the Z-axis.</param>
        public static void SetConstraints(Rigidbody rigidbody, bool freezePositionX, bool freezePositionY, bool freezePositionZ,
            bool freezeRotationX, bool freezeRotationY, bool freezeRotationZ)
        {
            rigidbody.constraints = (freezePositionX ? RigidbodyConstraints.FreezePositionX : RigidbodyConstraints.None) |
                                    (freezePositionY ? RigidbodyConstraints.FreezePositionY : RigidbodyConstraints.None) |
                                    (freezePositionZ ? RigidbodyConstraints.FreezePositionZ : RigidbodyConstraints.None) |
                                    (freezeRotationX ? RigidbodyConstraints.FreezeRotationX : RigidbodyConstraints.None) |
                                    (freezeRotationY ? RigidbodyConstraints.FreezeRotationY : RigidbodyConstraints.None) |
                                    (freezeRotationZ ? RigidbodyConstraints.FreezeRotationZ : RigidbodyConstraints.None);
        }


        /// <summary>
        /// Apply torque to a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="torque">The torque to be applied.</param>
        public static void ApplyTorque(Rigidbody rigidbody, Vector3 torque)
        {
            rigidbody.AddTorque(torque);
        }

        /// <summary>
        /// Apply linear damping to a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="linearDamping">The linear damping factor.</param>
        public static void ApplyLinearDamping(Rigidbody rigidbody, float linearDamping)
        {
            rigidbody.linearVelocity *= 1f - linearDamping * Time.fixedDeltaTime;
        }

        /// <summary>
        /// Apply angular damping to a 3D rigidbody.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="angularDamping">The angular damping factor.</param>
        public static void ApplyAngularDamping(Rigidbody rigidbody, float angularDamping)
        {
            rigidbody.angularVelocity *= 1f - angularDamping * Time.fixedDeltaTime;
        }

        /// <summary>
        /// Apply a force to a 3D rigidbody at a specific position.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="force">The force to be applied.</param>
        /// <param name="position">The position at which to apply the force.</param>
        public static void ApplyForceAtPosition(Rigidbody rigidbody, Vector3 force, Vector3 position)
        {
            rigidbody.AddForceAtPosition(force, position);
        }

        /// <summary>
        /// Apply torque to a 3D rigidbody around a specific point.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="torque">The torque to be applied.</param>
        /// <param name="position">The point around which to apply the torque.</param>
        public static void ApplyTorqueAtPosition(Rigidbody rigidbody, Vector3 torque, Vector3 position)
        {
            Vector3 torqueDirection = position - rigidbody.worldCenterOfMass;
            rigidbody.AddForceAtPosition(Vector3.Cross(torque, torqueDirection), position, ForceMode.Force);
        }

        /// <summary>
        /// Apply a force to simulate linear drag.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="linearDrag">The linear drag to be applied.</param>
        public static void ApplyLinearDrag(Rigidbody rigidbody, float linearDrag)
        {
            rigidbody.linearVelocity *= 1f - linearDrag * Time.fixedDeltaTime;
        }

        /// <summary>
        /// Apply angular drag to simulate rotational drag.
        /// </summary>
        /// <param name="rigidbody">The 3D rigidbody.</param>
        /// <param name="angularDrag">The angular drag to be applied.</param>
        public static void ApplyAngularDrag(Rigidbody rigidbody, float angularDrag)
        {
            rigidbody.angularVelocity *= 1f - angularDrag * Time.fixedDeltaTime;
        }
    }
}
