package electrosphere.net.server.protocol;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.joml.Vector3d;

import electrosphere.engine.Globals;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.server.ServerConnectionHandler;
import electrosphere.net.server.player.Player;
import electrosphere.net.template.ServerProtocolTemplate;
import electrosphere.server.datacell.Realm;
import electrosphere.server.fluid.manager.ServerFluidChunk;
import electrosphere.server.terrain.editing.TerrainEditing;
import electrosphere.server.terrain.manager.ServerTerrainChunk;

/**
 * Server handling for terrain network messages
 */
public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {

    @Override
    public TerrainMessage handleAsyncMessage(ServerConnectionHandler connectionHandler, TerrainMessage message) {
        return message;
    }

    @Override
    public void handleSyncMessage(ServerConnectionHandler connectionHandler, TerrainMessage message) {
        switch(message.getMessageSubtype()){
            case REQUESTMETADATA:
                sendWorldMetadata(connectionHandler);
                break;
            case REQUESTCHUNKDATA:
                sendWorldSubChunk(connectionHandler, 
                message.getworldX(), message.getworldY(), message.getworldZ()
                );
                break;
            case REQUESTEDITVOXEL: {
                attemptTerrainEdit(connectionHandler, message);
            } break;
            case REQUESTUSETERRAINPALETTE: {
                attemptUseTerrainEditPalette(connectionHandler, message);
            } break;
            case REQUESTFLUIDDATA: {
                sendWorldFluidSubChunk(connectionHandler,
                message.getworldX(), message.getworldY(), message.getworldZ()
                );
            } break;
            //all ignored message types
            case UPDATEFLUIDDATA:
            case RESPONSEMETADATA:
            case SPAWNPOSITION:
            case UPDATEVOXEL:
            case SENDCHUNKDATA:
            case SENDFLUIDDATA:
            case REQUESTREDUCEDCHUNKDATA:
            case SENDREDUCEDCHUNKDATA:
            //silently ignore
            break;
        }
    }

    /**
     * Sends a subchunk to the client
     * @param connectionHandler The connection handler
     * @param worldX the world x
     * @param worldY the world y
     * @param worldZ the world z
     */
    static void sendWorldSubChunk(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){
        /*
        int locationX,
        int locationY,
        float macroValue00,
        float macroValue01,
        float macroValue02,
        float macroValue10,
        float macroValue11,
        float macroValue12,
        float macroValue20,
        float macroValue21,
        float macroValue22,
        long randomizerValue00,
        long randomizerValue01,
        long randomizerValue02,
        long randomizerValue10,
        long randomizerValue11,
        long randomizerValue12,
        long randomizerValue20,
        long randomizerValue21,
        long randomizerValue22
        */
                
        // System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY());
        Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
        ServerTerrainChunk chunk = realm.getServerWorldData().getServerTerrainManager().getChunk(worldX, worldY, worldZ);
                        
        // float[][] macroValues = chunk.getMacroValues();//Globals.serverTerrainManager.getRad5MacroValues(message.getworldX(), message.getworldY());

        // long[][] randomizer = chunk.getRandomizer();//Globals.serverTerrainManager.getRandomizer(message.getworldX(), message.getworldY());

        //The length along each access of the chunk data. Typically, should be at least 17.
        //Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate
        //chunk data that connects seamlessly to the next chunk.
        int xWidth = chunk.getWeights().length;
        int yWidth = chunk.getWeights()[0].length;
        int zWidth = chunk.getWeights()[0][0].length;

        ByteBuffer buffer = ByteBuffer.allocate(xWidth*yWidth*zWidth*(4+4));
        FloatBuffer floatView = buffer.asFloatBuffer();

        for(int x = 0; x < xWidth; x++){
            for(int y = 0; y < yWidth; y++){
                for(int z = 0; z < zWidth; z++){
                    floatView.put(chunk.getWeights()[x][y][z]);
                }
            }
        }

        IntBuffer intView = buffer.asIntBuffer();
        intView.position(floatView.position());

        for(int x = 0; x < xWidth; x++){
            for(int y = 0; y < yWidth; y++){
                for(int z = 0; z < zWidth; z++){
                    intView.put(chunk.getValues()[x][y][z]);
                }
            }
        }


        connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendChunkDataMessage(worldX, worldY, worldZ, buffer.array()));

        // int numMessages = 2 + chunk.getModifications().size();

        // connectionHandler.addMessagetoOutgoingQueue(
        //     TerrainMessage.constructchunkLoadStartMessage(worldX, worldY, numMessages)
        // );

        // connectionHandler.addMessagetoOutgoingQueue(
        //     TerrainMessage.constructMacroValueMessage(
        //         worldX,
        //         worldY,
                
                
        //         macroValues[0][0],
        //         macroValues[0][1],
        //         macroValues[0][2],
        //         macroValues[0][3],
        //         macroValues[0][4],
        //         macroValues[1][0],
        //         macroValues[1][1],
        //         macroValues[1][2],
        //         macroValues[1][3],
        //         macroValues[1][4],
        //         macroValues[2][0],
        //         macroValues[2][1],
        //         macroValues[2][2],
        //         macroValues[2][3],
        //         macroValues[2][4],
        //         macroValues[3][0],
        //         macroValues[3][1],
        //         macroValues[3][2],
        //         macroValues[3][3],
        //         macroValues[3][4],
        //         macroValues[4][0],
        //         macroValues[4][1],
        //         macroValues[4][2],
        //         macroValues[4][3],
        //         macroValues[4][4],
                
                
        //         randomizer[0][0],
        //         randomizer[0][1],
        //         randomizer[0][2],
        //         randomizer[0][3],
        //         randomizer[0][4],
        //         randomizer[1][0],
        //         randomizer[1][1],
        //         randomizer[1][2],
        //         randomizer[1][3],
        //         randomizer[1][4],
        //         randomizer[2][0],
        //         randomizer[2][1],
        //         randomizer[2][2],
        //         randomizer[2][3],
        //         randomizer[2][4],
        //         randomizer[3][0],
        //         randomizer[3][1],
        //         randomizer[3][2],
        //         randomizer[3][3],
        //         randomizer[3][4],
        //         randomizer[4][0],
        //         randomizer[4][1],
        //         randomizer[4][2],
        //         randomizer[4][3],
        //         randomizer[4][4]
        //     )
        // );

        // for(TerrainModification modification : chunk.getModifications()){
        //     connectionHandler.addMessagetoOutgoingQueue(
        //         TerrainMessage.constructheightMapModificationMessage(
        //             modification.getValue(),
        //             modification.getWorldX(),
        //             0,
        //             modification.getWorldY(),
        //             modification.getLocationX(),
        //             0,
        //             modification.getLocationY()
        //         )
        //     );
        // }
    }


    /**
     * Sends a fluid sub chunk to the client
     * @param connectionHandler The connection handler
     * @param worldX the world x
     * @param worldY the world y
     * @param worldZ the world z
     */
    static void sendWorldFluidSubChunk(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){
        Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
                
        // System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY());
                        
        ServerFluidChunk chunk = realm.getServerWorldData().getServerFluidManager().getChunk(worldX, worldY, worldZ);

        
        ByteBuffer buffer = constructFluidByteBuffer(chunk);

        connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendFluidDataMessage(worldX, worldY, worldZ, buffer.array()));

    }

    /**
     * Sends world metadata to the client
     * @param connectionHandler The connection handler
     */
    static void sendWorldMetadata(ServerConnectionHandler connectionHandler){
        Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
        //world metadata
        connectionHandler.addMessagetoOutgoingQueue(
            TerrainMessage.constructResponseMetadataMessage(
                realm.getServerWorldData().getWorldSizeDiscrete(),
                realm.getServerWorldData().getDynamicInterpolationRatio(),
                realm.getServerWorldData().getRandomDampener(),
                (int)realm.getServerWorldData().getWorldBoundMin().x,
                (int)realm.getServerWorldData().getWorldBoundMin().z,
                (int)realm.getServerWorldData().getWorldBoundMax().x,
                (int)realm.getServerWorldData().getWorldBoundMax().z
            )
        );
    }

    /**
     * Attempts to perform an edit requested by a client
     * @param message The message containing the edit request
     */
    static void attemptTerrainEdit(ServerConnectionHandler connectionHandler, TerrainMessage message){
        // Player player = Globals.playerManager.getPlayerFromId(connectionHandler.getPlayerId());
        throw new UnsupportedOperationException();
    }

    /**
     * Attempts to use an edit palette on the terrain
     * @param connectionHandler The connection handler
     * @param message The message that contains the request to use an edit palette
     */
    static void attemptUseTerrainEditPalette(ServerConnectionHandler connectionHandler, TerrainMessage message){
        Player player = Globals.playerManager.getPlayerFromId(connectionHandler.getPlayerId());
        Realm realm = Globals.realmManager.getPlayerRealm(player);
        Vector3d location = new Vector3d(message.getrealLocationX(), message.getrealLocationY(), message.getrealLocationZ());
        TerrainEditing.editTerrain(realm, location, message.getvalue(), message.getterrainValue(), message.getterrainWeight());
    }

    /**
     * Constructs a buffer to send a fluid chunk to the client
     * @param chunk The chunk to send
     * @return the buffer
     */
    public static ByteBuffer constructFluidByteBuffer(ServerFluidChunk chunk){
        //The length along each access of the chunk data. Typically, should be at least 17.
        //Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate
        //chunk data that connects seamlessly to the next chunk.
        int xWidth = chunk.getWeights().length;
        int yWidth = chunk.getWeights()[0].length;
        int zWidth = chunk.getWeights()[0][0].length;

        ByteBuffer buffer = ByteBuffer.allocate(xWidth*yWidth*zWidth*(4+4+4+4));
        FloatBuffer floatView = buffer.asFloatBuffer();

        for(int x = 0; x < xWidth; x++){
            for(int y = 0; y < yWidth; y++){
                for(int z = 0; z < zWidth; z++){
                    floatView.put(chunk.getWeights()[x][y][z]);
                }
            }
        }

        for(int x = 0; x < xWidth; x++){
            for(int y = 0; y < yWidth; y++){
                for(int z = 0; z < zWidth; z++){
                    floatView.put(chunk.getVelocityX()[x][y][z]);
                }
            }
        }

        for(int x = 0; x < xWidth; x++){
            for(int y = 0; y < yWidth; y++){
                for(int z = 0; z < zWidth; z++){
                    floatView.put(chunk.getVelocityY()[x][y][z]);
                }
            }
        }

        for(int x = 0; x < xWidth; x++){
            for(int y = 0; y < yWidth; y++){
                for(int z = 0; z < zWidth; z++){
                    floatView.put(chunk.getVelocityZ()[x][y][z]);
                }
            }
        }

        return buffer;
    }
    
}
