package electrosphere.game.client.terrain.manager;

import electrosphere.game.client.terrain.cache.ClientTerrainCache;
import electrosphere.game.client.terrain.cache.LoadingChunk;
import electrosphere.game.client.terrain.cache.LoadingChunkCache;
import electrosphere.game.client.world.ClientWorldData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.WorldMessage;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ClientTerrainManager {
    
    //queues messages from server
    CopyOnWriteArrayList<WorldMessage> messageQueue = new CopyOnWriteArrayList();
    
    
    //caches chunks from server
    static final int CACHE_SIZE = 50;
    
    //used for caching the macro values
    ClientTerrainCache terrainCache;
    
    //used for caching loading chunks that are still streaming over the net
    LoadingChunkCache loadingChunkCache;
    
    
    ClientWorldData clientWorldData;
    
    
    public ClientTerrainManager(ClientWorldData clientWorldData){
        this.clientWorldData = clientWorldData;
        terrainCache = new ClientTerrainCache(CACHE_SIZE,clientWorldData);
        loadingChunkCache = new LoadingChunkCache();
    }
    
    
    public void handleMessages(){
        List<WorldMessage> bouncedMessages = new LinkedList();
        for(WorldMessage message : messageQueue){
            messageQueue.remove(message);
            switch(message.getMessageSubtype()){
                case MACROVALUE:
//                    terrainCache.addChunkValuesToCache(message.getlocationX(), message.getlocationY(), macroValues, randomizer);
                    if(loadingChunkCache.containsKey(loadingChunkCache.getKey(message.getworldX(), message.getworldY()))){
                        float[][] macroValues = new float[5][5];
                        long[][] randomizer = new long[5][5];
                        //macro values
                        macroValues[0][0] = message.getmacroValue00();
                        macroValues[0][1] = message.getmacroValue01();
                        macroValues[0][2] = message.getmacroValue02();
                        macroValues[0][3] = message.getmacroValue03();
                        macroValues[0][4] = message.getmacroValue04();
                        macroValues[1][0] = message.getmacroValue10();
                        macroValues[1][1] = message.getmacroValue11();
                        macroValues[1][2] = message.getmacroValue12();
                        macroValues[1][3] = message.getmacroValue13();
                        macroValues[1][4] = message.getmacroValue14();
                        macroValues[2][0] = message.getmacroValue20();
                        macroValues[2][1] = message.getmacroValue21();
                        macroValues[2][2] = message.getmacroValue22();
                        macroValues[2][3] = message.getmacroValue23();
                        macroValues[2][4] = message.getmacroValue24();
                        macroValues[3][0] = message.getmacroValue30();
                        macroValues[3][1] = message.getmacroValue31();
                        macroValues[3][2] = message.getmacroValue32();
                        macroValues[3][3] = message.getmacroValue33();
                        macroValues[3][4] = message.getmacroValue34();
                        macroValues[4][0] = message.getmacroValue40();
                        macroValues[4][1] = message.getmacroValue41();
                        macroValues[4][2] = message.getmacroValue42();
                        macroValues[4][3] = message.getmacroValue43();
                        macroValues[4][4] = message.getmacroValue44();
                        //randomizer
                        randomizer[0][0] = message.getrandomizerValue00();
                        randomizer[0][1] = message.getrandomizerValue01();
                        randomizer[0][2] = message.getrandomizerValue02();
                        randomizer[0][3] = message.getrandomizerValue03();
                        randomizer[0][4] = message.getrandomizerValue04();
                        randomizer[1][0] = message.getrandomizerValue10();
                        randomizer[1][1] = message.getrandomizerValue11();
                        randomizer[1][2] = message.getrandomizerValue12();
                        randomizer[1][3] = message.getrandomizerValue13();
                        randomizer[1][4] = message.getrandomizerValue14();
                        randomizer[2][0] = message.getrandomizerValue20();
                        randomizer[2][1] = message.getrandomizerValue21();
                        randomizer[2][2] = message.getrandomizerValue22();
                        randomizer[2][3] = message.getrandomizerValue23();
                        randomizer[2][4] = message.getrandomizerValue24();
                        randomizer[3][0] = message.getrandomizerValue30();
                        randomizer[3][1] = message.getrandomizerValue31();
                        randomizer[3][2] = message.getrandomizerValue32();
                        randomizer[3][3] = message.getrandomizerValue33();
                        randomizer[3][4] = message.getrandomizerValue34();
                        randomizer[4][0] = message.getrandomizerValue40();
                        randomizer[4][1] = message.getrandomizerValue41();
                        randomizer[4][2] = message.getrandomizerValue42();
                        randomizer[4][3] = message.getrandomizerValue43();
                        randomizer[4][4] = message.getrandomizerValue44();
                        LoadingChunk inProgressChunk = loadingChunkCache.fetch(loadingChunkCache.getKey(message.getworldX(), message.getworldY()));
                        inProgressChunk.addMacroValues(macroValues);
                        inProgressChunk.addRandomizer(randomizer);
                        inProgressChunk.incrementMessageCount();
                    } else {
                        bouncedMessages.add(message);
                    }
                    break;
                case CHUNKLOADSTART:
                    LoadingChunk newChunk = new LoadingChunk(message.getworldX(),message.getworldY(),(int)message.getvalue(),clientWorldData);
                    loadingChunkCache.addLoadingChunkToCache(newChunk);
                    newChunk.incrementMessageCount();
                    break;
                case HEIGHTMAPMODIFICATION:
                    if(loadingChunkCache.containsKey(loadingChunkCache.getKey(message.getworldX(), message.getworldY()))){
                        LoadingChunk inProgressChunk = loadingChunkCache.fetch(loadingChunkCache.getKey(message.getworldX(), message.getworldY()));
                        inProgressChunk.addModification(message.getworldX(), message.getworldY(), message.getlocationX(), message.getlocationY(), message.getvalue());
                        inProgressChunk.incrementMessageCount();
                    } else {
                        bouncedMessages.add(message);
                    }
                    break;
                default:
                    LoggerInterface.loggerEngine.WARNING("ClientTerrainManager: unhandled network message of type" + message.getMessageSubtype());
                    break;
            }
        }
        for(WorldMessage message : bouncedMessages){
            messageQueue.add(message);
        }
    }
    
    public void attachWorldMessage(WorldMessage message){
        messageQueue.add(message);
    }
    
    public boolean containsHeightmapAtDiscretePoint(int x, int y){
        return terrainCache.containsHeightmapAtChunkPoint(x, y);
    }
    
    public boolean containsHeightmapAtRealPoint(double x, double z){
        return terrainCache.containsHeightmapAtChunkPoint(clientWorldData.convertRealToChunkSpace(x), clientWorldData.convertRealToChunkSpace(z));
    }
    
    public float[][] getHeightmapAtPoint(int x, int y){
        return terrainCache.getHeightmapAtChunkPoint(x, y);
    }
    
    
    public double getHeightAtPosition(double x, double y){
        //get chunk coordinate space of input x,y
        int chunkX = (int)Math.floor(x / clientWorldData.getDynamicInterpolationRatio());
        int chunkY = (int)Math.floor(y / clientWorldData.getDynamicInterpolationRatio());
        //get local coordinate space of input x,y
        double localX = x - chunkX * clientWorldData.getDynamicInterpolationRatio();
        double localY = y - chunkY * clientWorldData.getDynamicInterpolationRatio();
        //get chunk elevation map
        float[][] chunkElevationMap = getHeightmapAtPoint(chunkX,chunkY);
        //floored variants of local values
        int localXf = (int)Math.floor(localX);
        int localYf = (int)Math.floor(localY);
        
        /*
        Average some inner value.
        
        01            11
           0.3 0.4 0.5
           0.1 0.2 0.3
        00            10
        */
        //interp elevation from map
        float elevation00 = chunkElevationMap[(int)localX+0][(int)localY+0];
        float elevation10 = chunkElevationMap[(int)localX+1][(int)localY+0];
        float elevation01 = chunkElevationMap[(int)localX+0][(int)localY+1];
        float elevation11 = chunkElevationMap[(int)localX+1][(int)localY+1];
        
        double rVal = 
                (1-(localX-localXf))*(1-(localY-localYf)) * elevation00 +
                (  (localX-localXf))*(1-(localY-localYf)) * elevation10 +
                (1-(localX-localXf))*(  (localY-localYf)) * elevation01 +
                (  (localX-localXf))*(  (localY-localYf)) * elevation11
                ;
        
        return rVal;
    }
    
    public float[][] getTextureMapAtPoint(int x, int y){
        float[][] rVal = new float[clientWorldData.getDynamicInterpolationRatio()][clientWorldData.getDynamicInterpolationRatio()];
        rVal[1][1] = 1;
        rVal[2][1] = 1;
        rVal[3][1] = 1;
        rVal[4][1] = 1;
        rVal[5][1] = 1;
        rVal[5][2] = 1;
        rVal[6][1] = 1;
        rVal[6][2] = 1;
        return rVal;
    }
    
    public void ejectLoadedChunks(){
        List<LoadingChunk> chunksToEject = new LinkedList();
        for(LoadingChunk chunk : loadingChunkCache.getChunks()){
            if(chunk.isComplete()){
                float[][] heightMap = chunk.exportFloats();
                terrainCache.addFloatsToCache(chunk.getWorldX(), chunk.getWorldY(), heightMap);
                chunksToEject.add(chunk);
            }
        }
        for(LoadingChunk loadedChunk : chunksToEject){
            loadingChunkCache.remove(loadedChunk);
        }
    }
    
}
