package electrosphere.client.interact;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import org.joml.Matrix4d;
import org.joml.Quaterniond;
import org.joml.Vector3d;
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.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.game.data.collidable.CollidableTemplate;

/**
 * Manages the interaction state 
 */
public class ClientInteractionEngine {

    /**
     * The lock used for thread-safe-ing the engine
     */
    private static ReentrantLock lock = new ReentrantLock();

    /**
     * The list of entities that are currently registered for interaction
     */
    private static List<Entity> interactables = new LinkedList<Entity>();
    
    /**
     * Attaches a collidable template to a given entity
     * @param rVal The entity
     * @param physicsTemplate The collidable template
     */
    public static void attachCollidableTemplate(Entity rVal, CollidableTemplate physicsTemplate){
        DBody rigidBody = null;
        Collidable collidable;
        long categoryBit = Collidable.TYPE_OBJECT_BIT;
        lock.lock();
        interactables.add(rVal);
        switch(physicsTemplate.getType()){
            case "CYLINDER": {

                //
                //create dbody
                rigidBody = CollisionBodyCreation.createCylinderBody(
                    Globals.clientSceneWrapper.getInteractionEngine(),
                    physicsTemplate.getDimension1(),
                    physicsTemplate.getDimension2(),
                    categoryBit
                );

                //
                //set offset from center of entity position
                CollisionBodyCreation.setOffsetPosition(
                    Globals.clientSceneWrapper.getInteractionEngine(),
                    rigidBody,
                    new Vector3d(physicsTemplate.getOffsetX(), physicsTemplate.getOffsetY(), physicsTemplate.getOffsetZ())
                );

                //
                //create collidable and link to structures
                collidable = new Collidable(rVal, Collidable.TYPE_OBJECT, true);

                //
                //store values
                Matrix4d offsetTransform = new Matrix4d().translationRotate(
                    physicsTemplate.getOffsetX(), physicsTemplate.getOffsetY(), physicsTemplate.getOffsetZ(), //translate
                    physicsTemplate.getRotX(), physicsTemplate.getRotY(), physicsTemplate.getRotZ(), physicsTemplate.getRotW() //rotate
                );
                rVal.putData(EntityDataStrings.INTERACTION_OFFSET_TRANSFORM, offsetTransform);
                rVal.putData(EntityDataStrings.INTERACTION_TEMPLATE, physicsTemplate);
                rVal.putData(EntityDataStrings.INTERACTION_COLLIDABLE, collidable);
                rVal.putData(EntityDataStrings.INTERACTION_BODY, rigidBody);

                Globals.clientSceneWrapper.getInteractionEngine().registerCollisionObject(rigidBody, collidable);
            } break;
            case "CUBE": {
                //
                //create dbody
                rigidBody = CollisionBodyCreation.createCubeBody(
                    Globals.clientSceneWrapper.getInteractionEngine(),
                    new Vector3d(physicsTemplate.getDimension1(),physicsTemplate.getDimension2(),physicsTemplate.getDimension3()),
                    categoryBit
                );

                //
                //set offset from center of entity position
                CollisionBodyCreation.setOffsetPosition(
                    Globals.clientSceneWrapper.getInteractionEngine(),
                    rigidBody,
                    new Vector3d(physicsTemplate.getOffsetX(), physicsTemplate.getOffsetY(), physicsTemplate.getOffsetZ())
                );

                //
                //create collidable and link to structures
                collidable = new Collidable(rVal, Collidable.TYPE_OBJECT, true);

                //
                //store values
                Matrix4d offsetTransform = new Matrix4d().translationRotateScale(
                    physicsTemplate.getOffsetX(), physicsTemplate.getOffsetY(), physicsTemplate.getOffsetZ(), //translate
                    physicsTemplate.getRotX(), physicsTemplate.getRotY(), physicsTemplate.getRotZ(), physicsTemplate.getRotW(), //rotate
                    1, 1, 1 //scale
                );
                rVal.putData(EntityDataStrings.INTERACTION_OFFSET_TRANSFORM, offsetTransform);
                rVal.putData(EntityDataStrings.INTERACTION_TEMPLATE, physicsTemplate);
                rVal.putData(EntityDataStrings.INTERACTION_COLLIDABLE, collidable);
                rVal.putData(EntityDataStrings.INTERACTION_BODY, rigidBody);

                Globals.clientSceneWrapper.getInteractionEngine().registerCollisionObject(rigidBody, collidable);
            } break;
            case "CAPSULE": {
                //
                //create dbody
                rigidBody = CollisionBodyCreation.createCapsuleBody(
                    Globals.clientSceneWrapper.getInteractionEngine(),
                    physicsTemplate.getDimension2(),
                    physicsTemplate.getDimension2() - physicsTemplate.getDimension1() - physicsTemplate.getDimension1(),
                    categoryBit
                );

                //
                //set offset from center of entity position
                CollisionBodyCreation.setOffsetPosition(
                    Globals.clientSceneWrapper.getInteractionEngine(),
                    rigidBody,
                    new Vector3d(physicsTemplate.getOffsetX(), physicsTemplate.getOffsetY(), physicsTemplate.getOffsetZ())
                );

                //
                //create collidable and link to structures
                collidable = new Collidable(rVal, Collidable.TYPE_OBJECT, true);

                //
                //store values
                Matrix4d offsetTransform = new Matrix4d().translationRotateScale(
                    physicsTemplate.getOffsetX(), physicsTemplate.getOffsetY(), physicsTemplate.getOffsetZ(), //translate
                    physicsTemplate.getRotX(), physicsTemplate.getRotY(), physicsTemplate.getRotZ(), physicsTemplate.getRotW(), //rotate
                    1, 1, 1 //scale
                );
                rVal.putData(EntityDataStrings.INTERACTION_OFFSET_TRANSFORM, offsetTransform);
                rVal.putData(EntityDataStrings.INTERACTION_TEMPLATE, physicsTemplate);
                rVal.putData(EntityDataStrings.INTERACTION_COLLIDABLE, collidable);
                rVal.putData(EntityDataStrings.INTERACTION_BODY, rigidBody);

                Globals.clientSceneWrapper.getInteractionEngine().registerCollisionObject(rigidBody, collidable);
            } break;
            default: {
                throw new Error("Unsupported shape type! " + physicsTemplate.getType());
            }
        }
        lock.unlock();
    }

    /**
     * Destroys the interaction collidable attached to this entity
     * @param rVal The entity
     */
    public static void destroyCollidableTemplate(Entity rVal){
        lock.lock();
        CollisionEngine interactionEngine = Globals.clientSceneWrapper.getInteractionEngine();
        Collidable collidable = (Collidable)rVal.getData(EntityDataStrings.INTERACTION_COLLIDABLE);
        DBody body = (DBody)rVal.getData(EntityDataStrings.INTERACTION_BODY);
        if(body != null){
            PhysicsUtils.destroyBody(interactionEngine, body);
            if(collidable != null){
                interactionEngine.deregisterCollisionObject(body, collidable);
            }
        }
        lock.unlock();
    }

    /**
     * Updates the positions of all hitboxes
     */
    private static void updateInteractableTransforms(){
        lock.lock();
        CollisionEngine interactionEngine = Globals.clientSceneWrapper.getInteractionEngine();
        for(Entity entity : interactables){
            if(entity != null){
                Vector3d entityPosition = EntityUtils.getPosition(entity);
                Quaterniond entityRotation = EntityUtils.getRotation(entity);
                DBody body = (DBody)entity.getData(EntityDataStrings.INTERACTION_BODY);
                PhysicsUtils.setRigidBodyTransform(interactionEngine, new Vector3d(entityPosition), new Quaterniond(entityRotation), body);
            }
        }
        lock.unlock();
    }

    /**
     * Ray cast to pick the current interaction target
     * @return The current interaction target if it exists, null otherwise
     */
    public static Entity rayCast(Vector3d centerPos, Vector3d eyePos){
        lock.lock();
        CollisionEngine interactionEngine = Globals.clientSceneWrapper.getInteractionEngine();
        //update position of all interactables
        ClientInteractionEngine.updateInteractableTransforms();
        //ray cast
        Entity target = interactionEngine.rayCast(centerPos, eyePos, CollisionEngine.DEFAULT_INTERACT_DISTANCE, Collidable.MASK_NO_TERRAIN);
        lock.unlock();
        return target;
    }

}
