package electrosphere.client.terrain.cells;

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

import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.collision.CollisionEngine;
import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.terrain.TerrainChunk;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
import electrosphere.server.terrain.manager.ServerTerrainChunk;

/**
 * A single drawcell - contains an entity that has a physics mesh and potentially graphics
 */
public class DrawCell {

    /**
     * Enum for the different faces of a draw cell -- used when filling in data for higher LOD faces
     */
    public enum DrawCellFace {
        X_POSITIVE,
        X_NEGATIVE,
        Y_POSITIVE,
        Y_NEGATIVE,
        Z_POSITIVE,
        Z_NEGATIVE,
    }
    
    //the position of the draw cell in world coordinates
    Vector3i worldPos;
    
    //the main entity for the cell
    Entity modelEntity;
    
    //the physics mesh
    DBody physicsObject;

    //Allocated once instead of continuously, used to generate the visual/physics models
    float[][][] weights = new float[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE];
    int[][][] types = new int[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE];


    //the maximum detail LOD level
    public static final int FULL_DETAIL_LOD = 0;
        
    
    /**
     * Private constructor
     */
    DrawCell(){
        
    }
    
    
    /**
     * Constructs a drawcell object
     */
    public static DrawCell generateTerrainCell(
            Vector3i worldPos
    ){
        DrawCell rVal = new DrawCell();
        rVal.worldPos = worldPos;
        return rVal;
    }
    
    /**
     * Generates a drawable entity based on this chunk
     */
    public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, DrawCellFace higherLODFace){
        if(modelEntity != null){
            Globals.clientScene.deregisterEntity(modelEntity);
        }
        this.fillInData();
        TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, types, lod);
        if(lod > FULL_DETAIL_LOD){

        }
        modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas);
        ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos(), new Quaterniond());
    }

    /**
     * Gets the real-space position of the draw cell
     * @return the real-space position
     */
    protected Vector3d getRealPos(){
        return new Vector3d(
            worldPos.x * ChunkData.CHUNK_SIZE,
            worldPos.y * ChunkData.CHUNK_SIZE,
            worldPos.z * ChunkData.CHUNK_SIZE
        );
    }
    
    /**
     * Destroys a drawcell including its physics
     */
    public void destroy(){
        CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine();
        collisionEngine.destroyPhysics(modelEntity);
        EntityUtils.cleanUpEntity(modelEntity);
    }


    /**
     * Fills in the internal arrays of data for generate terrain models
     */
    private void fillInData(){
        //
        //fill in data
        //
        //main chunk
        ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
        for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
            for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){
                for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
                    weights[x][y][z] = currentChunk.getWeight(x,y,z);
                    types[x][y][z] = currentChunk.getType(x,y,z);
                }
            }
        }
        //face X
        if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z);
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
                    weights[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getWeight(0, i, j);
                    types[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getType(0, i, j);
                }
            }
        } else {
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
                    weights[ChunkData.CHUNK_SIZE][i][j] = -1;
                    types[ChunkData.CHUNK_SIZE][i][j] = 0;
                }
            }
        }
        //face Y
        if(worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z);
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
                    weights[i][ChunkData.CHUNK_SIZE][j] = currentChunk.getWeight(i, 0, j);
                    types[i][ChunkData.CHUNK_SIZE][j] = currentChunk.getType(i, 0, j);
                }
            }
        } else {
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
                    weights[i][ChunkData.CHUNK_SIZE][j] = -1;
                    types[i][ChunkData.CHUNK_SIZE][j] = 0;
                }
            }
        }
        //face Z
        if(worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z + 1);
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
                    weights[i][j][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(i, j, 0);
                    types[i][j][ChunkData.CHUNK_SIZE] = currentChunk.getType(i, j, 0);
                }
            }
        } else {
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
                    weights[i][j][ChunkData.CHUNK_SIZE] = -1;
                    types[i][j][ChunkData.CHUNK_SIZE] = 0;
                }
            }
        }
        //edge X-Y
        if(
            worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z);
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = currentChunk.getWeight(0, 0, i);
                types  [ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = currentChunk.getType(0, 0, i);
            }
        } else {
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = -1;
                types  [ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = 0;
            }
        }
        //edge X-Z
        if(
            worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z + 1);
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                weights[ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(0, i, 0);
                types  [ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = currentChunk.getType(0, i, 0);
            }
        } else {
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                weights[ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = -1;
                types  [ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = 0;
            }
        }
        //edge Y-Z
        if(
            worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z + 1);
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                weights[i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(i, 0, 0);
                types  [i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getType(i, 0, 0);
            }
        } else {
            for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
                weights[i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = -1;
                types  [i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0;
            }
        }
        if(
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
            worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
        ){
            currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z + 1);
            if(currentChunk != null){
                weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(0, 0, 0);
                types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getType(0, 0, 0);
            }
        } else {
            weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = -1;
            types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0;
        }
    }


    /**
     * Fills in the internal arrays of data for generate terrain models
     */
    private void fillInData(TransvoxelChunkData chunkData, int lod, DrawCellFace face){
        float[][] faceData = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE];
        int[][] atlasData = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE];
        switch(face){
            case X_POSITIVE: {
            } break;
            case X_NEGATIVE: {
            } break;
            case Y_POSITIVE: {
            } break;
            case Y_NEGATIVE: {
            } break;
            case Z_POSITIVE: {
            } break;
            case Z_NEGATIVE: {
            } break;
        }
        //
        //fill in data
        //
        //main chunk
        //face X
        // if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
        //     currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z);
        //     for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
        //         for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
        //             weights[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getWeight(0, i, j);
        //             types[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getType(0, i, j);
        //         }
        //     }
        // } else {
        //     for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
        //         for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
        //             weights[ChunkData.CHUNK_SIZE][i][j] = -1;
        //             types[ChunkData.CHUNK_SIZE][i][j] = 0;
        //         }
        //     }
        // }
    }
    
}
