package electrosphere.server.terrain.manager;

import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.diskmap.ChunkDiskMap;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;

/**
 * A job that fetches a chunk, either by generating it or by reading it from disk
 */
public class ChunkGenerationThread implements Runnable {

    /**
     * The number of milliseconds to wait per iteration
     */
    static final int WAIT_TIME_MS = 2;

    /**
     * The maximum number of iterations to wait before failing
     */
    static final int MAX_TIME_TO_WAIT = 10;

    /**
     * The chunk disk map
     */
    ChunkDiskMap chunkDiskMap;

    /**
     * The chunk cache on the server
     */
    ServerChunkCache chunkCache;

    /**
     * The chunk generator
     */
    ChunkGenerator chunkGenerator;

    /**
     * The world x coordinate
     */
    int worldX;

    /**
     * The world y coordinate
     */
    int worldY;

    /**
     * The world z coordinate
     */
    int worldZ;

    /**
     * The stride of the data
     */
    int stride;
    
    /**
     * The work to do once the chunk is available
     */
    Consumer<ServerTerrainChunk> onLoad;

    /**
     * Creates the chunk generation job
     * @param chunkDiskMap The chunk disk map
     * @param chunkCache The chunk cache on the server
     * @param chunkGenerator The chunk generator
     * @param worldX The world x coordinate
     * @param worldY The world y coordinate
     * @param worldZ The world z coordinate
     * @param stride The stride of the data
     * @param onLoad The work to do once the chunk is available
     */
    public ChunkGenerationThread(
        ChunkDiskMap chunkDiskMap,
        ServerChunkCache chunkCache,
        ChunkGenerator chunkGenerator,
        int worldX, int worldY, int worldZ,
        int stride,
        Consumer<ServerTerrainChunk> onLoad
    ){
        this.chunkDiskMap = chunkDiskMap;
        this.chunkCache = chunkCache;
        this.chunkGenerator = chunkGenerator;
        this.worldX = worldX;
        this.worldY = worldY;
        this.worldZ = worldZ;
        this.stride = stride;
        this.onLoad = onLoad;
    }

    @Override
    public void run() {
        try {
            int i = 0;
            ServerTerrainChunk chunk = null;
            while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
                if(chunkCache.containsChunk(worldX, worldY, worldZ, stride)){
                    chunk = chunkCache.get(worldX, worldY, worldZ, stride);
                } else {
                    //pull from disk if it exists
                    if(chunkDiskMap != null){
                        if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
                            chunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ);
                        }
                    }
                    //generate if it does not exist
                    if(chunk == null){
                        chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, stride);
                    }
                    if(chunk != null){
                        chunkCache.add(worldX, worldY, worldZ, stride, chunk);
                    }
                }
                if(chunk == null){
                    try {
                        TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                i++;
            }
            if(i >= MAX_TIME_TO_WAIT){
                throw new Error("Failed to resolve chunk!");
            }
            this.onLoad.accept(chunk);
        } catch (Error e){
            LoggerInterface.loggerEngine.ERROR(e);
        } catch(Exception e){
            LoggerInterface.loggerEngine.ERROR(e);
        }
    }
    
}
