package electrosphere.server.datacell;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;

import org.joml.Vector3d;
import org.joml.Vector3i;

import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.server.player.Player;
import electrosphere.net.server.protocol.TerrainProtocol;
import electrosphere.server.content.ServerContentManager;
import electrosphere.server.datacell.interfaces.DataCellManager;
import electrosphere.server.datacell.interfaces.VoxelCellManager;
import electrosphere.server.datacell.physics.PhysicsDataCell;
import electrosphere.server.fluid.manager.ServerFluidChunk;
import electrosphere.server.fluid.manager.ServerFluidManager;
import electrosphere.server.terrain.manager.ServerTerrainManager;
import electrosphere.server.terrain.manager.ServerTerrainChunk;

/**
 * Implementation of DataCellManager that lays out cells in a logical grid (array). Useful for eg 3d terrain gridded world.
 */
public class GriddedDataCellManager implements DataCellManager, VoxelCellManager {

    /**
     * The minimum grid size allowed
     */
    public static final int MIN_GRID_SIZE = 1;

    /**
     * The max grid size allowed
     */
    public static final int MAX_GRID_SIZE = 10;

    /**
     * Tracks whether this manager has been flagged to unload cells or not
     */
    boolean unloadCells = true;

    //these are going to be the natural ground grid of data cells, but we're going to have more than this
    Map<String,ServerDataCell> groundDataCells = new HashMap<String,ServerDataCell>();
    Map<ServerDataCell,Vector3i> cellPositionMap = new HashMap<ServerDataCell,Vector3i>();
    //Map of server data cell to the number of frames said cell has had no players
    Map<ServerDataCell,Integer> cellPlayerlessFrameMap = new HashMap<ServerDataCell,Integer>();
    //The number of frames without players that must pass before a server data cell is unloaded
    static final int UNLOAD_FRAME_THRESHOLD = 100;
    //loaded cells
    Semaphore loadedCellsLock = new Semaphore(1);
    Set<ServerDataCell> loadedCells = new CopyOnWriteArraySet<ServerDataCell>();
    //parent realm
    Realm parent;
    //the world data of the parent
    ServerWorldData serverWorldData;
    //Manager for terrain for this particular cell manager
    ServerTerrainManager serverTerrainManager;
    //manager for fluids for this particular cell manager
    ServerFluidManager serverFluidManager;
    //lock for terrain editing
    Semaphore terrainEditLock = new Semaphore(1);
    //manager for getting entities to fill in a cell
    ServerContentManager serverContentManager;
    
    /**
     * Constructor
     * @param parent The gridded data cell manager's parent realm
     */
    public GriddedDataCellManager(
        Realm parent
    ) {
        this.parent = parent;
        this.serverWorldData = this.parent.getServerWorldData();
        this.serverTerrainManager = serverWorldData.getServerTerrainManager();
        this.serverFluidManager = serverWorldData.getServerFluidManager();
        this.serverContentManager = this.parent.getServerContentManager();

        //Assert the gridded data cell manager was given good data
        if(
            this.parent == null ||
            this.serverWorldData == null ||
            this.serverTerrainManager == null ||
            this.serverFluidManager == null ||
            this.serverContentManager == null
        ){
            throw new IllegalStateException("Tried to create a GriddedDataCellManager with invalid parameters " +
            this.parent + " " +
            this.serverWorldData + " " +
            this.serverTerrainManager + " " +
            this.serverFluidManager + " " +
            this.serverContentManager + " "
            );
        }
    }
    
    /**
     * Adds a player to the realm that this manager controls. Should do this intelligently based on the player's location
     * @param player The player
     */
    public void addPlayerToRealm(Player player){
        Globals.realmManager.setPlayerRealm(player, parent);
        int playerSimulationRadius = player.getSimulationRadius();
        Vector3i worldPos = player.getWorldPos();
        for(int x = worldPos.x - playerSimulationRadius; x < worldPos.x + playerSimulationRadius + 1; x++){
            for(int y = worldPos.y - playerSimulationRadius; y < worldPos.y + playerSimulationRadius + 1; y++){
                for(int z = worldPos.z - playerSimulationRadius; z < worldPos.z + playerSimulationRadius + 1; z++){
                    if(
                            x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() &&
                            y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() &&
                            z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete()
                            ){
                        Vector3i targetPos = new Vector3i(x,y,z);
                        LoggerInterface.loggerEngine.DEBUG("GriddedDataCellManager: Add player to " + x + " " + y + " " + z);
                        if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
                            groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
                        } else {
                            LoggerInterface.loggerEngine.DEBUG("Creating new cell @ " + x + " " + y + " " + z);
                            //create data cell
                            createServerDataCell(targetPos);
                            ///generates physics for the cell in a dedicated thread then finally registers
                            runPhysicsGenerationThread(targetPos);
                            //add to loaded cells
                            loadedCellsLock.acquireUninterruptibly();
                            loadedCells.add(groundDataCells.get(getServerDataCellKey(targetPos)));
                            cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0);
                            loadedCellsLock.release();
                            //generate/handle content for new server data cell
                            
                            //add player
                            groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
                        }
                    }
                }
            }
        }
    }
    
    /**
     * Moves a player to a new position
     * @param player The player
     * @param newPosition The new position
     */
    public void movePlayer(Player player, Vector3i newPosition){
        int playerSimulationRadius = player.getSimulationRadius();
        Vector3i oldPosition = player.getWorldPos();
        player.setWorldPos(newPosition);
    //    System.out.println("=======" + "SET" + newX + " " + newY + " FROM " + oldX + " " + oldY + "========");
        // int removals = 0;
        // int additions = 0;
        for(int x = oldPosition.x - playerSimulationRadius; x < oldPosition.x + playerSimulationRadius + 1; x++){
            for(int y = oldPosition.y - playerSimulationRadius; y < oldPosition.y + playerSimulationRadius + 1; y++){
                for(int z = oldPosition.z - playerSimulationRadius; z < oldPosition.z + playerSimulationRadius + 1; z++){
                    if(
                            x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() && 
                            y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() &&
                            z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete() &&
                            (
                                x < newPosition.x - playerSimulationRadius || 
                                x > newPosition.x + playerSimulationRadius || 
                                y < newPosition.y - playerSimulationRadius || 
                                y > newPosition.y + playerSimulationRadius ||
                                z < newPosition.z - playerSimulationRadius || 
                                z > newPosition.z + playerSimulationRadius
                            )
                            ){
                        Vector3i targetPos = new Vector3i(x,y,z);
                        if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
                            if(groundDataCells.get(getServerDataCellKey(targetPos)).containsPlayer(player)){
                                // removals++;
                                groundDataCells.get(getServerDataCellKey(targetPos)).removePlayer(player);
                            }
                        }
                    }
                }
            }
        }
        for(int x = newPosition.x - playerSimulationRadius; x < newPosition.x + playerSimulationRadius + 1; x++){
            for(int y = newPosition.y - playerSimulationRadius; y < newPosition.y + playerSimulationRadius + 1; y++){
                for(int z = newPosition.x - playerSimulationRadius; z < newPosition.z + playerSimulationRadius + 1; z++){
                    if(
                            x >= 0 && x < this.serverWorldData.getWorldSizeDiscrete() &&
                            y >= 0 && y < this.serverWorldData.getWorldSizeDiscrete() &&
                            z >= 0 && z < this.serverWorldData.getWorldSizeDiscrete() &&
                            (
                                x < oldPosition.x - playerSimulationRadius || 
                                x > oldPosition.x + playerSimulationRadius || 
                                y < oldPosition.y - playerSimulationRadius || 
                                y > oldPosition.y + playerSimulationRadius ||
                                z < oldPosition.z - playerSimulationRadius || 
                                z > oldPosition.z + playerSimulationRadius
                            )
                            ){
                        Vector3i targetPos = new Vector3i(x,y,z);
    //                    System.out.println("Add player to " + x + " " + y);
                        if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){
                            groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
                        } else {
                            //create data cell
                            createServerDataCell(targetPos);
                            //generates physics for the cell in a dedicated thread then finally registers
                            runPhysicsGenerationThread(targetPos);
                            //add to loaded cells
                            loadedCellsLock.acquireUninterruptibly();
                            loadedCells.add(groundDataCells.get(getServerDataCellKey(targetPos)));
                            cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0);
                            loadedCellsLock.release();
                            //add player
                            groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player);
                        }
                        // additions++;
                    } else {
    //                    System.out.println(x + "\t" + (oldX - playerSimulationRadius) + "\t" + (oldX + playerSimulationRadius));
    //                    System.out.println(y + "\t" + (oldY - playerSimulationRadius) + "\t" + (oldY + playerSimulationRadius));
                    }
                }
            }
        }
    //    System.out.println("removals: " + removals + "\tadditions: " + additions);
    }

    /**
     * Creates physics entities when new data cell being created
     */
    private void createTerrainPhysicsEntities(Vector3i worldPos){
        PhysicsDataCell cell = PhysicsDataCell.createPhysicsCell(parent, worldPos);
        cell.generatePhysics();
    }
    
    /**
     * For every player, looks at their entity and determines what data cell they should be considered inside of
     * @return True if the player changed cell, false otherwise
     */
    public boolean updatePlayerPositions(){
        boolean playerChangedChunk = false;
        for(Player player : Globals.playerManager.getPlayers()){
            Entity playerEntity = player.getPlayerEntity();
            if(playerEntity != null && !parent.getLoadingDataCell().containsPlayer(player)){
                Vector3d position = EntityUtils.getPosition(playerEntity);
                int currentWorldX = parent.getServerWorldData().convertRealToChunkSpace(position.x);
                int currentWorldY = parent.getServerWorldData().convertRealToChunkSpace(position.y);
                int currentWorldZ = parent.getServerWorldData().convertRealToChunkSpace(position.z);
                if(currentWorldX != player.getWorldPos().x || currentWorldY != player.getWorldPos().y || currentWorldZ != player.getWorldPos().z){
                    movePlayer(player,new Vector3i(currentWorldX,currentWorldY,currentWorldZ));
                    playerChangedChunk = true;
                }
            }
        }
        return playerChangedChunk;
    }

    //Used for cleaning server data cells no longer in use from the realm
    Set<ServerDataCell> toCleanQueue = new HashSet<ServerDataCell>();
    /**
     * Unloads all chunks that haven't had players in them for a set amount of time
     */
    public void unloadPlayerlessChunks(){
        if(this.unloadCells){
            //TODO: improve to make have less performance impact
            for(ServerDataCell cell : loadedCells){
                loadedCellsLock.acquireUninterruptibly();
                if(cell.getPlayers().size() < 1){
                    int frameCount = cellPlayerlessFrameMap.get(cell) + 1;
                    cellPlayerlessFrameMap.put(cell,frameCount);
                    if(frameCount > UNLOAD_FRAME_THRESHOLD){
                        toCleanQueue.add(cell);
                    }
                } else {
                    if(cellPlayerlessFrameMap.get(cell) > 0){
                        cellPlayerlessFrameMap.put(cell, 0);
                    }
                }
                loadedCellsLock.release();
            }
            for(ServerDataCell cell : toCleanQueue){
                parent.deregisterCell(cell);
                loadedCells.remove(cell);
                Vector3i worldPos = getCellWorldPosition(cell);
                String key = getServerDataCellKey(worldPos);
                groundDataCells.remove(key);
                //offload all entities in cell to chunk file
                serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList());
                //clear all entities in cell
                for(Entity entity : cell.getScene().getEntityList()){
                    EntityUtils.cleanUpEntity(entity);
                }
                //save terrain to disk
                serverTerrainManager.savePositionToDisk(worldPos);
            }
            toCleanQueue.clear();
        }
    }

    /**
     * Get data cell at a given real point in this realm
     * @param point The real point
     * @return Either the data cell if found, or null if not found
     */
    public ServerDataCell getDataCellAtPoint(Vector3d point){
        ServerDataCell rVal = null;
        int worldX = parent.getServerWorldData().convertRealToChunkSpace(point.x);
        int worldY = parent.getServerWorldData().convertRealToChunkSpace(point.y);
        int worldZ = parent.getServerWorldData().convertRealToChunkSpace(point.z);
        Vector3i worldPos = new Vector3i(worldX,worldY,worldZ);
        if(
            //in bounds of array
            worldX >= 0 && worldX < this.serverWorldData.getWorldSizeDiscrete() &&
            worldY >= 0 && worldY < this.serverWorldData.getWorldSizeDiscrete() &&
            worldZ >= 0 && worldZ < this.serverWorldData.getWorldSizeDiscrete() &&
            //isn't null
            groundDataCells.get(getServerDataCellKey(worldPos)) != null
            ){
                LoggerInterface.loggerEngine.DEBUG("Get server data cell key: " + getServerDataCellKey(worldPos));
            rVal = groundDataCells.get(getServerDataCellKey(worldPos));
        } else {
            LoggerInterface.loggerEngine.DEBUG("Failed to get server data cell at: " + worldPos);
        }
        return rVal;
    }

    /**
     * Tries to create a data cell at a given real point
     * @param point The real point
     * @return The data cell if created, null otherwise
     */
    public ServerDataCell tryCreateCellAtPoint(Vector3d point){
        int worldX = parent.getServerWorldData().convertRealToChunkSpace(point.x);
        int worldY = parent.getServerWorldData().convertRealToChunkSpace(point.y);
        int worldZ = parent.getServerWorldData().convertRealToChunkSpace(point.z);
        Vector3i worldPos = new Vector3i(worldX,worldY,worldZ);
        return tryCreateCellAtPoint(worldPos);
    }

    /**
     * Tries to create a data cell at a given discrete point
     * @param point The discrete point
     * @return The data cell if created, null otherwise
     */
    public ServerDataCell tryCreateCellAtPoint(Vector3i worldPos){
        if(
            //in bounds of array
            worldPos.x >= 0 && worldPos.x < this.serverWorldData.getWorldSizeDiscrete() &&
            worldPos.y >= 0 && worldPos.y < this.serverWorldData.getWorldSizeDiscrete() &&
            worldPos.z >= 0 && worldPos.z < this.serverWorldData.getWorldSizeDiscrete() &&
            //isn't null
            groundDataCells.get(getServerDataCellKey(worldPos)) == null
            ){
            //create data cell
            createServerDataCell(worldPos);
            //generates physics for the cell in a dedicated thread then finally registers
            runPhysicsGenerationThread(worldPos);
            //add to loaded cells
            loadedCellsLock.acquireUninterruptibly();
            loadedCells.add(groundDataCells.get(getServerDataCellKey(worldPos)));
            cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(worldPos)),0);
            loadedCellsLock.release();
        }
        return groundDataCells.get(getServerDataCellKey(worldPos));
    }

    /**
     * Gets a data cell at a given world position
     * @param position The world position
     * @return The data cell if found, null otherwise
     */
    public ServerDataCell getCellAtWorldPosition(Vector3i position){
        if(
            //in bounds of array
            position.x >= 0 && position.x < this.serverWorldData.getWorldSizeDiscrete() &&
            position.y >= 0 && position.y < this.serverWorldData.getWorldSizeDiscrete() &&
            position.z >= 0 && position.z < this.serverWorldData.getWorldSizeDiscrete() &&
            //isn't null
            groundDataCells.get(getServerDataCellKey(position)) != null
            ){
            return groundDataCells.get(getServerDataCellKey(position));
        }
        return null;
    }


    /**
     * Calls the simulate function on all loaded cells
     */
    public void simulate(){
        loadedCellsLock.acquireUninterruptibly();
        for(ServerDataCell cell : loadedCells){
            if(Globals.microSimulation != null && Globals.microSimulation.isReady()){
                Globals.microSimulation.simulate(cell);
            }

            //simulate fluid
            Vector3i cellPos = this.getCellWorldPosition(cell);
            boolean update = this.serverFluidManager.simulate(cellPos.x,cellPos.y,cellPos.z);
            if(update){
                rebroadcastFluidChunk(cellPos);
            }
        }
        loadedCellsLock.release();
        updatePlayerPositions();
    }


    /**
     * Gets the server terrain manager for this realm if it exists
     * @return The server terrain manager if it exists, null otherwise
     */
    public ServerTerrainManager getServerTerrainManager(){
        return serverTerrainManager;
    }

    /**
     * Gets the server fluid manager for this realm if it exists
     * @return The server fluid manager if it exists, null otherwise
     */
    public ServerFluidManager getServerFluidManager(){
        return serverFluidManager;
    }

    /**
     * Runs code to generate physics entities and register cell in a dedicated thread.
     * Because cell hasn't been registered yet, no simulation is performed until the physics is created.
     * @param worldPos
     */
    private void runPhysicsGenerationThread(Vector3i worldPos){
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                //create physics entities
                createTerrainPhysicsEntities(worldPos);
                //set ready
                groundDataCells.get(getServerDataCellKey(worldPos)).setReady(true);
            }
        });
        groundDataCells.get(getServerDataCellKey(worldPos)).setReady(false);
        thread.start();
    }

    /**
     * Gets the key in the groundDataCells map for the data cell at the provided world pos
     * @param worldPos The position in world coordinates of the server data cell
     * @return The server data cell if it exists, otherwise null
     */
    private String getServerDataCellKey(Vector3i worldPos){
        return worldPos.x + "_" + worldPos.y + "_" + worldPos.z;
    }

    /**
     * Registers a server data cell with the internal datastructure for tracking them
     * @param key The key to register the cell at
     * @param cell The cell itself
     */
    private ServerDataCell createServerDataCell(Vector3i worldPos){
        ServerDataCell rVal = parent.createNewCell();
        String cellKey = getServerDataCellKey(worldPos);
        groundDataCells.put(cellKey,rVal);
        LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey);
        cellPositionMap.put(rVal,new Vector3i(worldPos));
        serverContentManager.generateContentForDataCell(parent, worldPos, rVal, cellKey);
        return rVal;
    }

    @Override
    /**
     * Gets the weight of a single voxel at a position
     * @param worldPosition The position in world coordinates of the chunk to grab data from
     * @param voxelPosition The position in voxel coordinates (local/relative to the chunk) to get voxel values from
     * @return The weight of the described voxel
     */
    public float getVoxelWeightAtLocalPosition(Vector3i worldPosition, Vector3i voxelPosition) {
        return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z).getWeights()[voxelPosition.x][voxelPosition.y][voxelPosition.z];
    }

    @Override
    /**
     * Gets the type of a single voxel at a position
     * @param worldPosition The position in world coordinates of the chunk to grab data from
     * @param voxelPosition The position in voxel coordinates (local/relative to the chunk) to get voxel values from
     * @return The type of the described voxel
     */
    public int getVoxelTypeAtLocalPosition(Vector3i worldPosition, Vector3i voxelPosition) {
        return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z).getValues()[voxelPosition.x][voxelPosition.y][voxelPosition.z];
    }

    @Override
    /**
     * Gets the chunk data at a given world position
     * @param worldPosition The position in world coordinates
     * @return The ServerTerrainChunk of data at that position, or null if it is out of bounds or otherwise doesn't exist
     */
    public ServerTerrainChunk getChunkAtPosition(Vector3i worldPosition) {
        return serverTerrainManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z);
    }

    @Override
    /**
     * Edits a single voxel
     * @param worldPosition The world position of the chunk to edit
     * @param voxelPosition The voxel position of the voxel to edit
     * @param weight The weight to set the voxel to
     * @param type The type to set the voxel to
     */
    public void editChunk(Vector3i worldPosition, Vector3i voxelPosition, float weight, int type) {
        terrainEditLock.acquireUninterruptibly();
        //update terrain
        serverTerrainManager.deformTerrainAtLocationToValue(worldPosition, voxelPosition, weight, type);
        //broadcast update to terrain
        ServerDataCell cell = groundDataCells.get(getServerDataCellKey(worldPosition));
        if(cell != null){
            cell.broadcastNetworkMessage(TerrainMessage.constructUpdateVoxelMessage(
                worldPosition.x, worldPosition.y, worldPosition.z, 
                voxelPosition.x, voxelPosition.y, voxelPosition.z, 
                weight, type));
        }
        terrainEditLock.release();
    }


    /**
     * Gets the world position of a given data cell
     * @param cell The data cell
     * @return The world position
     */
    public Vector3i getCellWorldPosition(ServerDataCell cell){
        return cellPositionMap.get(cell);
    }

    @Override
    /**
     * Gets the fluid chunk at a given position
     */
    public ServerFluidChunk getFluidChunkAtPosition(Vector3i worldPosition) {
        return serverFluidManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z);
    }

    /**
     * Loads all cells
     */
    public void loadAllCells(){
        this.unloadCells = false;
        for(int x = 0; x < this.serverWorldData.getWorldSizeDiscrete(); x++){
            for(int y = 0; y < this.serverWorldData.getWorldSizeDiscrete(); y++){
                for(int z = 0; z < this.serverWorldData.getWorldSizeDiscrete(); z++){
                    this.tryCreateCellAtPoint(new Vector3i(x,y,z));
                }
            }
        }
    }

    /**
     * Rebroadcasts the fluid cell at a given position
     * @param worldPosition the world position
     */
    private void rebroadcastFluidChunk(Vector3i worldPosition){
        ServerDataCell cell = getCellAtWorldPosition(worldPosition);
        ServerFluidChunk chunk = getFluidChunkAtPosition(worldPosition);
        cell.broadcastNetworkMessage(
            TerrainMessage.constructupdateFluidDataMessage(worldPosition.x, worldPosition.y, worldPosition.z, TerrainProtocol.constructFluidByteBuffer(chunk).array())
        );
    }

    @Override
    public void save(String saveName) {
        for(ServerDataCell cell : loadedCells){
            String key = this.getServerDataCellKey(this.getCellWorldPosition(cell));
            //offload all entities in cell to chunk file
            serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList());
        }
    }

    @Override
    public Vector3d guaranteePositionIsInBounds(Vector3d positionToTest) {
        Vector3d returnPos = new Vector3d(positionToTest);
        if(positionToTest.x < 0){
            returnPos.x = 0;
        }
        if(positionToTest.x >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){
            returnPos.x = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1;
        }
        if(positionToTest.y < 0){
            returnPos.y = 0;
        }
        if(positionToTest.y >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){
            returnPos.y = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1;
        }
        if(positionToTest.z < 0){
            returnPos.z = 0;
        }
        if(positionToTest.z >= parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete())){
            returnPos.z = parent.getServerWorldData().convertChunkToRealSpace(parent.getServerWorldData().getWorldSizeDiscrete()) - 1;
        }
        return returnPos;
    }

}
