package electrosphere.client.sim;

import org.joml.Vector3d;

import electrosphere.client.entity.camera.CameraEntityUtils;
import electrosphere.client.entity.crosshair.Crosshair;
import electrosphere.client.fluid.manager.ClientFluidManager;
import electrosphere.client.instancing.InstanceUpdater;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityTags;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.state.attach.AttachUtils;
import electrosphere.entity.state.collidable.ClientCollidableTree;
import electrosphere.entity.state.equip.ClientEquipState;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.entity.types.item.ItemUtils;
import electrosphere.renderer.actor.Actor;

public class ClientSimulation {

    //if true, is ready to simulate
    boolean isReady = false;

    //if true, should load terrain
    boolean loadTerrain = false;

    //used for tracking different in player position between frames (principally for draw cell manager)
    Vector3d newPlayerCharacterPosition = new Vector3d();
    
    /**
     * Constructor
     */
    public ClientSimulation(){
        isReady = false;
    }
    
    /**
     * Main simulation function
     */
    public void simulate(){
        Globals.profiler.beginCpuSample("simulate");

        //
        //load terrain
        if(isLoadingTerrain()){
            loadTerrain();
        }

        //process all server synchronization messages
        Globals.profiler.beginCpuSample("clientSynchronizationManager.processMessages");
        Globals.clientSynchronizationManager.processMessages();
        Globals.profiler.endCpuSample();
        //
        //simulate bullet physics engine step
        if(Globals.RUN_PHYSICS){
            Globals.clientSceneWrapper.getCollisionEngine().simulatePhysics((float)Globals.timekeeper.getSimFrameTime());
            Globals.clientSceneWrapper.getCollisionEngine().updateDynamicObjectTransforms();
        }
        
        //update actor animations
        Globals.profiler.beginCpuSample("update actor animations");
        for(Entity currentEntity : Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.DRAWABLE)){
            Actor currentActor = EntityUtils.getActor(currentEntity);
            if(currentActor.isPlayingAnimation()){
                currentActor.incrementAnimationTime((float)Globals.timekeeper.getSimFrameTime());
            }
        }
        Globals.profiler.endCpuSample();
        //
        //make items play idle animation
        Globals.profiler.beginCpuSample("item animations");
        for(Entity item : Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.ITEM)){
            ItemUtils.updateItemActorAnimation(item);
        }
        Globals.profiler.endCpuSample();
        //
        //update attached entity positions
        AttachUtils.clientUpdateAttachedEntityPositions();
        Globals.particleService.handleAllSignals();
        //
        //Hitbox stuff
        Globals.profiler.beginCpuSample("update hitboxes");
        Globals.clientSceneWrapper.getHitboxManager().simulate();
        Globals.profiler.endCpuSample();
        //
        //update foliage
        if(Globals.foliageCellManager != null){
            Globals.foliageCellManager.update();
        }
        //
        //targeting crosshair
        Globals.profiler.beginCpuSample("crosshair update");
        Crosshair.checkTargetable();
        Crosshair.updateTargetCrosshairPosition();
        Globals.profiler.endCpuSample();
        //
        //simulate behavior trees
        Globals.clientSceneWrapper.getScene().simulateBehaviorTrees((float)Globals.timekeeper.getSimFrameTime());
        //
        //sum collidable impulses
        Globals.profiler.beginCpuSample("collidable logic");
        for(Entity collidable : Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.COLLIDABLE)){
            ClientCollidableTree.getClientCollidableTree(collidable).simulate((float)Globals.timekeeper.getSimFrameTime());
        }
        //
        //clear collidable impulse lists
        Globals.clientSceneWrapper.getCollisionEngine().clearCollidableImpulseLists();
        Globals.clientSceneWrapper.getChemistryEngine().clearCollidableImpulseLists();
        Globals.profiler.endCpuSample();

        //
        //wrap up functions
        this.runClientFunctions();

        Globals.profiler.endCpuSample();
    }

    public void runClientFunctions(){
        Globals.profiler.beginCpuSample("client functions");
        ClientTerrainManager.generateTerrainChunkGeometry();
        ClientFluidManager.generateFluidChunkGeometry();
        this.updateSkyboxPos();
        Globals.clientSceneWrapper.destroyEntitiesOutsideSimRange();
        InstanceUpdater.updateInstancedActorPriority();
        Globals.cameraHandler.updateGlobalCamera();
        this.updateFirstPersonAttachments();
        //bones have potenitally moved, so need to update where attached entities actually are before drawing
        AttachUtils.clientUpdateAttachedEntityPositions();
        // updateCellManager();
        Globals.profiler.endCpuSample();
    }


    /**
     * Updates the skybox position to center on the player
     */
    private void updateSkyboxPos(){
        Globals.profiler.beginCpuSample("updateSkyboxPos");
        if(Globals.skybox != null && Globals.playerEntity != null){
            EntityUtils.getPosition(Globals.skybox).set(EntityUtils.getPosition(Globals.playerEntity));
        }
        Globals.profiler.endCpuSample();
    }

    /**
     * If in first person, update the spatial transforms of things attached to the player viewmodel
     */
    private void updateFirstPersonAttachments(){
        Globals.profiler.beginCpuSample("updateFirstPersonAttachments");
        //update the facing vector when camera moves in first person
        if(!Globals.controlHandler.cameraIsThirdPerson() && Globals.playerCamera != null && Globals.playerEntity != null){
            CreatureUtils.setFacingVector(Globals.playerEntity, CameraEntityUtils.getFacingVec(Globals.playerCamera));
            //flush equipped item state
            if(ClientEquipState.hasEquipState(Globals.playerEntity)){
                ClientEquipState equipState = ClientEquipState.getClientEquipState(Globals.playerEntity);
                equipState.evaluatePlayerAttachments();
            }
        }
        Globals.profiler.endCpuSample();
    }

    /**
     * Loads terrain that is in queue
     */
    public void loadTerrain(){
        Globals.profiler.beginCpuSample("ClientSimulation.loadTerrain");
        if(Globals.clientTerrainManager != null){
            Globals.clientTerrainManager.handleMessages();
            this.updateTerrainCellManager();
        }
        if(Globals.clientFluidManager != null && Globals.RUN_FLUIDS){
            Globals.clientFluidManager.handleMessages();
            this.updateFluidCellManager();
        }
        if(Globals.clientBlockManager != null){
            Globals.clientBlockManager.handleMessages();
            this.updateBlockCellManager();
        }
        Globals.profiler.endCpuSample();
    }

    /**
     * Updates the terrain cell manager (specifically position handling)
     */
    private void updateTerrainCellManager(){
        ///
        ///      C L I E N T    C E L L    M A N A G E R
        ///
        if(Globals.clientDrawCellManager != null && Globals.clientWorldData != null){
            //Cell manager do your things
            Globals.clientDrawCellManager.update();
        }
    }

    /**
     * Updates the fluid cell manager (specifically position handling)
     */
    private void updateFluidCellManager(){
        //fluid work
        if(Globals.fluidCellManager != null && Globals.clientWorldData != null && Globals.RUN_FLUIDS){
            Globals.fluidCellManager.update();
        }
    }

    /**
     * Updates the block cell manager
     */
    private void updateBlockCellManager(){
        if(Globals.clientBlockCellManager != null && Globals.clientWorldData != null){
            Globals.clientBlockCellManager.update();
        }
    }
    
    /**
     * Gets whether the client simulation is ready to execute
     * @return True if ready to execute, false otherwise
     */
    public boolean isReady(){
        return isReady;
    }
    
    /**
     * Sets the ready status of the client simulation
     * @param ready True if ready to simulate, false otherwise
     */
    public void setReady(boolean ready){
        isReady = ready;
    }
    
    /**
     * Freezes simulation (sets ready to false)
     */
    public void freeze(){
        isReady = false;
    }

    /**
     * Unfreezes simulation (sets ready to true)
     */
    public void unfreeze(){
        isReady = true;
    }

    /**
     * Gets whether the client simulation is loading terrain
     * @return True if loading terrain, false otherwise
     */
    public boolean isLoadingTerrain(){
        return this.loadTerrain;
    }

    /**
     * Sets whether the client should load terrain or not
     * @param loadTerrain True if should load terrain, false otherwise
     */
    public void setLoadingTerrain(boolean loadTerrain){
        this.loadTerrain = loadTerrain;
    }

}
