package electrosphere.game.server.datacell;

import electrosphere.entity.Entity;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.entity.types.item.ItemUtils;
import electrosphere.entity.types.structure.StructureUtils;
import electrosphere.net.server.Player;
import electrosphere.game.server.character.Character;
import electrosphere.game.server.pathfinding.NavMeshUtils;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.main.Globals;
import electrosphere.net.parser.net.message.NetworkMessage;
import java.util.LinkedList;
import java.util.List;

/**
 * Container for entities loaded into memory. This isn't intended to be in charge
 * of simulation. It just acts as an object to relate players and entities by location.
 * This SHOULD be used for networking purposes. This is the mechanism to scope
 * network messages by location.
 * 
 */
public class ServerDataCell {
    
    List<Entity> loadedEntities = new LinkedList();
    List<Player> activePlayers = new LinkedList();
    NavMesh navMesh;
    
    
    /**
     * Constructs a datacell based on a virtual cell. Should be used when a player
     * first comes into range of the cell.
     * @param virtualCell 
     */
    ServerDataCell(){
        
    }
    
    
    public static ServerDataCell loadCellAtWorldCoord(int worldX, int worldY){
        ServerDataCell rVal = new ServerDataCell();
        if(!Globals.serverWorldData.isArena()){ //in other words, if not arena mode
            //if on disk (has already been generated)
            //else create from scratch
            EnvironmentGenerator.generatePlains(rVal.loadedEntities, worldX, worldY, Globals.serverTerrainManager.getRandomizerAtPoint(worldX, worldY));
        }
        rVal.navMesh = NavMeshUtils.createMeshFromChunk(Globals.serverTerrainManager.getChunk(worldX, worldY),Globals.navMeshManager.getBlockerCache().getBlocker(worldX, worldY));
        return rVal;
    }
    
    /**
     * Add a player to the current datacell. This should be done if a player moves
     * into close enough proximity with the cell that they are concerned with
     * the microlevel simulation of the cell. This should _not_ be called if
     * this is the first player coming into range.
     * @param p 
     */
    public void addPlayer(Player p){
        if(!activePlayers.contains(p)){
            activePlayers.add(p);
            serializeStateToPlayer(p);
        }
    }
    
    /**
     * Removes a player from the current datacell. Basically the player has moved
     * far enough away that this cell doesn't concern them anymore.
     * If the number of players for this cell is zero, we should prepare it to be
     * unloaded from memory and converted back into a virtual cell.
     * @param p 
     */
    public void removePlayer(Player p){
        activePlayers.remove(p);
    }
    
    /**
     * This should be used to translate a character from macrolevel simulation to
     * microlevel, datacell based simulation.
     * @param character 
     */
    public void addCharacter(Character character){
        Entity newEntity = new Entity();
        loadedEntities.add(newEntity);
    }
    
    /**
     * This should be used to translate an entity from one data cell to another.
     * @param e 
     */
    public void addEntity(Entity e){
        loadedEntities.add(e);
    }
    
    /**
     * Intention of this function is to entirely remove the entity from microlevel simulation.
     * This should be conbined with code to convert the entity into a tracked macrolevel character.
     * The previous should not be true if you're tracking something inconsequential like
     * a rabbit or something. Then just delete it.
     * @param e 
     */
    public void removeEntity(Entity e){
        loadedEntities.remove(e);
        
    }
    
    /**
     * Broadcast a message to all players within range of this cell.
     * @param message 
     */
    public void broadcastNetworkMessage(NetworkMessage message){
        for(Player player : activePlayers){
            if(player != Globals.serverPlayer){
                player.addMessage(message);
            }
        }
    }
    
    /**
     * Sends the current state of the datacell to the player
     * Commonly, this should be called when a player is added to the cell
     */
    void serializeStateToPlayer(Player player){
        if(player != Globals.serverPlayer){
            for(Entity entity : loadedEntities){
                if(CreatureUtils.isCreature(entity)){
                    CreatureUtils.sendEntityToPlayer(player, entity);
                }
                if(ItemUtils.isItem(entity)){
                    ItemUtils.sendEntityToPlayer(player, entity);
                }
                if(StructureUtils.isStructure(entity)){
                    StructureUtils.sendStructureToPlayer(player, entity);
                }
            }
        }
    }
    
    
    /**
     * Check if player is relevant to cell
     */
    public boolean containsPlayer(Player player){
        return activePlayers.contains(player);
    }
    
}
