/**********************************************************************************************
 * 
 * Physac
 * Original -https://github.com/raysan5/raylib/blob/master/src/physac.h
 * 
**********************************************************************************************/

using System;
using System.Runtime.InteropServices;

namespace Raylib
{
    #region Raylib-cs Enums

    public enum PhysicsShapeType
    {
        PHYSICS_CIRCLE,
        PHYSICS_POLYGON
    }

    #endregion

    #region Raylib-cs Types

    // Mat2 type (used for polygon shape rotation matrix)
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct Mat2
    {
        public float m00;
        public float m01;
        public float m10;
        public float m11;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct PolygonData
    {
        public uint vertexCount;                           // Current used vertex and normals count

        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = Raylib.PHYSAC_MAX_VERTICES)]
        public Vector2[] positions;     // Polygon vertex positions vectors
	    
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = Raylib.PHYSAC_MAX_VERTICES)]
        public Vector2[] normals;       // Polygon vertex normals vectors
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct PhysicsShape
    {
        public PhysicsShapeType type;                      // Physics shape type (circle or polygon)
        public IntPtr body;                       // Shape physics body reference
        public float radius;                               // Circle shape radius (used for circle shapes)
        public Mat2 transform;                             // Vertices transform matrix 2x2
        public PolygonData vertexData;                     // Polygon shape vertices position and normals data (just used for polygon shapes)
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct PhysicsBodyData
    {
        public uint id;                                    // Reference unique identifier
        public bool enabled;                               // Enabled dynamics state (collisions are calculated anyway)
        public Vector2 position;                           // Physics body shape pivot
        public Vector2 velocity;                           // Current linear velocity applied to position
        public Vector2 force;                              // Current linear force (reset to 0 every step)
        public float angularVelocity;                      // Current angular velocity applied to orient
        public float torque;                               // Current angular force (reset to 0 every step)
        public float orient;                               // Rotation in radians
        public float inertia;                              // Moment of inertia
        public float inverseInertia;                       // Inverse value of inertia
        public float mass;                                 // Physics body mass
        public float inverseMass;                          // Inverse value of mass
        public float staticFriction;                       // Friction when the body has not movement (0 to 1)
        public float dynamicFriction;                      // Friction when the body has movement (0 to 1)
        public float restitution;                          // Restitution coefficient of the body (0 to 1)
        public bool useGravity;                            // Apply gravity force to dynamics
        public bool isGrounded;                            // Physics grounded on other body state
        public bool freezeOrient;                          // Physics rotation constraint
        public PhysicsShape shape;                         // Physics body shape information (type, radius, vertices, normals)
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct PhysicsManifoldData
    {
        public uint id;                            // Reference unique identifier
        public PhysicsBodyData bodyA;                          // Manifold first physics body reference
        public PhysicsBodyData bodyB;                          // Manifold second physics body reference
        public float penetration;                          // Depth of penetration from collision
        public Vector2 normal;                             // Normal direction vector from 'a' to 'b'
	    
        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 2)]
        public Vector2[] contacts;                        // Points of contact during collision
       
        public uint contactsCount;                 // Current collision number of contacts
        public float restitution;                          // Mixed restitution during collision
        public float dynamicFriction;                      // Mixed dynamic friction during collision
        public float staticFriction;                       // Mixed static friction during collision
    }

    #endregion

    public static partial class Raylib
    {
        #region Raylib-cs Variables

        // Used by DllImport to load the native library.
        // private const string nativeLibName = "raylib.dll";
        public const int PHYSAC_MAX_BODIES = 64;
        public const int PHYSAC_MAX_MANIFOLDS = 4096;
        public const int PHYSAC_MAX_VERTICES = 24;
        public const int PHYSAC_CIRCLE_VERTICES = 24;

        public const float PHYSAC_DESIRED_DELTATIME = 1.0f / 60.0f;
        public const float PHYSAC_MAX_TIMESTEP = 0.02f;
        public const int PHYSAC_COLLISION_ITERATIONS = 100;
        public const float PHYSAC_PENETRATION_ALLOWANCE = 0.05f;
        public const float PHYSAC_PENETRATION_CORRECTION = 0.4f;

        public const float PHYSAC_PI = 3.14159265358979323846f;
        public const float PHYSAC_DEG2RAD = (PHYSAC_PI / 180.0f);

        #endregion

        #region Raylib-cs Functions 

        // Initializes physics values, pointers and creates physics loop thread
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void InitPhysics();                                                                          

        // Returns true if physics thread is currently enabled
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern bool IsPhysicsEnabled();                                                                      

        // Sets physics global gravity force
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void SetPhysicsGravity(float x, float y);                                                         

        // Creates a new circle physics body with generic parameters
        [DllImport(nativeLibName, EntryPoint = "CreatePhysicsBodyCircle")]
        // [return: MarshalAs(UnmanagedType.LPStruct)]
        private static extern IntPtr CreatePhysicsBodyCircleImport(Vector2 pos, float radius, float density);
        public static PhysicsBodyData CreatePhysicsBodyCircle(Vector2 pos, float radius, float density)
        {
            var body = CreatePhysicsBodyCircleImport(pos, radius, density);
            var data = (PhysicsBodyData)Marshal.PtrToStructure(body, typeof(PhysicsBodyData));
            return data;
        }

        // Creates a new rectangle physics body with generic parameters
        [DllImport(nativeLibName, EntryPoint = "CreatePhysicsBodyRectangle")]
        private static extern IntPtr CreatePhysicsBodyRectangleImport(Vector2 pos, float width, float height, float density);
        public static PhysicsBodyData CreatePhysicsBodyRectangle(Vector2 pos, float width, float height, float density)
        {
            var body = CreatePhysicsBodyRectangleImport(pos, width, height, density);
            var data = (PhysicsBodyData)Marshal.PtrToStructure(body, typeof(PhysicsBodyData));
            return data;
        }

        // Creates a new polygon physics body with generic parameters
        [DllImport(nativeLibName, EntryPoint = "CreatePhysicsBodyPolygon")]
        private static extern IntPtr CreatePhysicsBodyPolygonImport(Vector2 pos, float radius, int sides, float density);
        public static PhysicsBodyData CreatePhysicsBodyPolygon(Vector2 pos, float radius, int sides, float density)
        {
            var body = CreatePhysicsBodyPolygonImport(pos, radius, sides, density);
            var data = (PhysicsBodyData)Marshal.PtrToStructure(body, typeof(PhysicsBodyData));
            return data;
        }

        // Adds a force to a physics body
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void PhysicsAddForce(PhysicsBodyData body, Vector2 force);                                            

        // Adds an angular force to a physics body
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void PhysicsAddTorque(PhysicsBodyData body, float amount);                                            
        
        // Shatters a polygon shape physics body to little physics bodies with explosion force
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void PhysicsShatter(PhysicsBodyData body, Vector2 position, float force);                             

        // Returns the current amount of created physics bodies
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern int GetPhysicsBodiesCount();                                                                  

        // Returns a physics body of the bodies pool at a specific index
        [DllImport(nativeLibName, EntryPoint = "GetPhysicsBody")]
        public static extern IntPtr GetPhysicsBodyImport(int index);     
        public static PhysicsBodyData GetPhysicsBody(int index)
        {
            var body = GetPhysicsBodyImport(index);
            var data = (PhysicsBodyData)Marshal.PtrToStructure(body, typeof(PhysicsBodyData));
            return data;
        }

        // Returns the physics body shape type (PHYSICS_CIRCLE or PHYSICS_POLYGON)
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern int GetPhysicsShapeType(int index);                                                             

        // Returns the amount of vertices of a physics body shape
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern int GetPhysicsShapeVerticesCount(int index);                                                      

        // Returns transformed position of a body shape (body position + vertex transformed position)
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern Vector2 GetPhysicsShapeVertex(PhysicsBodyData body, int vertex);                                      

        // Sets physics body shape transform based on radians parameter
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void SetPhysicsBodyRotation(PhysicsBodyData body, float radians);                                     

        // Unitializes and destroy a physics body
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void DestroyPhysicsBody(PhysicsBodyData body);                                                        

        // Destroys created physics bodies and manifolds and resets global values
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void ResetPhysics();                                                                        

        // Unitializes physics pointers and closes physics loop thread
        [DllImport(nativeLibName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void ClosePhysics();                                                                      

        #endregion

    }
}