import * as RAPIER from '@dimforge/rapier3d'
import { Quaternion, Vector3 } from 'three';
class Physics{
    constructor(engine){
        this.engine = engine
        this.started = false;
    }

    start(){
        this.started = true;
    }

    stop(){
        this.started = false;
    }
        
    init = async ()=>{
        let gravity = { x: 0.0, y: -9.81, z: 0.0 };
        let world = new RAPIER.World(gravity);

        // Create Ground.
        // let bodyDesc = RAPIER.RigidBodyDesc.fixed();
        // let body = world.createRigidBody(bodyDesc);
        // let colliderDesc = RAPIER.ColliderDesc.cuboid(100, 0, 100);
        // //bodyDesc.setTranslation(-50,0,-50);
        // world.createCollider(colliderDesc, body.handle);
        this.world = world;
        this.physicsObjects = [];
        this.eventQueue = new RAPIER.EventQueue(true);
        return this;
    }

    add = (mesh, rigidBodyType, autoAnimate = true, postPhysicsFn, colliderType, colliderSettings = {}) => {
        const rigidBodyDesc = RAPIER.RigidBodyDesc[rigidBodyType]()
        let position, quaternion, scale;
        // if (colliderSettings.root){
        //     // mesh.getWorldPosition(position = new Vector3());
        //     // mesh.getWorldQuaternion(quaternion = new Quaternion());
        //     position = colliderSettings.root.position;
        //     quaternion = colliderSettings.root.quaternion;
        // }else{
            position = mesh?.position?.clone();
            quaternion = mesh?.quaternion?.clone();
            scale = mesh?.scale?.clone();
        //}

        if (colliderSettings.root){
            mesh.updateWorldMatrix(true);
            let p = mesh;
            let m = p.matrix.clone();
            while (p != colliderSettings.root) {
                p = p.parent;
                m.premultiply(p.matrix)
            }
            m.decompose(position, quaternion, scale)
        }

        rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z)
        mesh.quaternion && rigidBodyDesc.setRotation(mesh.quaternion)

        // * Responsible for collision response
        const rigidBody = this.world.createRigidBody(rigidBodyDesc)
        rigidBody.userData = colliderSettings.userData || {};

        let colliderDesc

        switch (colliderType) {
            case 'cuboid':{
                const { width, height, depth } = colliderSettings
                colliderDesc = RAPIER.ColliderDesc.cuboid(width, height, depth)
            }
            break

            case 'ball':{
                const { radius } = colliderSettings
                colliderDesc = RAPIER.ColliderDesc.ball(radius)
            }
            break

            case 'capsule':{
                const { halfHeight, radius } = colliderSettings
                colliderDesc = RAPIER.ColliderDesc.capsule(halfHeight, radius)
            }
            break

            default:{
                const vertices = [];
                const vertex = new Vector3();
                const position = mesh.geometry.getAttribute( 'position' );
                for ( let i = 0; i < position.count; i ++ ) {
                    vertex.fromBufferAttribute( position, i ).multiply(scale);
                    vertices.push( vertex.x, vertex.y, vertex.z );
                }

                // if the buffer is non-indexed, generate an index buffer
                const indices = mesh.geometry.getIndex() === null
                    ? Uint32Array.from( Array( parseInt( vertices.length / 3 ) ).keys() )
                    : mesh.geometry.getIndex().array;

                colliderDesc = RAPIER.ColliderDesc.trimesh( vertices, indices );
            }
        }

        if (!colliderDesc) {
            console.error('Collider Mesh Error: convex mesh creation failed.')
        }

        if (colliderSettings.isSensor){
            colliderDesc.setSensor(true);
            colliderDesc.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS);
            colliderDesc.setActiveCollisionTypes(RAPIER.ActiveCollisionTypes.KINEMATIC_FIXED);
        }

        // * Responsible for collision detection
        const collider = this.world.createCollider(colliderDesc, rigidBody)
        if (colliderSettings.root){
            collider.setTranslationWrtParent(position)
            collider.setRotationWrtParent(quaternion)
            //console.log(colliderSettings.root.position, mesh.position)
        }
        const physicsObject = { mesh, collider, rigidBody, fn: postPhysicsFn, autoAnimate }
        this.physicsObjects.push(physicsObject)

        return physicsObject
    }

    apply(object){
        object.traverse(o=>{
            if (o.name.startsWith('land') || o.name.endsWith('-physics')){
                this.add(o, 'fixed', true, undefined, 'mesh', { root: object}) 
            }
        })
    }

    clear(){
        this.physicsObjects.forEach(po=>{
            po.characterController && this.world.removeCharacterController(po.characterController)
            this.world.removeCollider(po.collider);
            this.world.removeRigidBody(po.rigidBody);
        })
        this.physicsObjects = [];
    }

    step(){
        if (!this.started){
            return;
        }
        this.world.step(this.eventQueue)

        for (let po of this.physicsObjects) {
            if (po.autoAnimate) {
                po.mesh.position.copy(po.rigidBody.translation())
                po.mesh.quaternion.copy(po.rigidBody.rotation() )
            }
            po.fn?.()
        }

        this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
            /* Handle the collision event. */
            //console.log(this.world.getCollider(handle2), handle1, started)
            this.engine.dispatchEvent({
                type: 'collision', handle1, handle2, started 
            })
        });
    }
}

export { Physics }