package electrosphere.entity.state.inventory;

import org.joml.Vector3d;

import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.state.equip.ClientEquipState;
import electrosphere.entity.state.equip.ServerEquipState;
import electrosphere.entity.state.gravity.GravityUtils;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.entity.types.item.ItemUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.net.parser.net.message.InventoryMessage;
import electrosphere.net.parser.net.message.NetworkMessage;
import electrosphere.net.server.player.Player;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.ServerDataCell;
import electrosphere.server.datacell.utils.DataCellSearchUtils;
import electrosphere.server.utils.ServerScriptUtils;

public class InventoryUtils {
    

    public static boolean hasNaturalInventory(Entity target){
        return target.containsKey(EntityDataStrings.NATURAL_INVENTORY);
    }

    public static UnrelationalInventoryState getNaturalInventory(Entity target){
        return (UnrelationalInventoryState)target.getData(EntityDataStrings.NATURAL_INVENTORY);
    }

    public static boolean hasEquipInventory(Entity target){
        return target.containsKey(EntityDataStrings.EQUIP_INVENTORY);
    }

    public static RelationalInventoryState getEquipInventory(Entity target){
        return (RelationalInventoryState)target.getData(EntityDataStrings.EQUIP_INVENTORY);
    }

    /**
     * Gets the current inventory state
     * @param target the entity to get inventory state from
     * @return The inventory state behavior tree or null
     */
    public static ClientInventoryState clientGetInventoryState(Entity target){
        return (ClientInventoryState)target.getData(EntityDataStrings.CLIENT_INVENTORY_STATE);
    }

    /**
     * Gets the current inventory state
     * @param target the entity to get inventory state from
     * @return The inventory state behavior tree or null
     */
    public static ServerInventoryState serverGetInventoryState(Entity target){
        return (ServerInventoryState)target.getData(EntityDataStrings.SERVER_INVENTORY_STATE);
    }

    /**
     * Sets the current inventory state
     * @param target The entity to attach inventory state to
     * @param state The inventory state to attach
     */
    public static void clientSetInventoryState(Entity target, ClientInventoryState state){
        target.putData(EntityDataStrings.CLIENT_INVENTORY_STATE, state);
    }

    /**
     * Sets the current inventory state
     * @param target The entity to attach inventory state to
     * @param state The inventory state to attach
     */
    public static void serverSetInventoryState(Entity target, ServerInventoryState state){
        target.putData(EntityDataStrings.SERVER_INVENTORY_STATE, state);
    }

    /**
     * Perform the entity transforms to actually store an item in an inventory, if server this has the side effect of also sending packets on success
     * @param creature The creature to store the item in
     * @param item The item to store
     * @return The in-inventory item
     */
    public static Entity serverAttemptStoreItemTransform(Entity creature, Entity item){
        boolean creatureIsCreature = CreatureUtils.isCreature(creature);
        boolean itemIsItem = ItemUtils.isItem(item);
        boolean hasInventory = hasNaturalInventory(creature);
        //check if the item is already in an inventory
        boolean itemIsInInventory = ItemUtils.itemIsInInventory(item);
        if(creatureIsCreature && itemIsItem && hasInventory && !itemIsInInventory){
            //get inventory
            //for the moment we're just gonna get natural inventory
            //later we'll need to search through all creature inventories to find the item
            UnrelationalInventoryState inventory = getNaturalInventory(creature);
            //destroy in-world entity and create in-inventory item
            //we're doing this so that we're not constantly sending networking messages for invisible entities attached to the player
            Entity inventoryItem = ItemUtils.serverRecreateContainerItem(item, creature);
            //store item in inventory
            inventory.addItem(inventoryItem);
            //set item containing parent
            ItemUtils.setContainingParent(inventoryItem, creature);
            //if we are the server, immediately send required packets
            ServerDataCell dataCell = DataCellSearchUtils.getEntityDataCell(item);
            // ServerDataCell dataCell = Globals.dataCellLocationResolver.getDataCellAtPoint(EntityUtils.getPosition(item),item);
            //broadcast destroy entity
            dataCell.broadcastNetworkMessage(EntityMessage.constructDestroyMessage(item.getId()));
            //tell controlling player that they have an item in their inventory
            if(CreatureUtils.hasControllerPlayerId(creature)){
                //get the player
                int controllerPlayerID = CreatureUtils.getControllerPlayerId(creature);
                Player controllerPlayer = Globals.playerManager.getPlayerFromId(controllerPlayerID);
                //send message
                controllerPlayer.addMessage(InventoryMessage.constructaddItemToInventoryMessage(inventoryItem.getId(), ItemUtils.getType(inventoryItem)));
            }
            //alert script engine
            ServerScriptUtils.fireSignalOnEntity(creature, "itemPickup", item.getId(), inventoryItem.getId());
            //destroy the item that was left over
            ServerEntityUtils.destroyEntity(item);
            return inventoryItem;
        }
        return null;
    }

    /**
     * Attempts to store the in-world item entity in a creature inventory container
     * @param creature the creature which has a natural inventory
     * @param item the in-world item entity to store
     */
    public static void clientAttemptStoreItem(Entity creature, Entity item){
        //tell the server we want to try the transform
        NetworkMessage requestPickupMessage = InventoryMessage.constructaddItemToInventoryMessage(
            Globals.clientSceneWrapper.mapClientToServerId(item.getId()), 
            ItemUtils.getType(item)
        );
        Globals.clientConnection.queueOutgoingMessage(requestPickupMessage);
    }

    /**
     * Attempts to store the in-world item entity in a creature inventory container
     * @param creature the creature which has a natural inventory
     * @param item the in-world item entity to store
     * @return The in-inventory entity if it succeeded, null otherwise
     */
    public static Entity serverAttemptStoreItem(Entity creature, Entity item){
        //immediately attempt the transform
        return serverAttemptStoreItemTransform(creature,item);
    }

    /**
     * Places an item of provided type in the parent container's natural inventory
     * @param parentContainer The entity (typically a creature) which will receive the item in their natural inventory
     * @param type The type of item to place in the inventory
     * @return The in-inventory item entity
     */
    public static Entity clientConstructInInventoryItem(Entity parentContainer, String type){
        //sanity checks
        boolean creatureIsCreature = CreatureUtils.isCreature(parentContainer);
        boolean hasInventory = hasNaturalInventory(parentContainer);
        if(creatureIsCreature && hasInventory){
            //if we pass sanity checks, actually perform transform
            //get inventory
            //for the moment we're just gonna get natural inventory
            //later we'll need to search through all creature inventories to find the item
            UnrelationalInventoryState inventory = getNaturalInventory(parentContainer);
            //create item
            //TODO: optimize by directly creating the container item instead of first spawning regular item
            LoggerInterface.loggerNetworking.DEBUG("[CLIENT] Spawning temporary in-world item before placing into inventory");
            Entity spawnedItem = ItemUtils.clientSpawnBasicItem(type);
            //convert to in-inventory
            Entity inventoryItem = ItemUtils.clientRecreateContainerItem(spawnedItem, parentContainer);
            //destroy the item that was left over
            ItemUtils.clientDestroyInWorldItem(spawnedItem);
            //store item in inventory
            inventory.addItem(inventoryItem);
            //set item containing parent
            ItemUtils.setContainingParent(inventoryItem, parentContainer);
            //return
            return inventoryItem;
        }
        //if we fail, return null
        return null;
    }


    /**
     * Attempts the transform to eject an item from an inventory, if this is the server it has added side effect of sending packets on success
     * @param creature The creature to eject the item from
     * @param item The item to eject
     */
    public static void serverAttemptEjectItemTransform(Entity creature, Entity item){
        //verify creature is creature, item is item, inventory exists, and item is in inventory
        boolean creatureIsCreature = CreatureUtils.isCreature(creature);
        boolean itemIsItem = ItemUtils.isItem(item);
        boolean hasNaturalInventory = hasNaturalInventory(creature);
        boolean hasEquipInventory = hasEquipInventory(creature);
        //check if the item is in an inventory
        boolean itemIsInInventory = ItemUtils.itemIsInInventory(item);
        if(creatureIsCreature && itemIsItem && itemIsInInventory){
            if(hasNaturalInventory){
                //get inventory
                UnrelationalInventoryState inventory = getNaturalInventory(creature);
                //remove item from inventory
                inventory.removeItem(item);
            }
            if(hasEquipInventory){
                //get inventory
                RelationalInventoryState inventory = getEquipInventory(creature);
                //get inventory slot
                String inventorySlot = inventory.getItemSlot(item);
                //remove item from inventory
                if(inventory.tryRemoveItem(item) != null){
                    //get real world item
                    Entity realWorldItem = ItemUtils.getRealWorldEntity(item);
                    if(realWorldItem != null){
                        //Tell players to unequip the item
                        Realm realm = Globals.realmManager.getEntityRealm(realWorldItem);
                        if(realm != null){
                            //get closest chunk
                            ServerDataCell dataCell = realm.getEntityDataCellMapper().getEntityDataCell(realWorldItem);
                            //broadcast destroy item
                            NetworkMessage unequipMessage = InventoryMessage.constructserverCommandUnequipItemMessage(creature.getId(), inventorySlot);
                            dataCell.broadcastNetworkMessage(unequipMessage);
                        }
                        //drop item
                        ServerEquipState equipState = ServerEquipState.getEquipState(creature);
                        equipState.serverTransformUnequipPoint(inventorySlot);
                    }
                }
            }
            //
            //tell player that the item is no longer in their inventory
            if(CreatureUtils.hasControllerPlayerId(creature)){
                //get player
                int playerId = CreatureUtils.getControllerPlayerId(creature);
                Player controllerPlayer = Globals.playerManager.getPlayerFromId(playerId);
                //tell the player they don't have the item anymore
                controllerPlayer.addMessage(InventoryMessage.constructremoveItemFromInventoryMessage(item.getId()));
            }
            //get parent realm
            Realm realm = Globals.realmManager.getEntityRealm(creature);
            //find "in front of creature"
            Vector3d dropSpot = new Vector3d(EntityUtils.getPosition(creature)).add(CreatureUtils.getFacingVector(creature));
            //
            //compose item into in-world entity
            Entity inWorldItem = ItemUtils.serverSpawnBasicItem(realm,dropSpot,ItemUtils.getType(item));
            //destroy the entity on server side
            ServerEntityUtils.destroyEntity(item);
            //activate gravity
            GravityUtils.serverAttemptActivateGravity(inWorldItem);
        }
    }

    //need creature so we can figure out where to drop the item
    public static void serverAttemptEjectItem(Entity creature, Entity item){
        //if we're the server, immediately attempt the transform
        serverAttemptEjectItemTransform(creature,item);
    }

    //need creature so we can figure out where to drop the item
    public static void clientAttemptEjectItem(Entity creature, Entity item){
        //if we're the client, tell the server we want to try the transform
        NetworkMessage requestPickupMessage = InventoryMessage.constructremoveItemFromInventoryMessage(Globals.clientSceneWrapper.mapClientToServerId(item.getId()));
        Globals.clientConnection.queueOutgoingMessage(requestPickupMessage);
    }

    /**
     * [CLIENT ONLY] Called when the server says to remove an item from all inventories
     * Only does the remove, doesn't create the in-world item
     * @param creature The creature to remove the item from (likely to be player entity)
     * @param item The item to remove
     */
    public static void removeItemFromInventories(Entity creature, Entity item){
        //verify creature is creature, item is item, inventory exists, and item is in inventory
        boolean creatureIsCreature = CreatureUtils.isCreature(creature);
        boolean itemIsItem = ItemUtils.isItem(item);
        boolean hasNaturalInventory = hasNaturalInventory(creature);
        boolean hasEquipInventory = hasEquipInventory(creature);
        //check if the item is in an inventory
        boolean itemIsInInventory = ItemUtils.itemIsInInventory(item);
        if(creatureIsCreature && itemIsItem && itemIsInInventory){
            if(hasNaturalInventory){
                //get inventory
                UnrelationalInventoryState inventory = getNaturalInventory(creature);
                //remove item from inventory
                inventory.removeItem(item);
            }
            if(hasEquipInventory){
                //get inventory
                RelationalInventoryState inventory = getEquipInventory(creature);
                //get real world item
                Entity realWorldItem = ItemUtils.getRealWorldEntity(item);
                if(realWorldItem != null){
                    //drop item
                    ClientEquipState equipState = ClientEquipState.getEquipState(creature);
                    equipState.clientTransformUnequipPoint(inventory.getItemSlot(item));
                }
                //remove item from inventory
                inventory.tryRemoveItem(item);
            }
        }
    }


    //need creature so we can figure out where to drop the item
    // public static void attemptDestroyItem(Entity creature, Entity item){
    //     //verify creature is creature, item is item, inventory exists, and item is in inventory
    //     boolean creatureIsCreature = CreatureUtils.isCreature(creature);
    //     boolean itemIsItem = ItemUtils.isItem(item);
    //     boolean hasNaturalInventory = hasNaturalInventory(creature);
    //     boolean hasEquipInventory = hasEquipInventory(creature);
    //     if(creatureIsCreature && itemIsItem){
    //         if(hasNaturalInventory){
    //             //get inventory
    //             UnrelationalInventoryState inventory = getNaturalInventory(creature);
    //             //remove item from inventory
    //             inventory.removeItem(item);
    //         }
    //         if(hasEquipInventory){
    //             //get inventory
    //             RelationalInventoryState inventory = getEquipInventory(creature);
    //             //get real world item
    //             Entity realWorldItem = ItemUtils.getRealWorldEntity(item);
    //             if(realWorldItem != null){
    //                 //drop item
    //                 EquipState equipState = EquipState.getEquipState(creature);
    //                 equipState.transformUnequipPoint(inventory.getItemSlot(item));
    //             }
    //             //remove item from inventory
    //             inventory.tryRemoveItem(item);
    //         }
    //         //delete in container item
    //         ItemUtils.destroyInInventoryItem(item);
    //     }
    // }

}
