package electrosphere.client.fluid.cells;

import org.joml.Vector3d;
import org.joml.Vector3i;
import org.ode4j.ode.DBody;

import electrosphere.client.fluid.cache.FluidChunkData;
import electrosphere.collision.CollisionEngine;
import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.fluid.FluidChunk;
import electrosphere.renderer.shader.ShaderProgram;
import electrosphere.server.terrain.manager.ServerTerrainChunk;

/**
 *
 * @author satellite
 */
public class FluidCell {
    //the position of the draw cell in world coordinates
    Vector3i worldPos;
    
    FluidChunkData data;
    
    Entity modelEntity;
    
    ShaderProgram program;
    
    DBody physicsObject;

    float[][][] weights = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE];
    
    //the value of an empty fluid cell weight that is not neighbored by a fluid value
    public static final float ISO_SURFACE_EMPTY = -1;
    
    FluidCell(){
        
    }
    
    
    /**
     * Constructs a drawcell object
     */
    public static FluidCell generateFluidCell(
            Vector3i worldPos,
            FluidChunkData data,
            ShaderProgram program
    ){
        FluidCell rVal = new FluidCell();
        rVal.worldPos = worldPos;
        rVal.program = program;
        rVal.data = data;
        return rVal;
    }

    /**
     * Generates a drawable entity based on this chunk
     */
    public void generateDrawableEntity(){
        if(modelEntity != null){
            Globals.clientScene.deregisterEntity(modelEntity);
        }

        fillInData();

        modelEntity = FluidChunk.clientCreateFluidChunkEntity(weights);

        ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos());
    }

    protected Vector3d getRealPos(){
        return new Vector3d(
            worldPos.x * FluidChunkData.CHUNK_SIZE,
            worldPos.y * FluidChunkData.CHUNK_SIZE,
            worldPos.z * FluidChunkData.CHUNK_SIZE
        );
    }
    
    /**
     * Destroys a drawcell including its physics
     */
    public void destroy(){
        CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine();
        collisionEngine.destroyEntityThatHasPhysics(modelEntity);
        EntityUtils.cleanUpEntity(modelEntity);
        //destruct model
        String modelPath = (String)modelEntity.getData(EntityDataStrings.DATA_STRING_MODEL_PATH);
        Globals.assetManager.deregisterModelPath(modelPath);
    }

    /**
     * Gets the current chunk data for this draw cell
     * @return The chunk data
     */
    public FluidChunkData getData(){
        return data;
    }

    /**
     * Fills in the internal arrays of data for generate terrain models
     */
    private void fillInData(){

        //
        //fill in data
        //
        //main chunk
        FluidChunkData currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos);
        for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
            for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
                for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
                    weights[x][y][z] = currentChunk.getWeight(x,y,z);
                }
            }
        }
        //face X
        if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z);
            if(currentChunk != null){
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                        weights[ServerTerrainChunk.CHUNK_DIMENSION][i][j] = currentChunk.getWeight(0, i, j);
                    }
                }
            } else {
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                        weights[ServerTerrainChunk.CHUNK_DIMENSION][i][j] = ISO_SURFACE_EMPTY;
                    }
                }
            }
        } else {
            for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                    weights[ServerTerrainChunk.CHUNK_DIMENSION][i][j] = ISO_SURFACE_EMPTY;
                }
            }
        }
        //face Y
        if(worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z);
            if(currentChunk != null){
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                        weights[i][ServerTerrainChunk.CHUNK_DIMENSION][j] = currentChunk.getWeight(i, 0, j);
                    }
                }
            } else {
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                        weights[i][ServerTerrainChunk.CHUNK_DIMENSION][j] = ISO_SURFACE_EMPTY;
                    }
                }
            }
        } else {
            for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                    weights[i][ServerTerrainChunk.CHUNK_DIMENSION][j] = ISO_SURFACE_EMPTY;
                }
            }
        }
        //face Z
        if(worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z + 1);
            if(currentChunk != null){
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                        weights[i][j][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(i, j, 0);
                    }
                }
            } else {
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                        weights[i][j][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
                    }
                }
            }
        } else {
            for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){
                    weights[i][j][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
                }
            }
        }
        //edge X-Y
        if(
            worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z);
            if(currentChunk != null){
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][i] = currentChunk.getWeight(0, 0, i);
                }
            } else {
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][i] = ISO_SURFACE_EMPTY;
                }
            }
        } else {
            for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][i] = ISO_SURFACE_EMPTY;
            }
        }
        //edge X-Z
        if(
            worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z + 1);
            if(currentChunk != null){
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    weights[ServerTerrainChunk.CHUNK_DIMENSION][i][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(0, i, 0);
                }
            } else {
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    weights[ServerTerrainChunk.CHUNK_DIMENSION][i][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
                }
            }
        } else {
            for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                weights[ServerTerrainChunk.CHUNK_DIMENSION][i][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
            }
        }
        //edge Y-Z
        if(
            worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z + 1);
            if(currentChunk != null){
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    weights[i][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(i, 0, 0);
                }
            } else {
                for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                    weights[i][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
                }
            }
        } else {
            for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){
                weights[i][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
            }
        }
        if(
            worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z + 1);
            if(currentChunk != null){
                weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(0, 0, 0);
            } else {
                weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
            }
        } else {
            weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = ISO_SURFACE_EMPTY;
        }

        //now set neighboring air weights based on nearby fluid count
        //idea being that we dont have the snapping behavior from iso surface jumping from -1->0.01
        int[] neighborIndexX = new int[]{-1,1,0,0,0,0};
        int[] neighborIndexY = new int[]{0,0,-1,1,0,0};
        int[] neighborIndexZ = new int[]{0,0,0,0,-1,1};
        for(int x = 0; x < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; x++){
            for(int y = 0; y < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; y++){
                for(int z = 0; z < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; z++){
                    if(weights[x][y][z] > 0){
                        continue;
                    }
                    for(int i = 0; i < 6; i++){
                        int currX = x + neighborIndexX[i];
                        int currY = y + neighborIndexY[i];
                        int currZ = z + neighborIndexZ[i];
                        if(
                            currX >= 0 && currX < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE &&
                            currY >= 0 && currY < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE &&
                            currZ >= 0 && currZ < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE &&
                            (1 + weights[x][y][z]) < weights[currX][currY][currZ]
                        ){
                            weights[x][y][z] = -(1 - weights[currX][currY][currZ]);
                            if(weights[x][y][z] >= 0){
                                weights[x][y][z] = -0.01f;
                            }
                        }
                    }
                }
            }
        }
    }
    
}
