package electrosphere.server.terrain.diskmap;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.util.FileUtils;

/**
 * An interface for accessing the disk map of chunk information
 */
public class ChunkDiskMap {

    //The map of world position+chunk type to the file that actually houses that information
    Map<String,String> worldPosFileMap = new HashMap<String,String>();

    /**
     * Constructor
     */
    public ChunkDiskMap(){

    }

    /**
     * Gets a key for a given chunk file based on a world coordinate
     * @param worldX The x component
     * @param worldY The y component
     * @param worldZ The z component
     * @return The key
     */
    private static String getTerrainChunkKey(int worldX, int worldY, int worldZ){
        return worldX + "_" + worldY + "_" + worldZ + "t";
    }

    /**
     * Gets a key for a given chunk file based on a world coordinate
     * @param worldX The x component
     * @param worldY The y component
     * @param worldZ The z component
     * @return The key
     */
    private static String getFluidChunkKey(int worldX, int worldY, int worldZ){
        return worldX + "_" + worldY + "_" + worldZ + "f";
    }

    /**
     * Initializes a diskmap based on a given save name
     * @param saveName The save name
     */
    public void init(String saveName){
        LoggerInterface.loggerEngine.DEBUG("INIT CHUNK MAP " + saveName);
        if(FileUtils.getSaveFile(saveName, "chunk.map").exists()){
            worldPosFileMap = FileUtils.loadObjectFromSavePath(saveName, "chunk.map", Map.class);
            LoggerInterface.loggerEngine.DEBUG("POS FILE MAP: " + worldPosFileMap.keySet());
        } else {
            worldPosFileMap = new HashMap<String,String>();
        }
    }

    /**
     * Saves the disk map to disk
     */
    public void save(){
        FileUtils.serializeObjectToSavePath(Globals.currentSave.getName(), "chunk.map", worldPosFileMap);
    }

    /**
     * Checks if the map contains a given chunk position
     * @param worldX The x component
     * @param worldY The y component
     * @param worldZ The z component
     * @return True if the map contains the chunk, false otherwise
     */
    public boolean containsTerrainAtPosition(int worldX, int worldY, int worldZ){
        return worldPosFileMap.containsKey(getTerrainChunkKey(worldX, worldY, worldZ));
    }

    /**
     * Checks if the map contains a given chunk position
     * @param worldX The x component
     * @param worldY The y component
     * @param worldZ The z component
     * @return True if the map contains the chunk, false otherwise
     */
    public boolean containsFluidAtPosition(int worldX, int worldY, int worldZ){
        return worldPosFileMap.containsKey(getFluidChunkKey(worldX, worldY, worldZ));
    }

    /**
     * Gets the server terrain chunk from disk if it exists, otherwise returns null
     * @param worldX The x coordinate
     * @param worldY The y coordinate
     * @param worldZ The z coordinate
     * @return The server terrain chunk if it exists, null otherwise
     */
    public ServerTerrainChunk getTerrainChunk(int worldX, int worldY, int worldZ){
        LoggerInterface.loggerEngine.INFO("Load chunk " + worldX + " " + worldY + " " + worldZ);
        ServerTerrainChunk rVal = null;
        if(containsTerrainAtPosition(worldX, worldY, worldZ)){
            //read file
            String fileName = worldPosFileMap.get(getTerrainChunkKey(worldX, worldY, worldZ));
            byte[] rawDataCompressed = FileUtils.loadBinaryFromSavePath(Globals.currentSave.getName(), fileName);
            //decompress
            byte[] rawData = null;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            InflaterOutputStream inflaterInputStream = new InflaterOutputStream(out);
            try {
                inflaterInputStream.write(rawDataCompressed);
                inflaterInputStream.flush();
                inflaterInputStream.close();
                rawData = out.toByteArray();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //parse
            if(rawData != null){
                ByteBuffer buffer = ByteBuffer.wrap(rawData);
                FloatBuffer floatView = buffer.asFloatBuffer();
                int DIM = ServerTerrainChunk.CHUNK_DIMENSION;
                float[][][] weights = new float[DIM][DIM][DIM];
                int[][][] values = new int[DIM][DIM][DIM];
                for(int x = 0; x < DIM; x++){
                    for(int y = 0; y < DIM; y++){
                        for(int z = 0; z < DIM; z++){
                            weights[x][y][z] = floatView.get();
                        }
                    }
                }
                IntBuffer intView = buffer.asIntBuffer();
                intView.position(DIM * DIM * DIM);
                for(int x = 0; x < DIM; x++){
                    for(int y = 0; y < DIM; y++){
                        for(int z = 0; z < DIM; z++){
                            values[x][y][z] = intView.get();
                        }
                    }
                }
                rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
            }
        }
        return rVal;
    }

    /**
     * Saves a terrain chunk to disk
     * @param terrainChunk The terrain chunk
     */
    public void saveToDisk(ServerTerrainChunk terrainChunk){
        LoggerInterface.loggerEngine.DEBUG("Save to disk: " + terrainChunk.getWorldX() + " " + terrainChunk.getWorldY() + " " + terrainChunk.getWorldZ());
        //get the file name for this chunk
        String fileName = null;
        String chunkKey = getTerrainChunkKey(terrainChunk.getWorldX(),terrainChunk.getWorldY(),terrainChunk.getWorldZ());
        if(worldPosFileMap.containsKey(chunkKey)){
            fileName = worldPosFileMap.get(chunkKey);
        } else {
            fileName = chunkKey + ".dat";
        }
        //generate binary for the file
        float[][][] weights = terrainChunk.getWeights();
        int[][][] values = terrainChunk.getValues();
        int DIM = ServerTerrainChunk.CHUNK_DIMENSION;
        ByteBuffer buffer = ByteBuffer.allocate(DIM * DIM * DIM * 4 + DIM * DIM * DIM * 4);
        FloatBuffer floatView = buffer.asFloatBuffer();
        for(int x = 0; x < DIM; x++){
            for(int y = 0; y < DIM; y++){
                for(int z = 0; z < DIM; z++){
                    floatView.put(weights[x][y][z]);
                }
            }
        }
        buffer.position(DIM * DIM * DIM * 4);
        IntBuffer intView = buffer.asIntBuffer();
        for(int x = 0; x < DIM; x++){
            for(int y = 0; y < DIM; y++){
                for(int z = 0; z < DIM; z++){
                    intView.put(values[x][y][z]);
                }
            }
        }
        //compress
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        DeflaterOutputStream deflaterInputStream = new DeflaterOutputStream(out);
        try {
            deflaterInputStream.write(buffer.array());
            deflaterInputStream.flush();
            deflaterInputStream.close();
            //write to disk
            FileUtils.saveBinaryToSavePath(Globals.currentSave.getName(), fileName, out.toByteArray());
            //save to the map of filenames
            worldPosFileMap.put(chunkKey,fileName);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
