package electrosphere.server.block.manager;

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

import electrosphere.client.block.BlockChunkCache;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.block.diskmap.ServerBlockChunkDiskMap;

/**
 * A job that fetches a chunk, either by generating it or by reading it from disk
 */
public class ServerBlockChunkGenerationThread 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
     */
    ServerBlockChunkDiskMap chunkDiskMap;

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

    /**
     * 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<BlockChunkData> onLoad;

    /**
     * Creates the chunk generation job
     * @param chunkDiskMap The chunk disk map
     * @param chunkCache The chunk cache on the server
     * @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 ServerBlockChunkGenerationThread(
        ServerBlockChunkDiskMap chunkDiskMap,
        BlockChunkCache chunkCache,
        int worldX, int worldY, int worldZ,
        int stride,
        Consumer<BlockChunkData> onLoad
    ){
        this.chunkDiskMap = chunkDiskMap;
        this.chunkCache = chunkCache;
        this.worldX = worldX;
        this.worldY = worldY;
        this.worldZ = worldZ;
        this.stride = stride;
        this.onLoad = onLoad;
    }

    @Override
    public void run() {
        BlockChunkData chunk = null;
        int i = 0;
        try {
            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.containsBlocksAtPosition(worldX, worldY, worldZ)){
                            chunk = chunkDiskMap.getBlockChunk(worldX, worldY, worldZ);
                        }
                    }
                    //generate if it does not exist
                    if(chunk == null){
                        //TODO: generate from macro-level data
                        chunk = BlockChunkData.allocate();
                        chunk.setHomogenousValue(0);
                        // if(worldX == 0 && worldY == 0 && worldZ == 0){
                        //     chunk.setHomogenousValue(BlockChunkData.NOT_HOMOGENOUS);
                        //     for(int x = 0; x < 16; x++){
                        //         for(int y = 0; y < 16; y++){
                        //             for(int z = 0; z < 16; z++){
                        //                 chunk.setType(x, y, z, 1);
                        //             }
                        //         }
                        //     }
                        // }
                    }
                    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);
        }
    }
    
}
