package electrosphere.entity.types.item;

import java.util.List;

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

import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
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.state.AnimationPriorities;
import electrosphere.entity.state.gravity.ClientGravityTree;
import electrosphere.entity.state.gravity.ServerGravityTree;
import electrosphere.entity.state.hitbox.HitboxCollectionState;
import electrosphere.entity.types.attach.AttachUtils;
import electrosphere.game.data.collidable.CollidableTemplate;
import electrosphere.game.data.item.type.EquipData;
import electrosphere.game.data.item.type.EquipWhitelist;
import electrosphere.game.data.item.type.Item;
import electrosphere.game.data.item.type.WeaponData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.net.parser.net.message.NetworkMessage;
import electrosphere.net.server.player.Player;
import electrosphere.renderer.actor.Actor;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.utils.ServerEntityTagUtils;
import electrosphere.server.poseactor.PoseActor;

/**
 * Utilities for working with items
 */
public class ItemUtils {

    //The item creature type
    public static final int ENTITY_TYPE_ITEM = 1;

    //generic item icon filepath
    static final String genericItemIconPath = "Textures/icons/itemIconItemGeneric.png";

    public static Entity clientSpawnBasicItem(String name){
        Item item = Globals.gameConfigCurrent.getItemMap().getItem(name);
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();
        EntityCreationUtils.makeEntityDrawable(rVal, item.getModelPath());
        if(item.getWeaponData() != null){
            rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true);
            WeaponData weaponData = item.getWeaponData();
            if(weaponData.getHitboxes() != null){
                HitboxCollectionState.attachHitboxState(Globals.clientSceneWrapper.getHitboxManager(), false, rVal, weaponData.getHitboxes());
            }
            rVal.putData(EntityDataStrings.ITEM_WEAPON_CLASS,weaponData.getWeaponClass());
            rVal.putData(EntityDataStrings.ITEM_WEAPON_DATA_RAW,weaponData);
        }
        //physics
        if(item.getCollidable() != null){
            CollidableTemplate physicsTemplate = item.getCollidable();
            PhysicsEntityUtils.clientAttachCollidableTemplate(rVal, physicsTemplate);
        }
        //tokens
        for(String token : item.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 "OUTLINE":
                rVal.putData(EntityDataStrings.DRAW_OUTLINE, true);
                break;
            }
        }
        if(item.getIdleAnim() != null){
            rVal.putData(EntityDataStrings.ANIM_IDLE,item.getIdleAnim());
        }
        if(item.getIconPath() != null && !item.getIconPath().equals("")){
            rVal.putData(EntityDataStrings.ITEM_ICON,item.getIconPath());
        } else {
            rVal.putData(EntityDataStrings.ITEM_ICON,genericItemIconPath);
        }

        //
        //
        // Equip data
        //
        //
        if(item.getEquipData() != null){
            EquipData equipData = item.getEquipData();
            if(equipData.getEquipWhitelist() != null){
                rVal.putData(EntityDataStrings.ITEM_EQUIP_WHITELIST, equipData.getEquipWhitelist());
            }
            if(equipData.getEquipClass() != null){
                rVal.putData(EntityDataStrings.ITEM_EQUIP_CLASS,equipData.getEquipClass());
            }
        }
        rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true);
        EntityUtils.setEntityType(rVal, ENTITY_TYPE_ITEM);
        rVal.putData(EntityDataStrings.ITEM_IS_IN_INVENTORY, false);
        EntityUtils.setEntitySubtype(rVal, name);
//        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, new Vector3f(0.005f,0.005f,0.005f));
//        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, new Quaternionf().identity().rotateY((float)(-Math.PI/2)).rotateZ(-(float)(Math.PI/2)));
        Globals.clientSceneWrapper.getScene().registerEntityToTag(rVal, EntityTags.ITEM);
        return rVal;
    }



    /**
     * Spawns an item on the server
     * @param realm the realm to spawn in
     * @param position the position to spawn at
     * @param name the name of the item to spawn
     * @return The item entity
     */
    public static Entity serverSpawnBasicItem(Realm realm, Vector3d position, String name){
        Item item = Globals.gameConfigCurrent.getItemMap().getItem(name);
        //must correct the position such that it spawns inside the realm
        Vector3d correctedPosition = ServerEntityUtils.guaranteePositionIsInBounds(realm, position);
        Entity rVal = EntityCreationUtils.createServerEntity(realm, correctedPosition);// EntityUtils.spawnDrawableEntity(item.getModelPath());
        EntityCreationUtils.makeEntityPoseable(rVal, item.getModelPath());
        if(item.getWeaponData() != null){
            rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true);
            WeaponData weaponData = item.getWeaponData();
            if(weaponData.getHitboxes() != null){
                HitboxCollectionState.attachHitboxState(realm.getHitboxManager(), true, rVal, weaponData.getHitboxes());
            }
            rVal.putData(EntityDataStrings.ITEM_WEAPON_CLASS,weaponData.getWeaponClass());
            rVal.putData(EntityDataStrings.ITEM_WEAPON_DATA_RAW,weaponData);
        }
        //physics
        if(item.getCollidable() != null){
            CollidableTemplate physicsTemplate = item.getCollidable();
            PhysicsEntityUtils.serverAttachCollidableTemplate(realm, rVal, physicsTemplate);
        }
        //tokens
        for(String token : item.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 "OUTLINE":
                rVal.putData(EntityDataStrings.DRAW_OUTLINE, true);
                break;
            }
        }
        if(item.getIdleAnim() != null){
            rVal.putData(EntityDataStrings.ANIM_IDLE,item.getIdleAnim());
        }
        if(item.getIconPath() != null && !item.getIconPath().equals("")){
            rVal.putData(EntityDataStrings.ITEM_ICON,item.getIconPath());
        } else {
            rVal.putData(EntityDataStrings.ITEM_ICON,genericItemIconPath);
        }
        //
        //
        // Equip data
        //
        //
        if(item.getEquipData() != null){
            EquipData equipData = item.getEquipData();
            if(equipData.getEquipWhitelist() != null){
                rVal.putData(EntityDataStrings.ITEM_EQUIP_WHITELIST, equipData.getEquipWhitelist());
            }
            if(equipData.getEquipClass() != null){
                rVal.putData(EntityDataStrings.ITEM_EQUIP_CLASS,equipData.getEquipClass());
            }
        }
        rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true);
        rVal.putData(EntityDataStrings.ITEM_IS_IN_INVENTORY, false);
//        rVal.putData(EntityDataStrings.DATA_STRING_SCALE, new Vector3f(0.005f,0.005f,0.005f));
//        rVal.putData(EntityDataStrings.DATA_STRING_ROTATION, new Quaternionf().identity().rotateY((float)(-Math.PI/2)).rotateZ(-(float)(Math.PI/2)));


        //required for synchronization manager
        ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.ITEM);
        EntityUtils.setEntityType(rVal, ENTITY_TYPE_ITEM);
        EntityUtils.setEntitySubtype(rVal, name);


        //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,correctedPosition);

        //error checking
        if(Globals.realmManager.getEntityRealm(rVal) == null){
            LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Created item without it being assigned to a realm!"));
        }

        return rVal;
    }
    
    public static void removePhysics(Entity item){
        /*
        rVal.putData(EntityDataStrings.PHYSICS_COLLISION_BODY, rigidBody);
                    rVal.putData(EntityDataStrings.PHYSICS_COLLISION_BODY_OFFSET, new Vector3f(physicsTemplate.getOffsetX(),physicsTemplate.getOffsetY(),physicsTemplate.getOffsetZ()));
                    rVal.putData(EntityDataStrings.PHYSICS_MODEL_TEMPLATE, physicsTemplate);
                    rVal.putData(EntityDataStrings.PHYSICS_COLLIDABLE,collidable);
                    rVal.putData(EntityDataStrings.COLLIDABLE_TREE, new CollidableTree(rVal,collidable,rigidBody));
        */
        if(item.containsKey(EntityDataStrings.PHYSICS_COLLISION_BODY)){
            item.removeData(EntityDataStrings.PHYSICS_COLLISION_BODY);
        }
        if(item.containsKey(EntityDataStrings.PHYSICS_MODEL_TEMPLATE)){
            item.removeData(EntityDataStrings.PHYSICS_MODEL_TEMPLATE);
        }
        if(item.containsKey(EntityDataStrings.PHYSICS_COLLIDABLE)){
            item.removeData(EntityDataStrings.PHYSICS_COLLIDABLE);
        }
        if(item.containsKey(EntityDataStrings.SERVER_COLLIDABLE_TREE)){
            item.removeData(EntityDataStrings.SERVER_COLLIDABLE_TREE);
        }
        if(item.containsKey(EntityDataStrings.CLIENT_COLLIDABLE_TREE)){
            item.removeData(EntityDataStrings.CLIENT_COLLIDABLE_TREE);
        }
    }
    
    /**
     * Updates an item to play its idle animation
     * @param item THe item entity
     */
    public static void updateItemActorAnimation(Entity item){
        Actor actor = EntityUtils.getActor(item);
        if(actor != null && item.getData(EntityDataStrings.ANIM_IDLE) != null){
            String idleAnim = (String)item.getData(EntityDataStrings.ANIM_IDLE);
            if(!actor.isPlayingAnimation(idleAnim)){
                actor.playAnimation(idleAnim,AnimationPriorities.getValue(AnimationPriorities.INTERACTION));
                actor.incrementAnimationTime(0.0001);
            }
        }
    }

    /**
     * Updates the item's pose actor to play its idle animation on the server
     * @param item The item entity
     */
    public static void updateItemPoseActorAnimation(Entity item){
        PoseActor actor = EntityUtils.getPoseActor(item);
        if(actor != null && item.getData(EntityDataStrings.ANIM_IDLE) != null){
            String idleAnim = (String)item.getData(EntityDataStrings.ANIM_IDLE);
            if(!actor.isPlayingAnimation(idleAnim)){
                actor.playAnimation(idleAnim,AnimationPriorities.getValue(AnimationPriorities.INTERACTION));
                actor.incrementAnimationTime(0.0001);
            }
        }
    }
    
    public static void sendEntityToPlayer(Player player, Entity item){
        int id = item.getId();
        String type = ItemUtils.getType(item);
        Vector3d position = EntityUtils.getPosition(item);
        //construct the spawn message and attach to player
        NetworkMessage message = EntityMessage.constructSpawnItemMessage(
            id,
            type,
            position.x,
            position.y,
            position.z);
        player.addMessage(message);
    }
    

    /**
     * Checks if this entity is an item
     * @param e the entity
     * @return true if it is an item, false otherwise
     */
    public static boolean isItem(Entity e){
        if(!e.containsKey(EntityDataStrings.ENTITY_TYPE)){
            return false;
        }
        return (int)e.getData(EntityDataStrings.ENTITY_TYPE) == ENTITY_TYPE_ITEM;
    }

    /**
     * Checks if an item should be sent to a client on synchronization
     * @param item The item
     * @return true if should be sent, false otherwise
     */
    public static boolean itemShouldBeSentToClient(Entity item){
        //if the item is attached to a creature, don't send a dedicated item
        //instead, the creature template should include the item
        if(AttachUtils.hasParent(item)){
            return false;
        }
        return true;
    }

    /**
     * Checks if an item should be serialized to disk when saving a chunk
     * @param item The item
     * @return true if should be sent, false otherwise
     */
    public static boolean itemShouldBeSerialized(Entity item){
        //if the item is attached to a creature, don't save a dedicated item
        //instead, the creature template should include the item
        if(AttachUtils.hasParent(item)){
            return false;
        }
        return true;
    }
    
    /**
     * Gets the type of item
     * @param item The entity
     * @return The type
     */
    public static String getType(Entity item){
        return EntityUtils.getEntitySubtype(item);
    }
    
    public static boolean isWeapon(Entity item){
        return item.containsKey(EntityDataStrings.ITEM_IS_WEAPON);
    }

    public static boolean isArmor(Entity item){
        return item.containsKey(EntityDataStrings.ITEM_IS_ARMOR);
    }

    public static boolean hasEquipList(Entity item){
        return item.containsKey(EntityDataStrings.ITEM_EQUIP_WHITELIST);
    }

    public static List<EquipWhitelist> getEquipWhitelist(Entity item){
        return (List<EquipWhitelist>)item.getData(EntityDataStrings.ITEM_EQUIP_WHITELIST);
    }

    /**
     * Checks if the item is inside an inventory container
     * @param item The item entity to check
     * @return true if the item IS in an inventory container, otherwise false
     */
    public static boolean itemIsInInventory(Entity item){
        return (boolean)item.getData(EntityDataStrings.ITEM_IS_IN_INVENTORY);
    }

    /**
     * Sets the parent that contains this item
     * @param item The item entity
     * @param parent The parent which contains the item entity
     */
    public static void setContainingParent(Entity item, Entity parent){
        item.putData(EntityDataStrings.ITEM_CONTAINING_PARENT, parent);
    }

    /**
     * Gets the parent that contains the item
     * @param item The item
     * @return The parent that contains the item
     */
    public static Entity getContainingParent(Entity item){
        return (Entity)item.getData(EntityDataStrings.ITEM_CONTAINING_PARENT);
    }

    /**
     * Emits an entity which represents the item inside a container
     * @param item The item to recreate
     * @param containingParent The parent that contains the item
     */
    public static Entity clientRecreateContainerItem(Entity item, Entity containingParent){
        if(isItem(item)){
            Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(ItemUtils.getType(item));
            Entity rVal = EntityCreationUtils.createClientNonSpatialEntity();
            if(getEquipWhitelist(item) != null){
                rVal.putData(EntityDataStrings.ITEM_EQUIP_WHITELIST, getEquipWhitelist(item));
            }
            if(itemData.getWeaponData() != null){
                rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true);
                WeaponData weaponData = itemData.getWeaponData();
                rVal.putData(EntityDataStrings.ITEM_WEAPON_CLASS,weaponData.getWeaponClass());
                rVal.putData(EntityDataStrings.ITEM_WEAPON_DATA_RAW,weaponData);
            }
            rVal.putData(EntityDataStrings.ITEM_ICON,ItemUtils.getItemIcon(item));
            rVal.putData(EntityDataStrings.ITEM_EQUIP_CLASS, item.getData(EntityDataStrings.ITEM_EQUIP_CLASS));
            EntityUtils.setEntityType(rVal, ENTITY_TYPE_ITEM);
            rVal.putData(EntityDataStrings.ITEM_IS_IN_INVENTORY, true);
            ItemUtils.setContainingParent(rVal, containingParent);
            EntityUtils.setEntitySubtype(rVal, EntityUtils.getEntitySubtype(item));
            Globals.clientSceneWrapper.getScene().registerEntity(rVal);
            Globals.clientSceneWrapper.getScene().registerEntityToTag(rVal, EntityTags.ITEM);
            return rVal;
        } else {
            return null;
        }
    }


    /**
     * Emits an entity which represents the item inside a container
     * @param item The item to recreate
     * @param containingParent The parent that contains the item
     */
    public static Entity serverRecreateContainerItem(Entity item, Entity containingParent){
        if(isItem(item)){
            Entity rVal = EntityCreationUtils.createRealmlessServerEntity();
            if(getEquipWhitelist(item) != null){
                rVal.putData(EntityDataStrings.ITEM_EQUIP_WHITELIST, getEquipWhitelist(item));
            }
            rVal.putData(EntityDataStrings.ITEM_ICON,ItemUtils.getItemIcon(item));
            rVal.putData(EntityDataStrings.ITEM_EQUIP_CLASS, item.getData(EntityDataStrings.ITEM_EQUIP_CLASS));
            EntityUtils.setEntityType(rVal, ENTITY_TYPE_ITEM);
            rVal.putData(EntityDataStrings.ITEM_IS_IN_INVENTORY, true);
            ItemUtils.setContainingParent(rVal, containingParent);
            EntityUtils.setEntitySubtype(rVal, EntityUtils.getEntitySubtype(item));
            return rVal;
        } else {
            return null;
        }
    }

    public static void clientDestroyInWorldItem(Entity item){
        if(isItem(item)){
            //destroy physics
            if(item.containsKey(EntityDataStrings.PHYSICS_COLLISION_BODY) && item.containsKey(EntityDataStrings.PHYSICS_COLLIDABLE)){
                //destroy physics
                //this deregisters from all four & unhooks rigid bodies from the physics runtime
                Globals.clientSceneWrapper.getCollisionEngine().destroyEntityThatHasPhysics(item);
                //destroy hitboxes
                HitboxCollectionState.destroyHitboxState(item);
                //destroy graphics
                EntityUtils.cleanUpEntity(item);
            }
        }
    }

    public static String getWeaponClass(Entity item){
        return (String)item.getData(EntityDataStrings.ITEM_WEAPON_CLASS);
    }

    public static String getItemIcon(Entity item){
        return (String)item.getData(EntityDataStrings.ITEM_ICON);
    }

    public static String getEquipClass(Entity item){
        return (String)item.getData(EntityDataStrings.ITEM_EQUIP_CLASS);
    }

    public static void setRealWorldEntity(Entity inInventory, Entity inWorld){
        inInventory.putData(EntityDataStrings.ITEM_IN_WORLD_REPRESENTATION, inWorld);
    }

    public static Entity getRealWorldEntity(Entity inInventory){
        return (Entity) inInventory.getData(EntityDataStrings.ITEM_IN_WORLD_REPRESENTATION);
    }

    public static WeaponData getWeaponDataRaw(Entity item){
        return (WeaponData) item.getData(EntityDataStrings.ITEM_WEAPON_DATA_RAW);
    }

}
