package electrosphere.net.synchronization.client;


import electrosphere.entity.state.movement.speed.ClientWalkTree;
import electrosphere.entity.state.equip.ClientEquipState;
import electrosphere.entity.state.movement.jump.ClientJumpTree;
import electrosphere.entity.state.life.ClientLifeTree;
import electrosphere.entity.state.block.ClientBlockTree;
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree;
import electrosphere.logger.LoggerInterface;
import electrosphere.entity.state.attack.ClientAttackTree;
import electrosphere.entity.state.gravity.ClientGravityTree;
import electrosphere.entity.state.idle.ClientIdleTree;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.net.parser.net.message.SynchronizationMessage;
import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums;
import electrosphere.net.synchronization.enums.FieldIdEnums;

/**
 * Takes in raw behavior tree packets from server and pushes their values into respective behavior trees in entities
 */
public class ClientSynchronizationManager {

    //The list of messages to loop through
    List<SynchronizationMessage> messages = new CopyOnWriteArrayList<SynchronizationMessage>();

    //Map that tracks the number of times a network message bounces
    Map<SynchronizationMessage,Integer> messageBounceCount = new HashMap<SynchronizationMessage,Integer>();

    //the count at which to warn about a message bouncing
    static final int MESSAGE_BOUNCE_WARNING_COUNT = 10;

    /**
     * Pushes a message into the queue to be processed
     * @param message The message
     */
    public void pushMessage(SynchronizationMessage message){
        this.messages.add(message);
    }

    /**
     * Processes all messages in the queue and then clears the queue
     */
    public void processMessages(){
        List<SynchronizationMessage> messagesToClear = new LinkedList<SynchronizationMessage>();
        for(SynchronizationMessage message : messages){

            //track number of times this message has bounced
            if(messageBounceCount.containsKey(message)){
                messageBounceCount.put(message, messageBounceCount.get(message) + 1);
            } else {
                messageBounceCount.put(message, 0);
            }

            //attempt to handle the message
            if(Globals.clientSceneWrapper.containsServerId(message.getentityId()) && Globals.clientSceneWrapper.getEntityFromServerId(message.getentityId()) != null){
                messagesToClear.add(message);
                messageBounceCount.remove(message);
                switch(message.getMessageSubtype()){
                    case UPDATECLIENTSTATE:
                    case UPDATECLIENTDOUBLESTATE:
                    case UPDATECLIENTFLOATSTATE:
                    case UPDATECLIENTINTSTATE:
                    case UPDATECLIENTLONGSTATE:
                    case UPDATECLIENTSTRINGSTATE:{
                        int bTreeId = message.getbTreeId();
                        int entityId = message.getentityId();
                        Entity targetEntity = Globals.clientSceneWrapper.getEntityFromServerId(entityId);
                        updateEntityState(targetEntity,bTreeId,message);
                    } break;
                    case SERVERNOTIFYBTREETRANSITION: {
                        int bTreeId = message.getbTreeId();
                        int entityId = message.getentityId();
                        Entity targetEntity = Globals.clientSceneWrapper.getEntityFromServerId(entityId);
                        transitionBTree(targetEntity, bTreeId, message);
                    } break;
                    case ATTACHTREE:{
                        // int bTreeId = message.getbTreeId();
                        // int bTreeValue = message.getbTreeValue();
                        // int entityId = message.getentityId();
                        throw new UnsupportedOperationException("Not implemented yet!");
                    }
                    case DETATCHTREE:{
                        // int bTreeId = message.getbTreeId();
                        // int entityId = message.getentityId();
                        throw new UnsupportedOperationException("Not implemented yet!");
                    }
                    case LOADSCENE: {
                        throw new UnsupportedOperationException("Not implemented yet!");
                    }
                    case CLIENTREQUESTBTREEACTION:
                        //silently ignore
                        break;
                }
            } else if(Globals.clientSceneWrapper.containsServerId(message.getentityId()) && Globals.clientSceneWrapper.getEntityFromServerId(message.getentityId()) == null){
                String errorMessage = 
                "Client received synchronization packet for entity that does not exists on client!\n" +
                "Specifically, the synchronization manager thinks this id is registered, but there is no entity at that key\n" +
                "Entity id in network message: " + message.getentityId()
                ;
                Globals.clientSceneWrapper.dumpTranslationLayerStatus();
                Globals.clientSceneWrapper.dumpIdData(message.getentityId());
                Globals.clientSceneWrapper.getScene().describeScene();
                throw new IllegalStateException(errorMessage);
            } else if(!Globals.clientSceneWrapper.containsServerId(message.getentityId()) && !Globals.clientSceneWrapper.hasBeenDeleted(message.getentityId())){
                //TODO: have client send query to server to resend this entity
                // String errorMessage = 
                // "Client received synchronization packet for entity that does not exists on client!\n" +
                // "This ID was never created on the client, yet the client is receiving a synchronization packet for it!\n" +
                // "Entity id in network message: " + message.getentityId()
                // ;
                // Globals.clientSceneWrapper.dumpTranslationLayerStatus();
                // Globals.clientSceneWrapper.dumpIdData(message.getentityId());
                // Globals.clientSceneWrapper.getScene().describeScene();
                // throw new IllegalStateException(errorMessage);
            }

            //warn if a message has bounced a certain number of times
            if(messageBounceCount.containsKey(message) && messageBounceCount.get(message) > MESSAGE_BOUNCE_WARNING_COUNT){
                String warningMessage =
                "A synchronization message has bounced at least " + MESSAGE_BOUNCE_WARNING_COUNT + "times!";
                LoggerInterface.loggerNetworking.WARNING(warningMessage);
            }
        }
        for(SynchronizationMessage message : messagesToClear){
            messages.remove(message);
        }
    }

    /**
     * <p> Automatically generated </p>
     * <p>
     * Updates the state of a given behavior tree on a given entity
     * </p>
     * @param entity The entity
     * @param bTreeId The id of the behavior tree
     * @param message The raw synchronization message holding the update data
     */
    private void updateEntityState(Entity entity, int bTreeId, SynchronizationMessage message){
        switch(bTreeId){
            case BehaviorTreeIdEnums.BTREE_SERVERATTACKTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERATTACKTREE_SYNCEDFIELD_STATE_ID:{
                        ClientAttackTree tree = ClientAttackTree.getClientAttackTree(entity);
                        tree.setState(ClientAttackTree.getAttackTreeStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                    case FieldIdEnums.TREE_SERVERATTACKTREE_SYNCEDFIELD_DRIFTSTATE_ID:{
                        ClientAttackTree tree = ClientAttackTree.getClientAttackTree(entity);
                        tree.setDriftState(ClientAttackTree.getAttackTreeDriftStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                    case FieldIdEnums.TREE_SERVERATTACKTREE_SYNCEDFIELD_CURRENTMOVEID_ID:{
                        ClientAttackTree tree = ClientAttackTree.getClientAttackTree(entity);
                        tree.setCurrentMoveId(message.getstringValue());
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERBLOCKTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERBLOCKTREE_SYNCEDFIELD_STATE_ID:{
                        ClientBlockTree tree = ClientBlockTree.getClientBlockTree(entity);
                        tree.setState(ClientBlockTree.getBlockStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                    case FieldIdEnums.TREE_SERVERBLOCKTREE_SYNCEDFIELD_CURRENTBLOCKVARIANT_ID:{
                        ClientBlockTree tree = ClientBlockTree.getClientBlockTree(entity);
                        tree.setCurrentBlockVariant(message.getstringValue());
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVEREQUIPSTATE_ID: {
                switch(message.getfieldId()){
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERGRAVITY_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERGRAVITY_SYNCEDFIELD_STATE_ID:{
                        ClientGravityTree tree = ClientGravityTree.getClientGravityTree(entity);
                        tree.setState(ClientGravityTree.getGravityTreeStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERIDLE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERIDLE_SYNCEDFIELD_STATE_ID:{
                        ClientIdleTree tree = ClientIdleTree.getClientIdleTree(entity);
                        tree.setState(ClientIdleTree.getIdleTreeStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERLIFETREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERLIFETREE_SYNCEDFIELD_STATE_ID:{
                        ClientLifeTree tree = ClientLifeTree.getClientLifeTree(entity);
                        tree.setState(ClientLifeTree.getLifeStateEnumShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERGROUNDMOVEMENTTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERGROUNDMOVEMENTTREE_SYNCEDFIELD_FACING_ID:{
                        ClientGroundMovementTree tree = ClientGroundMovementTree.getClientGroundMovementTree(entity);
                        tree.setFacing(ClientGroundMovementTree.getMovementRelativeFacingShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERJUMPTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERJUMPTREE_SYNCEDFIELD_STATE_ID:{
                        ClientJumpTree tree = ClientJumpTree.getClientJumpTree(entity);
                        tree.setState(ClientJumpTree.getJumpStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                    case FieldIdEnums.TREE_SERVERJUMPTREE_SYNCEDFIELD_CURRENTFRAME_ID:{
                        ClientJumpTree tree = ClientJumpTree.getClientJumpTree(entity);
                        tree.setCurrentFrame(message.getintValue());
                    } break;
                    case FieldIdEnums.TREE_SERVERJUMPTREE_SYNCEDFIELD_CURRENTJUMPFORCE_ID:{
                        ClientJumpTree tree = ClientJumpTree.getClientJumpTree(entity);
                        tree.setCurrentJumpForce(message.getfloatValue());
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERWALKTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERWALKTREE_SYNCEDFIELD_STATE_ID:{
                        ClientWalkTree tree = ClientWalkTree.getClientWalkTree(entity);
                        tree.setState(ClientWalkTree.getWalkStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
    
        }
    }

    /**
     * <p> Automatically generated </p>
     * <p>
     * Transitions a behavior tree to a new state
     * </p>
     * @param entity The entity
     * @param bTreeId The id of the behavior tree
     * @param message The raw synchronization message holding the update data
     */
    private void transitionBTree(Entity entity, int bTreeId, SynchronizationMessage message){
        switch(bTreeId){
            case BehaviorTreeIdEnums.BTREE_SERVERATTACKTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERATTACKTREE_SYNCEDFIELD_STATE_ID:{
                        ClientAttackTree tree = ClientAttackTree.getClientAttackTree(entity);
                        tree.transitionState(ClientAttackTree.getAttackTreeStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERBLOCKTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERBLOCKTREE_SYNCEDFIELD_STATE_ID:{
                        ClientBlockTree tree = ClientBlockTree.getClientBlockTree(entity);
                        tree.transitionState(ClientBlockTree.getBlockStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
            case BehaviorTreeIdEnums.BTREE_SERVERJUMPTREE_ID: {
                switch(message.getfieldId()){
                    case FieldIdEnums.TREE_SERVERJUMPTREE_SYNCEDFIELD_STATE_ID:{
                        ClientJumpTree tree = ClientJumpTree.getClientJumpTree(entity);
                        tree.transitionState(ClientJumpTree.getJumpStateShortAsEnum((short)message.getbTreeValue()));
                    } break;
                }
            } break;
    
        }
    }
    
}
