package electrosphere.client.scene;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.joml.Vector3d;
import org.ode4j.ode.DContactGeom;

import electrosphere.collision.CollisionEngine;
import electrosphere.collision.CollisionEngine.CollisionResolutionCallback;
import electrosphere.collision.collidable.Collidable;
import electrosphere.collision.hitbox.HitboxManager;
import electrosphere.collision.hitbox.HitboxUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.Scene;
import electrosphere.logger.LoggerInterface;

/**
 * Wrapper around the scene object to provide lots of much needed client-specific utility
 * Does all the server<->client id translation and provides utilities to map between the two
 */
public class ClientSceneWrapper {
    
    //entity id translation between server/client
    Map<Integer,Integer> clientToServerIdMap = new ConcurrentHashMap<Integer,Integer>();
    Map<Integer,Integer> serverToClientIdMap = new ConcurrentHashMap<Integer,Integer>();

    //The scene backing the wrapper
    Scene scene;

    //The engine used to back physics collision checks in client
    CollisionEngine collisionEngine;

    //The hitbox manager
    HitboxManager hitboxManager;

    /**
     * Constructor
     * @param scene The scene
     * @param collisionEngine The collision engine
     */
    public ClientSceneWrapper(Scene scene, CollisionEngine collisionEngine){
        this.scene = scene;
        this.collisionEngine = collisionEngine;
        this.hitboxManager = new HitboxManager(resolutionCallback);
    }

    /**
     * Registers a server provided ID as a mapping to a given ID on the client
     * @param clientId The client's generated ID
     * @param serverId The server's provided ID
     */
    public void mapIdToId(int clientId, int serverId){
        LoggerInterface.loggerNetworking.DEBUG("MapID: " + clientId + " <===> " + serverId);
        clientToServerIdMap.put(clientId, serverId);
        serverToClientIdMap.put(serverId, clientId);
    }

    /**
     * Resolves a client ID to the equivalent ID on the server
     * @param clientId The id provided by the client
     * @return The equivalent id on the server
     */
    public int mapClientToServerId(int clientId){
        return clientToServerIdMap.get(clientId);
    }

    /**
     * Translates the id provided by the server into the equivalent id on the client
     * @param serverId The id provided by the server
     * @return The equivalent id on the client
     */
    public int mapServerToClientId(int serverId){
        return serverToClientIdMap.get(serverId);
    }

    /**
     * Checks if the scene wrapper contains the provided server id
     * @param serverId The server id
     * @return true if the map contains that id, false otherwise
     */
    public boolean containsServerId(int serverId){
        return serverToClientIdMap.containsKey(serverId);
    }


    /**
     * Returns true if the server->client map contains a given id
     * @param id The id to search for
     * @return True if the server->client map contains the provided id
     */
    public boolean serverToClientMapContainsId(int id){
        return serverToClientIdMap.containsKey(id);
    }


    /**
     * Gets the entity provided a server-provided id
     * @param id The server-provided ID
     * @return The entity in question
     */
    public Entity getEntityFromServerId(int id){
        if(serverToClientIdMap.containsKey(id)){
            int clientId = mapServerToClientId(id);
            return scene.getEntityFromId(clientId);
        } else {
            return null;
        }
    }

    /**
     * Dumps the status of the network translation layer
     */
    public void dumpTranslationLayerStatus(){
        LoggerInterface.loggerNetworking.WARNING("Client -> Server keys");
        LoggerInterface.loggerNetworking.WARNING(clientToServerIdMap.keySet() + "");
        LoggerInterface.loggerNetworking.WARNING("Server -> Client keys");
        LoggerInterface.loggerNetworking.WARNING(serverToClientIdMap.keySet() + "");
    }

    /**
     * Dumps data about a given id
     * @param id The id
     */
    public void dumpIdData(int id){
        LoggerInterface.loggerNetworking.WARNING("Offending ID " + id);
        LoggerInterface.loggerNetworking.WARNING("Client->Server Map contains? " + clientToServerIdMap.containsKey(id));
        LoggerInterface.loggerNetworking.WARNING("Server->Client Map contains? " + serverToClientIdMap.containsKey(id));
        if(clientToServerIdMap.containsKey(id)){
            LoggerInterface.loggerNetworking.WARNING("Client->Server Map entity: " + clientToServerIdMap.get(id));
        }
        if(clientToServerIdMap.containsKey(id)){
            LoggerInterface.loggerNetworking.WARNING("Server->Client Map entity: " + serverToClientIdMap.get(id));
        }
    }

    /**
     * Gets the scene backing this client scene wrapper
     * @return The scene
     */
    public Scene getScene(){
        return this.scene;
    }

    /**
     * Gets the collision engine backing the wrapper
     * @return
     */
    public CollisionEngine getCollisionEngine(){
        return collisionEngine;
    }

    /**
     * Gets the hitbox manager for the client
     * @return The hitbox manager
     */
    public HitboxManager getHitboxManager(){
        return hitboxManager;
    }

    /**
     * Destroys all entities outside simulation range
     */
    public void destroyEntitiesOutsideSimRange(){
        Globals.profiler.beginCpuSample("destroyEntitiesOutsideSimRange");
        // if(Globals.drawCellManager != null && Globals.playerEntity != null){
        //     double cullRadius = Globals.drawCellManager.getDrawRadius() + ServerTerrainChunk.CHUNK_DIMENSION;
        //     Vector3d playerPosition = EntityUtils.getPosition(Globals.playerEntity);
        //     List<Entity> entityList = scene.getEntityList();
        //     for(Entity entity : entityList){
        //         Vector3d position = EntityUtils.getPosition(entity);
        //         if(playerPosition.distance(position) > cullRadius){
        //             EntityUtils.cleanUpEntity(entity);
        //         }
        //     }
        // }
        Globals.profiler.endCpuSample();
    }

    
    /**
     * The resolution callback that is invoked once a collision has occurred
     */
    CollisionResolutionCallback resolutionCallback = new CollisionResolutionCallback() {
        @Override
        public void resolve(DContactGeom geom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude) {
            HitboxUtils.clientDamageHitboxColision(geom, impactor, receiver, normal, localPosition, worldPos, magnitude);
        }
    };

}
