package electrosphere.entity.types.terrain;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

import electrosphere.client.block.BlockChunkData;
import electrosphere.client.block.cells.BlockDrawCell;
import electrosphere.client.block.cells.BlockTextureAtlas;
import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.collision.PhysicsUtils;
import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.queue.QueuedModel;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.collision.CollisionObjUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.meshgen.BlockMeshgen;
import electrosphere.renderer.meshgen.BlockMeshgen.BlockMeshData;
import electrosphere.server.datacell.Realm;

/**
 * Generates block chunk entities
 */
public class BlockChunkEntity {
    
    /**
     * Used for generating block chunks
     */
    static final ExecutorService generationService = Executors.newFixedThreadPool(4);

    /**
     * Creates a client block chunk based on weights and values provided
     * @param chunkData the chunk data to generate with
     * @param toDelete The entity to delete on full generation of this entity
     * @param notifyTarget The target draw cell to notify once this has successfully generated its model
     * @param levelOfDetail Increasing value that increments level of detail. 0 would be full resolution, 1 would be half resolution and so on. Only generates physics if levelOfDetail is 0
     * @param hasPolygons true if the chunk has polygons to generate a model with, false otherwise
     * @return The block chunk entity
     */
    public static Entity clientCreateBlockChunkEntity(
        BlockChunkData chunkData,
        BlockDrawCell notifyTarget,
        Entity toDelete,
        int levelOfDetail,
        BlockTextureAtlas atlas,
        boolean hasPolygons
    ){
        Globals.profiler.beginAggregateCpuSample("BlockChunk.clientCreateBlockChunkEntity");

        Entity rVal = EntityCreationUtils.createClientSpatialEntity();

        if(hasPolygons && chunkData.getType() != null && chunkData.getMetadata() != null){
            generationService.submit(() -> {
                BlockMeshData data;
                try {
                    data = BlockMeshgen.rasterize(chunkData);
                    if(Globals.clientScene.containsEntity(rVal)){
                        String modelPath = Globals.assetManager.queuedAsset(new QueuedModel(() -> {
                            return BlockMeshgen.generateBlockModel(data);
                        }));
                        EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
                        if(levelOfDetail == BlockChunkData.LOD_FULL_RES){
                            PhysicsEntityUtils.clientAttachMultiShapeTriGeomRigidBody(rVal, data);
                            CollisionObjUtils.clientPositionCharacter(rVal, new Vector3d(EntityUtils.getPosition(rVal)), new Quaterniond());
                        } else {
                            EntityCreationUtils.bypassShadowPass(rVal);
                            EntityCreationUtils.bypassVolumetics(rVal);
                        }
                        rVal.putData(EntityDataStrings.HAS_UNIQUE_MODEL, true);
                    } else {
                        LoggerInterface.loggerEngine.DEBUG("Finished generating block polygons; however, entity has already been deleted.");
                    }
                    if(notifyTarget != null){
                        notifyTarget.alertToGeneration();
                    }
                    if(toDelete != null){
                        ClientEntityUtils.destroyEntity(toDelete);
                    }
                } catch (Error e){
                    LoggerInterface.loggerEngine.ERROR(e);
                } catch(Exception e){
                    LoggerInterface.loggerEngine.ERROR(e);
                }
            });
        } else {
            if(notifyTarget != null){
                notifyTarget.alertToGeneration();
            }
            if(toDelete != null){
                ClientEntityUtils.destroyEntity(toDelete);
            }
        }

        rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
        Globals.profiler.endCpuSample();
        return rVal;
    }

    /**
     * Creates a block chunk entity on the server
     * @param entity The entity to populate
     * @param weights The weights for the block chunk
     * @param values The values of each voxel in the chunk
     * @return The block entity
     */
    public static void serverCreateBlockChunkEntity(Entity entity, BlockMeshData blockChunkData){
        if(blockChunkData.getVertices().length > 0){
            PhysicsEntityUtils.serverAttachMultiShapeTriGeomRigidBody(entity, blockChunkData);
            Realm realm = Globals.realmManager.getEntityRealm(entity);
            DBody terrainBody = PhysicsEntityUtils.getDBody(entity);
            Vector3d entityPos = EntityUtils.getPosition(entity);
            Quaterniond entityRot = EntityUtils.getRotation(entity);
            PhysicsUtils.setRigidBodyTransform(realm.getCollisionEngine(), entityPos, entityRot, terrainBody);
            entity.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
        }
    }

    /**
     * Halts all running generation threads
     */
    public static void haltThreads(){
        generationService.shutdownNow();
    }

}
