package electrosphere.entity.types.object;

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

import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.PhysicsMeshQueueItem;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityTags;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.btree.BehaviorTree;
import electrosphere.entity.state.gravity.ClientGravityTree;
import electrosphere.entity.state.gravity.ServerGravityTree;
import electrosphere.entity.state.hitbox.HitboxCollectionState;
import electrosphere.entity.state.idle.ClientIdleTree;
import electrosphere.entity.state.inventory.ClientInventoryState;
import electrosphere.entity.state.inventory.InventoryUtils;
import electrosphere.entity.state.inventory.ServerInventoryState;
import electrosphere.entity.state.inventory.UnrelationalInventoryState;
import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.game.data.collidable.CollidableTemplate;
import electrosphere.game.data.object.type.ObjectData;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.net.parser.net.message.NetworkMessage;
import electrosphere.net.server.player.Player;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils;
import electrosphere.server.datacell.utils.ServerEntityTagUtils;

public class ObjectUtils {

    //the entity type value
    public static final int ENTITY_TYPE_OBJECT = 2;
    
    /**
     * Spawns an object in the client scene
     * @param type The type of object
     * @return The object entity
     */
    public static Entity clientSpawnBasicObject(String type){
        ObjectData rawType = Globals.gameConfigCurrent.getObjectTypeLoader().getObject(type);
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();
        if(rawType.getModelPath() != null){
            EntityCreationUtils.makeEntityDrawable(rVal, rawType.getModelPath());
        }
        //forward-searching tokens
        for(String token : rawType.getTokens()){
            switch(token){
                case "GENERATE_COLLISION_OBJECT": {
                    Globals.assetManager.addCollisionMeshToQueue(new PhysicsMeshQueueItem(Globals.clientSceneWrapper.getCollisionEngine(),rawType.getModelPath()));
                    Globals.clientSceneWrapper.getScene().registerBehaviorTree(new BehaviorTree() {public void simulate(float deltaTime) {
                        DBody collisionObject = Globals.assetManager.fetchCollisionObject(Globals.clientSceneWrapper.getCollisionEngine(),rawType.getModelPath());
                        if(collisionObject != null){
                            Globals.clientSceneWrapper.getScene().deregisterBehaviorTree(this);
                            CollisionObjUtils.clientAttachCollisionObjectToEntity(rVal, collisionObject, 0, Collidable.TYPE_OBJECT);
                        }
                    }});
                } break;
                case "GENERATE_COLLISION_TERRAIN": {
                    Globals.assetManager.addCollisionMeshToQueue(new PhysicsMeshQueueItem(Globals.clientSceneWrapper.getCollisionEngine(),rawType.getModelPath()));
                    Globals.clientSceneWrapper.getScene().registerBehaviorTree(new BehaviorTree() {public void simulate(float deltaTime) {
                        DBody collisionObject = Globals.assetManager.fetchCollisionObject(Globals.clientSceneWrapper.getCollisionEngine(),rawType.getModelPath());
                        if(collisionObject != null){
                            Globals.clientSceneWrapper.getScene().deregisterBehaviorTree(this);
                            CollisionObjUtils.clientAttachCollisionObjectToEntity(rVal, collisionObject, 0, Collidable.TYPE_TERRAIN);
                        }
                    }});
                } break;
            }
        }
        //
        //main entity construction
        //physics
        if(rawType.getCollidable() != null){
            CollidableTemplate physicsTemplate = rawType.getCollidable();
            PhysicsEntityUtils.clientAttachCollidableTemplate(rVal, physicsTemplate);
        }
        //tokens
        for(String token : rawType.getTokens()){
            switch(token){
                case "GRAVITY":
                    Collidable collidable = (Collidable)rVal.getData(EntityDataStrings.PHYSICS_COLLIDABLE);
                    DBody collisionObject = (DBody)rVal.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
                    ClientGravityTree.attachTree(rVal, collidable, collisionObject, 30);
                    rVal.putData(EntityDataStrings.GRAVITY_ENTITY, true);
                    break;
                case "TARGETABLE":
                    Globals.clientSceneWrapper.getScene().registerEntityToTag(rVal, EntityTags.TARGETABLE);
                    break;
                case "INVENTORY":
                    rVal.putData(EntityDataStrings.NATURAL_INVENTORY,UnrelationalInventoryState.createUnrelationalInventory(10));
                    InventoryUtils.clientSetInventoryState(rVal, ClientInventoryState.clientCreateInventoryState(rVal));
                    break;
                case "OUTLINE":
                    rVal.putData(EntityDataStrings.DRAW_OUTLINE, true);
                    break;
                case "TERRAIN_COLLISION": {
                    CollisionObjUtils.getCollidable(rVal).overrideType(Collidable.TYPE_TERRAIN);
                } break;
                case "SPAWNPOINT": {
                    //ignore on client
                } break;
            }
        }
        if(rawType.getHitboxData() != null){
            HitboxCollectionState.attachHitboxState(Globals.clientSceneWrapper.getHitboxManager(), false, rVal, rawType.getHitboxData());
        }
        //add health system
        // rVal.putData(EntityDataStrings.LIFE_STATE, new LifeState(rVal, rawType.getHealthSystem()));
        // Globals.entityManager.registerLifeStateEntity(rVal);
        //idle tree & generic stuff all objects have
        rVal.putData(EntityDataStrings.TREE_IDLE, new ClientIdleTree(rVal));
        rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true);
        rVal.putData(EntityDataStrings.ENTITY_TYPE, ENTITY_TYPE_OBJECT);
        rVal.putData(EntityDataStrings.ENTITY_SUBTYPE, type);
        return rVal;
    }



    /**
     * Spawns a server-side object
     * @param type The type of object to spawn
     * @return The object
     */
    public static Entity serverSpawnBasicObject(Realm realm, Vector3d position, String type){
        ObjectData rawType = Globals.gameConfigCurrent.getObjectTypeLoader().getObject(type);
        Entity rVal = EntityCreationUtils.createServerEntity(realm, position);

        if(rawType.getModelPath() != null){
            EntityCreationUtils.makeEntityPoseable(rVal, rawType.getModelPath());
        }
        //forward-searching tokens
        for(String token : rawType.getTokens()){
            switch(token){
                case "GENERATE_COLLISION_OBJECT": {
                    Globals.assetManager.addCollisionMeshToQueue(new PhysicsMeshQueueItem(realm.getCollisionEngine(),rawType.getModelPath()));
                    ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, new BehaviorTree() {public void simulate(float deltaTime) {
                        DBody collisionObject = Globals.assetManager.fetchCollisionObject(realm.getCollisionEngine(),rawType.getModelPath());
                        if(collisionObject != null){
                            ServerBehaviorTreeUtils.detatchBTreeFromEntity(rVal, this);
                            CollisionObjUtils.serverAttachCollisionObjectToEntity(rVal, collisionObject, 0, Collidable.TYPE_OBJECT);
                        }
                    }});
                } break;
                case "GENERATE_COLLISION_TERRAIN": {
                    Globals.assetManager.addCollisionMeshToQueue(new PhysicsMeshQueueItem(realm.getCollisionEngine(),rawType.getModelPath()));
                    ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, new BehaviorTree() {public void simulate(float deltaTime) {
                        DBody collisionObject = Globals.assetManager.fetchCollisionObject(realm.getCollisionEngine(),rawType.getModelPath());
                        if(collisionObject != null){
                            ServerBehaviorTreeUtils.detatchBTreeFromEntity(rVal, this);
                            CollisionObjUtils.serverAttachCollisionObjectToEntity(rVal, collisionObject, 0, Collidable.TYPE_TERRAIN);
                        }
                    }});
                } break;
            }
        }
        //
        //main entity construction
        //physics
        if(rawType.getCollidable() != null){
            CollidableTemplate physicsTemplate = rawType.getCollidable();
            PhysicsEntityUtils.serverAttachCollidableTemplate(realm, rVal, physicsTemplate);
        }
        //tokens
        for(String token : rawType.getTokens()){
            switch(token){
                case "GRAVITY":
                    Collidable collidable = (Collidable)rVal.getData(EntityDataStrings.PHYSICS_COLLIDABLE);
                    DBody collisionObject = (DBody)rVal.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
                    ServerGravityTree.attachTree(rVal, collidable, collisionObject, 30);
                    rVal.putData(EntityDataStrings.GRAVITY_ENTITY, true);
                    break;
                case "TARGETABLE":
                    ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.TARGETABLE);
                    break;
                case "INVENTORY":
                    rVal.putData(EntityDataStrings.NATURAL_INVENTORY,UnrelationalInventoryState.createUnrelationalInventory(10));
                    InventoryUtils.serverSetInventoryState(rVal, ServerInventoryState.serverCreateInventoryState(rVal));
                    break;
                case "OUTLINE":
                    rVal.putData(EntityDataStrings.DRAW_OUTLINE, true);
                    break;
                case "TERRAIN_COLLISION": {
                    CollisionObjUtils.getCollidable(rVal).overrideType(Collidable.TYPE_TERRAIN);
                } break;
                case "SPAWNPOINT": {
                    realm.registerSpawnPoint(position);
                } break;
            }
        }
        if(rawType.getHitboxData() != null){
            HitboxCollectionState.attachHitboxState(realm.getHitboxManager(), true, rVal, rawType.getHitboxData());
        }


        //required for synchronization manager
        ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.OBJECT);
        EntityUtils.setEntityType(rVal, ENTITY_TYPE_OBJECT);
        EntityUtils.setEntitySubtype(rVal, type);


        //position entity
        //this needs to be called at the end of this function.
        //Burried underneath this is function call to initialize a server side entity.
        //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored
        //the server will not be able to synchronize it properly.
        ServerEntityUtils.initiallyPositionEntity(realm,rVal,position);

        return rVal;
    }

    /**
     * Checks if the provided entity is an object
     * @param entity The entity
     * @return true if it is an object, false otherwise
     */
    public static boolean isObject(Entity entity){
        if(!entity.containsKey(EntityDataStrings.ENTITY_TYPE)){
            return false;
        }
        return (int)entity.getData(EntityDataStrings.ENTITY_TYPE) == ENTITY_TYPE_OBJECT;
    }

    /**
     * Gets the type of object
     * @param e the entity
     * @return the type
     */
    public static String getType(Entity e){
        return (String)EntityUtils.getEntitySubtype(e);
    }

    /**
     * Sets the object to a given player
     * @param player The player
     * @param item The object entity
     */
    public static void sendEntityToPlayer(Player player, Entity object){
        int id = object.getId();
        String type = ObjectUtils.getType(object);
        Vector3d position = EntityUtils.getPosition(object);
        //construct the spawn message and attach to player
        NetworkMessage message = EntityMessage.constructSpawnObjectMessage(
            id,
            type,
            position.x,
            position.y,
            position.z
        );
        player.addMessage(message);
    }

}
