package electrosphere.entity.state.gravity;


import electrosphere.entity.EntityDataStrings;
import electrosphere.net.parser.net.message.SynchronizationMessage;
import electrosphere.server.datacell.utils.DataCellSearchUtils;
import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

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

import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.btree.BehaviorTree;
import electrosphere.entity.state.collidable.Impulse;
import electrosphere.entity.state.collidable.ServerCollidableTree;
import electrosphere.entity.state.gravity.ClientGravityTree.GravityTreeState;
import electrosphere.entity.state.movement.ServerFallTree;
import electrosphere.entity.state.movement.jump.ServerJumpTree;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.net.synchronization.annotation.SyncedField;
import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree;
import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums;
import electrosphere.net.synchronization.enums.FieldIdEnums;

@SynchronizedBehaviorTree(name = "serverGravity", isServer = true, correspondingTree="clientGravity")
/**
 * Tree for making the entity fall if there's nothing underneath it
 */
public class ServerGravityTree implements BehaviorTree {
    
    @SyncedField
    GravityTreeState state;
    
    Entity parent;

    int frameCurrent = 0;
    int fallFrame = 1;
    
    DBody body;
    Collidable collidable;
    
    List<EntityMessage> networkMessageQueue = new CopyOnWriteArrayList<EntityMessage>();
    
    private ServerGravityTree(Entity e, Object ... params){
        //Collidable collidable, DBody body, int fallFrame
        state = GravityTreeState.ACTIVE;
        parent = e;
        this.collidable = (Collidable)params[0];
        this.body = (DBody)params[1];
        this.fallFrame = (int)params[2];
    }
    
//    public void setCollisionObject(CollisionObject body, Collidable collidable){
//        this.body = body;
//        this.collidable = collidable;
//    }
    
    /**
     * <p> Automatically generated </p>
     * <p>
     * Gets state.
     * </p>
     */
    public GravityTreeState getState(){
        return state;
    }
    
    public void start(){
        //TODO: check if can start moving
        setState(GravityTreeState.ACTIVE);
        if(state == GravityTreeState.NOT_ACTIVE){
            frameCurrent = 0;
        }
    }
    
    public void interrupt(){
        setState(GravityTreeState.NOT_ACTIVE);
    }
    
    public void stop(){
        setState(GravityTreeState.NOT_ACTIVE);
    }
    
    static final float gravityConstant = 0.2f;
    static final float linearDamping = 0.1f;
    
    public void simulate(float deltaTime){
//        float velocity = CreatureUtils.getVelocity(parent);
//        float acceleration = CreatureUtils.getAcceleration(parent);
//        float maxNaturalVelocity = CreatureUtils.getMaxNaturalVelocity(parent);
//        Actor entityActor = EntityUtils.getActor(parent);
        Vector3d position = EntityUtils.getPosition(parent);
//        Vector3f movementVector = CreatureUtils.getMovementVector(parent);
        Quaterniond rotation = EntityUtils.getRotation(parent);
        Vector3f newPosition;
        ServerCollidableTree collidableTree = null;
        if(ServerCollidableTree.hasServerCollidableTree(parent)){
            collidableTree = ServerCollidableTree.getServerCollidableTree(parent);
        }

        //parse attached network messages
//        for(EntityMessage message : networkMessageQueue){
//            networkMessageQueue.remove(message);
////            System.out.println("MOVE to " + message.getX() + " " + message.getY() + " " + message.getZ());
//            switch(message.getMessageSubtype()){
//                case ATTACKUPDATE:
//                    switch(message.gettreeState()){
//                     case 0:
//                            state = IdleTreeState.IDLE;
//                            break;
//                        case 1:
//                            state = IdleTreeState.NOT_IDLE;
//                            break;
//                    }
//                    EntityUtils.getPosition(parent).set(message.getpositionX(),message.getpositionY(),message.getpositionZ());
//                    CreatureUtils.setMovementVector(parent, new Vector3f(message.getrotationX(),message.getrotationY(),message.getrotationZ()));
//                    break;
//            }
//        }

        
        //state machine
        switch(state){
            case ACTIVE:
                if(hadGroundCollision()){
                    setState(GravityTreeState.NOT_ACTIVE);
                    ServerJumpTree jumpTree;
                    if((jumpTree = ServerJumpTree.getServerJumpTree(parent))!=null){
                        jumpTree.land();
                    }
                    ServerFallTree fallTree;
                    if((fallTree = ServerFallTree.getFallTree(parent))!=null){
                        fallTree.land();
                    }
                    frameCurrent = 0;
                } else {
                    //animation nonsense
                    frameCurrent++;
                    if(frameCurrent == fallFrame){
                        ServerFallTree fallTree;
                        if((fallTree = ServerFallTree.getFallTree(parent))!=null){
                            fallTree.start();
                        }
                    }
                }
                break;
            case NOT_ACTIVE:
                if(hadEntityCollision()){
                    start();
                }
                //nothing here atm
                //eventually want to check if need to re-activate somehow
                break;
        }
    }

    public void addNetworkMessage(EntityMessage networkMessage) {
        networkMessageQueue.add(networkMessage);
    }
    
    public boolean hadStructureCollision(){
        boolean rVal = false;
        for(Impulse impulse : collidable.getImpulses()){
            if(impulse.getType().equals(Collidable.TYPE_STRUCTURE)){
                rVal = true;
                break;
            }
        }
        return rVal;
    }
    
    public boolean hadGroundCollision(){
        boolean rVal = false;
        for(Impulse impulse : collidable.getImpulses()){
            if(impulse.getType().equals(Collidable.TYPE_TERRAIN)){
                rVal = true;
                break;
            } else if(
                    impulse.getType().equals(Collidable.TYPE_STRUCTURE) &&
                    new Vector3d(impulse.getDirection()).normalize().y > 0.7
                    ){
                rVal = true;
            }
        }
        return rVal;
    }
    
    public boolean hadEntityCollision(){
        boolean rVal = false;
        for(Impulse impulse : collidable.getImpulses()){
            if(impulse.getType().equals(Collidable.TYPE_CREATURE)){
                rVal = true;
                break;
            }
        }
        return rVal;
    }
    
    /**
     * <p> Automatically generated </p>
     * <p>
     * Sets state and handles the synchronization logic for it.
     * </p>
     * @param state The value to set state to.
     */
    public void setState(GravityTreeState state){
        this.state = state;
        int value = ClientGravityTree.getGravityTreeStateEnumAsShort(state);
        DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage(SynchronizationMessage.constructUpdateClientStateMessage(parent.getId(), BehaviorTreeIdEnums.BTREE_SERVERGRAVITY_ID, FieldIdEnums.TREE_SERVERGRAVITY_SYNCEDFIELD_STATE_ID, value));
    }
    /**
     * <p> (initially) Automatically generated </p>
     * <p>
     * Attaches this tree to the entity.
     * </p>
     * @param entity The entity to attach to
     * @param tree The behavior tree to attach
     * @param params Optional parameters that will be provided to the constructor
     */
    public static ServerGravityTree attachTree(Entity parent, Object ... params){
        ServerGravityTree rVal = new ServerGravityTree(parent,params);
        //!!WARNING!! from here below should not be touched
        //This was generated automatically to properly alert various systems that the btree exists and should be tracked
        ServerBehaviorTreeUtils.attachBTreeToEntity(parent, rVal);
        parent.putData(EntityDataStrings.TREE_SERVERGRAVITY, rVal);
        Globals.entityValueTrackingService.attachTreeToEntity(parent, BehaviorTreeIdEnums.BTREE_SERVERGRAVITY_ID);
        return rVal;
    }
    /**
     * <p> Automatically generated </p>
     * <p>
     * Detatches this tree from the entity.
     * </p>
     * @param entity The entity to detach to
     * @param tree The behavior tree to detach
     */
    public static void detachTree(Entity entity, BehaviorTree tree){
        Globals.entityValueTrackingService.detatchTreeFromEntity(entity, BehaviorTreeIdEnums.BTREE_SERVERGRAVITY_ID);
    }
    /**
     * <p>
     * Gets the ServerGravityTree of the entity
     * </p>
     * @param entity the entity
     * @return The ServerGravityTree
     */
    public static ServerGravityTree getServerGravityTree(Entity entity){
        return (ServerGravityTree)entity.getData(EntityDataStrings.TREE_SERVERGRAVITY);
    }
}
