package electrosphere.entity.state.gravity;


import electrosphere.net.parser.net.message.SynchronizationMessage;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

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

import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.btree.BehaviorTree;
import electrosphere.entity.state.collidable.Impulse;
import electrosphere.entity.state.movement.FallTree;
import electrosphere.entity.state.movement.jump.ClientJumpTree;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.net.synchronization.annotation.SyncedField;
import electrosphere.net.synchronization.annotation.SynchronizableEnum;
import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree;
import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums;

@SynchronizedBehaviorTree(name = "clientGravity", isServer = false, correspondingTree="serverGravity")
/**
 * Tree for making the entity fall if there's nothing underneath it
 */
public class ClientGravityTree implements BehaviorTree {
    
    @SynchronizableEnum
    public static enum GravityTreeState {
        ACTIVE,
        NOT_ACTIVE,
    }
    
    @SyncedField
    GravityTreeState state;
    
    Entity parent;

    int frameCurrent = 0;
    int fallFrame = 1;
    
    DBody body;
    Collidable collidable;
    
    List<EntityMessage> networkMessageQueue = new CopyOnWriteArrayList<EntityMessage>();
    
    private ClientGravityTree(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(){
        state = GravityTreeState.ACTIVE;
    }
    
    
    public void interrupt(){
        state = GravityTreeState.NOT_ACTIVE;
    }
    
    public void stop(){
        state = GravityTreeState.NOT_ACTIVE;
    }
    
    static final float gravityConstant = 0.2f;
    static final float linearDamping = 0.1f;
    
    public void simulate(float deltaTime){
        
        //state machine
        switch(state){
            case ACTIVE:
                if(hadGroundCollision()){
                    state = GravityTreeState.NOT_ACTIVE;
                    if(!hadStructureCollision()){
//                        position.set(new Vector3d(position.x,Globals.commonWorldData.getElevationAtPoint(position) + 0.0001f,position.z));
                    }
                    ClientJumpTree jumpTree;
                    if((jumpTree = ClientJumpTree.getClientJumpTree(parent))!=null){
                        jumpTree.land();
                    }
                    FallTree fallTree;
                    if((fallTree = FallTree.getFallTree(parent))!=null){
                        fallTree.land();
                    }
                    frameCurrent = 0;
                } else {
                    //animation nonsense
                    frameCurrent++;
                    if(frameCurrent == fallFrame){
                        FallTree fallTree;
                        if((fallTree = FallTree.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> (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 ClientGravityTree attachTree(Entity parent, Object ... params){
        ClientGravityTree rVal = new ClientGravityTree(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
        parent.putData(EntityDataStrings.TREE_CLIENTGRAVITY, rVal);
        Globals.clientSceneWrapper.getScene().registerBehaviorTree(rVal);
        Globals.entityValueTrackingService.attachTreeToEntity(parent, BehaviorTreeIdEnums.BTREE_CLIENTGRAVITY_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_CLIENTGRAVITY_ID);
    }
    /**
     * <p>
     * Gets the ClientGravityTree of the entity
     * </p>
     * @param entity the entity
     * @return The ClientGravityTree
     */
    public static ClientGravityTree getClientGravityTree(Entity entity){
        return (ClientGravityTree)entity.getData(EntityDataStrings.TREE_CLIENTGRAVITY);
    }
    /**
     * <p> Automatically generated </p>
     * <p>
     * Converts this enum type to an equivalent short value
     * </p>
     * @param enumVal The enum value
     * @return The short value
     */
    public static short getGravityTreeStateEnumAsShort(GravityTreeState enumVal){
        switch(enumVal){
            case ACTIVE:
                return 0;
            case NOT_ACTIVE:
                return 1;
            default:
                return 0;
        }
    }
    /**
     * <p> Automatically generated </p>
     * <p>
     * Converts a short to the equivalent enum value
     * </p>
     * @param shortVal The short value
     * @return The enum value
     */
    public static GravityTreeState getGravityTreeStateShortAsEnum(short shortVal){
        switch(shortVal){
            case 0:
                return GravityTreeState.ACTIVE;
            case 1:
                return GravityTreeState.NOT_ACTIVE;
            default:
                return GravityTreeState.ACTIVE;
        }
    }
    /**
     * <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;
    }
}
