package electrosphere.entity.types.collision;

import org.joml.Quaterniond;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.ode4j.ode.DBody;

import electrosphere.collision.CollisionBodyCreation;
import electrosphere.collision.CollisionEngine;
import electrosphere.collision.PhysicsUtils;
import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.attach.AttachUtils;
import electrosphere.server.datacell.Realm;

/**
 * Collision object utility functions
 */
public class CollisionObjUtils {

    public static Entity clientSpawnCollisionPlane(Vector3f scale, Vector3d position, Quaterniond rotation){
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();

        float mass = 1.0f;
        
        DBody planeObject = CollisionBodyCreation.createPlaneBody(Globals.clientSceneWrapper.getCollisionEngine(),new Vector3d(scale),Collidable.TYPE_STATIC_BIT);
        PhysicsUtils.setRigidBodyTransform(Globals.clientSceneWrapper.getCollisionEngine(), position, rotation, planeObject);
        Collidable collidable = new Collidable(rVal, Collidable.TYPE_STRUCTURE);
        Globals.clientSceneWrapper.getCollisionEngine().registerCollisionObject(planeObject, collidable);
        
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_TYPE_PLANE, true);
        rVal.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(position.x,position.y,position.z));
        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, rotation);
        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, scale);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, planeObject);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        rVal.putData(EntityDataStrings.PHYSICS_MASS, mass);
        rVal.putData(EntityDataStrings.DATA_STRING_DRAW, true);
        
        return rVal;
    }

    public static Entity serverSpawnCollisionPlane(Realm realm, Vector3f scale, Vector3d position, Quaterniond rotation){
        Entity rVal = EntityCreationUtils.createServerEntity(realm, new Vector3d(position));

        float mass = 1.0f;
        
        DBody planeObject = CollisionBodyCreation.createPlaneBody(realm.getCollisionEngine(),new Vector3d(scale),Collidable.TYPE_STATIC_BIT);
        PhysicsUtils.setRigidBodyTransform(realm.getCollisionEngine(), position, rotation, planeObject);
        Collidable collidable = new Collidable(rVal, Collidable.TYPE_STRUCTURE);
        realm.getCollisionEngine().registerCollisionObject(planeObject, collidable);
        
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_TYPE_PLANE, true);
        rVal.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(position.x,position.y,position.z));
        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, rotation);
        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, scale);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, planeObject);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        rVal.putData(EntityDataStrings.PHYSICS_MASS, mass);
        rVal.putData(EntityDataStrings.DATA_STRING_DRAW, true);
        
        return rVal;
    }
    
    
    public static Entity clientSpawnCollisionCube(Vector3f scale, Vector3d position, Quaterniond rotation){
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();

        float mass = 1.0f;
        
        DBody cubeObject = CollisionBodyCreation.createCubeBody(Globals.clientSceneWrapper.getCollisionEngine(),new Vector3d(scale),Collidable.TYPE_STATIC_BIT);
        PhysicsUtils.setRigidBodyTransform(Globals.clientSceneWrapper.getCollisionEngine(), position, rotation, cubeObject);
        Collidable collidable = new Collidable(rVal, Collidable.TYPE_STRUCTURE);
        Globals.clientSceneWrapper.getCollisionEngine().registerCollisionObject(cubeObject, collidable);
        
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_TYPE_CUBE, true);
        rVal.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(position.x,position.y,position.z));
        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, rotation);
        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, scale);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, cubeObject);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        rVal.putData(EntityDataStrings.PHYSICS_MASS, mass);
        rVal.putData(EntityDataStrings.DATA_STRING_DRAW, true);
        
        return rVal;
    }

    public static Entity serverSpawnCollisionCube(Realm realm, Vector3f scale, Vector3d position, Quaterniond rotation){
        Entity rVal = EntityCreationUtils.createServerEntity(realm, new Vector3d(position));

        float mass = 1.0f;
        
        DBody cubeObject = CollisionBodyCreation.createCubeBody(realm.getCollisionEngine(),new Vector3d(scale),Collidable.TYPE_STATIC_BIT);
        PhysicsUtils.setRigidBodyTransform(realm.getCollisionEngine(), position, rotation, cubeObject);
        Collidable collidable = new Collidable(rVal, Collidable.TYPE_STRUCTURE);
        realm.getCollisionEngine().registerCollisionObject(cubeObject, collidable);
        
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_TYPE_CUBE, true);
        rVal.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(position.x,position.y,position.z));
        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, rotation);
        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, scale);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, cubeObject);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        rVal.putData(EntityDataStrings.PHYSICS_MASS, mass);
        rVal.putData(EntityDataStrings.DATA_STRING_DRAW, true);
        
        return rVal;
    }
    
    public static Entity clientSpawnCollisionCylinder(Vector3f scale, Vector3d position, Quaterniond rotation){
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();

        float mass = 1.0f;
        DBody cubeObject = CollisionBodyCreation.createCylinderBody(Globals.clientSceneWrapper.getCollisionEngine(),scale.x,scale.y,Collidable.TYPE_STATIC_BIT);
        PhysicsUtils.setRigidBodyTransform(Globals.clientSceneWrapper.getCollisionEngine(), position, rotation, cubeObject);
        Collidable collidable = new Collidable(rVal, Collidable.TYPE_STRUCTURE);
        Globals.clientSceneWrapper.getCollisionEngine().registerCollisionObject(cubeObject, collidable);
        
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_TYPE_CYLINDER, true);
        rVal.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(position.x,position.y,position.z));
        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, rotation);
        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, scale);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, cubeObject);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        rVal.putData(EntityDataStrings.PHYSICS_MASS, mass);
        rVal.putData(EntityDataStrings.DATA_STRING_DRAW, true);
        
        return rVal;
    }

    public static Entity serverSpawnCollisionCylinder(Realm realm, Vector3f scale, Vector3d position, Quaterniond rotation){
        Entity rVal = EntityCreationUtils.createServerEntity(realm, new Vector3d(position));

        float mass = 1.0f;
        DBody cubeObject = CollisionBodyCreation.createCylinderBody(realm.getCollisionEngine(),scale.x,scale.y,Collidable.TYPE_STATIC_BIT);
        PhysicsUtils.setRigidBodyTransform(realm.getCollisionEngine(), position, rotation, cubeObject);
        Collidable collidable = new Collidable(rVal, Collidable.TYPE_STRUCTURE);
        realm.getCollisionEngine().registerCollisionObject(cubeObject, collidable);
        
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_TYPE_CYLINDER, true);
        rVal.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(position.x,position.y,position.z));
        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, rotation);
        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, scale);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, cubeObject);
        rVal.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        rVal.putData(EntityDataStrings.PHYSICS_MASS, mass);
        rVal.putData(EntityDataStrings.DATA_STRING_DRAW, true);
        
        return rVal;
    }
    
    
    public static Entity clientAttachCollisionPlane(Vector3f scale, Vector3d position, Quaterniond rotation, Entity parent){
        Entity rVal = clientSpawnCollisionPlane(scale, position, rotation);
        
        AttachUtils.attachEntityToEntity(parent, rVal);

        float mass = 1.0f;
        parent.putData(EntityDataStrings.PHYSICS_MASS, mass);
        
        return rVal;
    }

    public static Entity serverAttachCollisionPlane(Vector3f scale, Vector3d position, Quaterniond rotation, Entity parent){
        Realm parentRealm = Globals.realmManager.getEntityRealm(parent);
        Entity rVal = serverSpawnCollisionPlane(parentRealm, scale, position, rotation);
        
        AttachUtils.attachEntityToEntity(parent, rVal);

        float mass = 1.0f;
        parent.putData(EntityDataStrings.PHYSICS_MASS, mass);
        
        return rVal;
    }
    
    public static Entity clientAttachCollisionCube(Vector3f scale, Vector3d position, Quaterniond rotation, Entity parent){
        Entity rVal = clientSpawnCollisionCube(scale, position, rotation);
        
        AttachUtils.attachEntityToEntity(parent, rVal);

        float mass = 1.0f;
        parent.putData(EntityDataStrings.PHYSICS_MASS, mass);
        
        return rVal;
    }

    public static Entity serverAttachCollisionCube(Vector3f scale, Vector3d position, Quaterniond rotation, Entity parent){
        Realm parentRealm = Globals.realmManager.getEntityRealm(parent);
        Entity rVal = serverSpawnCollisionCube(parentRealm, scale, position, rotation);
        
        AttachUtils.attachEntityToEntity(parent, rVal);

        float mass = 1.0f;
        parent.putData(EntityDataStrings.PHYSICS_MASS, mass);
        
        return rVal;
    }
    
    public static Entity clientAttachCollisionCylinder(Vector3f scale, Vector3d position, Quaterniond rotation, Entity parent){
        Entity rVal = clientSpawnCollisionCylinder(scale, position, rotation);
        
        AttachUtils.attachEntityToEntity(parent, rVal);

        float mass = 1.0f;
        parent.putData(EntityDataStrings.PHYSICS_MASS, mass);
        
        return rVal;
    }

    public static Entity serverAttachCollisionCylinder(Vector3f scale, Vector3d position, Quaterniond rotation, Entity parent){
        Realm parentRealm = Globals.realmManager.getEntityRealm(parent);
        Entity rVal = serverSpawnCollisionCylinder(parentRealm, scale, position, rotation);
        
        AttachUtils.attachEntityToEntity(parent, rVal);

        float mass = 1.0f;
        parent.putData(EntityDataStrings.PHYSICS_MASS, mass);
        
        return rVal;
    }

    /**
     * Attach a collision object to a provided entity
     * @param entity The entity to attach a collision object to
     * @param collisionObject The jBulletF collision object to attach to the entity
     * @param mass The mass of the collidable
     * @param collidableType The type of collidable we are attaching. For instance, "Terrain", "Creature", "Item", etc. Refer to Collidable class for options.
     */
    public static void clientAttachCollisionObjectToEntity(Entity entity, DBody collisionObject, float mass, String collidableType){
        Vector3d position = EntityUtils.getPosition(entity);
        Vector3f scale = EntityUtils.getScale(entity);
        Collidable collidable = new Collidable(entity, collidableType);
        Globals.clientSceneWrapper.getCollisionEngine().registerCollisionObject(collisionObject, collidable);

        entity.putData(EntityDataStrings.PHYSICS_COLLISION_BODY, collisionObject);

        //update world transform of collision object
        clientPositionCharacter(entity,position);
        
        entity.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, collisionObject);
        entity.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        entity.putData(EntityDataStrings.PHYSICS_MASS, mass);
    }

    /**
     * Attach a collision object to a provided entity
     * @param entity The entity to attach a collision object to
     * @param collisionObject The jBulletF collision object to attach to the entity
     * @param mass The mass of the collidable
     * @param collidableType The type of collidable we are attaching. For instance, "Terrain", "Creature", "Item", etc. Refer to Collidable class for options.
     */
    public static void serverAttachCollisionObjectToEntity(Entity entity, DBody collisionObject, float mass, String collidableType){
        Vector3d position = EntityUtils.getPosition(entity);
        Vector3f scale = EntityUtils.getScale(entity);
        Collidable collidable = new Collidable(entity, collidableType);
        Realm realm = Globals.realmManager.getEntityRealm(entity);
        realm.getCollisionEngine().registerCollisionObject(collisionObject, collidable);

        entity.putData(EntityDataStrings.PHYSICS_COLLISION_BODY, collisionObject);

        //update world transform of collision object
        serverPositionCharacter(entity,position);
        
        entity.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, collisionObject);
        entity.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
        entity.putData(EntityDataStrings.PHYSICS_MASS, mass);
    }
    
    public static DBody getCollisionBody(Entity e){
        return (DBody)e.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
    }
    
    public static void serverPositionCharacter(Entity e, Vector3d position){
        EntityUtils.getPosition(e).set(position);
        Quaterniond rotation = EntityUtils.getRotation(e);
        DBody body = getCollisionBody(e);
        CollisionEngine collisionEngine = Globals.realmManager.getEntityRealm(e).getCollisionEngine();
        if(body != null){
            PhysicsUtils.setRigidBodyTransform(collisionEngine, position, rotation, body);
        }
    }

    public static void clientPositionCharacter(Entity e, Vector3d position){
        EntityUtils.getPosition(e).set(position);
        Quaterniond rotation = EntityUtils.getRotation(e);
        DBody body = getCollisionBody(e);
        if(body != null){
            PhysicsUtils.setRigidBodyTransform(Globals.clientSceneWrapper.getCollisionEngine(), position, rotation, body);
        }
    }
    
    public static Collidable getCollidable(Entity e){
        return (Collidable)e.getData(EntityDataStrings.PHYSICS_COLLIDABLE);
    }

    public static float getMass(Entity e){
        return (float)e.getData(EntityDataStrings.PHYSICS_MASS);
    }

    
}
