// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.npc.corecomponents.movement;

import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import com.hypixel.hytale.server.npc.navigation.AStarWithTarget;
import com.hypixel.hytale.server.npc.navigation.AStarNode;
import com.hypixel.hytale.server.npc.navigation.AStarBase;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider;
import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceWithTarget;
import com.hypixel.hytale.server.npc.movement.Steering;
import com.hypixel.hytale.server.npc.role.Role;
import java.util.logging.Level;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFindWithTarget;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFind;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForcePursue;

public class BodyMotionFind extends BodyMotionFindWithTarget
{
    protected final double distance;
    protected final double distanceSquared;
    protected final boolean reachable;
    protected final double heightDifferenceMin;
    protected final double heightDifferenceMax;
    protected final double abortDistance;
    protected final double abortDistanceSquared;
    protected final double switchToSteeringDistance;
    protected final double switchToSteeringDistanceSquared;
    protected final SteeringForcePursue seek;
    protected final Vector3d tempDirectionVector;
    protected double effectiveDistanceSquared;
    
    public BodyMotionFind(@Nonnull final BuilderBodyMotionFind builderMotionFind, @Nonnull final BuilderSupport support) {
        super(builderMotionFind, support);
        this.seek = new SteeringForcePursue();
        this.tempDirectionVector = new Vector3d();
        this.reachable = builderMotionFind.getReachable(support);
        this.distance = builderMotionFind.getStopDistance(support);
        this.distanceSquared = this.distance * this.distance;
        final double[] heightDifference = builderMotionFind.getHeightDifference(support);
        this.heightDifferenceMin = heightDifference[0];
        this.heightDifferenceMax = heightDifference[1];
        this.seek.setDistances(builderMotionFind.getSlowDownDistance(support), builderMotionFind.getStopDistance(support));
        this.seek.setFalloff(builderMotionFind.getFalloff(support));
        this.abortDistance = builderMotionFind.getAbortDistance(support);
        this.abortDistanceSquared = this.abortDistance * this.abortDistance;
        this.switchToSteeringDistance = builderMotionFind.getSwitchToSteeringDistance(support);
        this.switchToSteeringDistanceSquared = this.switchToSteeringDistance * this.switchToSteeringDistance;
    }
    
    @Override
    protected boolean canSwitchToSteering(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Vector3d position = transformComponent.getPosition();
        if (motionController.waypointDistanceSquared(position, this.getLastTargetPosition()) > this.switchToSteeringDistanceSquared) {
            return false;
        }
        if (this.dbgMotionState) {
            NPCPlugin.get().getLogger().at(Level.INFO).every(100).log("MotionFind: computing canSwitchToSteering");
        }
        return this.canReachTarget(ref, motionController, position, this.getLastAccessibleTargetPosition(motionController, true, componentAccessor), componentAccessor);
    }
    
    @Override
    protected boolean shouldSkipSteering(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController activeMotionController, @Nonnull final Vector3d position, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final Vector3d targetPosition = this.getLastAccessibleTargetPosition(activeMotionController, true, componentAccessor);
        this.probeMoveData.setPosition(position).setTargetPosition(targetPosition);
        activeMotionController.probeMove(ref, this.probeMoveData, componentAccessor);
        return !this.isGoalReached(ref, activeMotionController, this.probeMoveData.probePosition, targetPosition, componentAccessor);
    }
    
    @Override
    protected boolean computeSteering(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, @Nonnull final Vector3d position, @Nonnull final Steering desiredSteering, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.seek.setPositions(position, this.getLastTargetPosition());
        final MotionController motionController = role.getActiveMotionController();
        this.seek.setComponentSelector(motionController.getComponentSelector());
        final double desiredAltitudeWeight = (this.desiredAltitudeWeight >= 0.0) ? this.desiredAltitudeWeight : motionController.getDesiredAltitudeWeight();
        return this.scaleSteering(ref, role, this.seek, desiredSteering, desiredAltitudeWeight, componentAccessor);
    }
    
    @Override
    public boolean canComputeMotion(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, @Nullable final InfoProvider infoProvider, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (!super.canComputeMotion(ref, role, infoProvider, componentAccessor) || (this.abortDistance > 0.0 && role.getActiveMotionController().waypointDistanceSquared(ref, this.getLastTargetPosition(), componentAccessor) >= this.abortDistanceSquared)) {
            return false;
        }
        if (this.selfBoundingBox != null && this.adjustRangeByHitboxSize) {
            double effectiveDistance = this.distance + Math.max(this.selfBoundingBox.width(), this.selfBoundingBox.depth());
            if (this.targetBoundingBox != null) {
                effectiveDistance += Math.max(this.targetBoundingBox.width(), this.targetBoundingBox.depth());
            }
            this.effectiveDistanceSquared = effectiveDistance * effectiveDistance;
        }
        else {
            this.effectiveDistanceSquared = this.distanceSquared;
        }
        return this.selfBoundingBox != null;
    }
    
    @Override
    protected boolean isGoalReached(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, @Nonnull final Vector3d position, @Nonnull final Vector3d targetPosition, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final double differenceY = targetPosition.y - position.y;
        return differenceY >= this.heightDifferenceMin && differenceY <= this.heightDifferenceMax && motionController.waypointDistanceSquared(position, targetPosition) <= this.effectiveDistanceSquared && (!this.reachable || this.canReachTarget(ref, motionController, position, targetPosition, componentAccessor));
    }
    
    @Override
    public boolean isGoalReached(@Nonnull final Ref<EntityStore> ref, final AStarBase aStarBase, @Nonnull final AStarNode aStarNode, @Nonnull final MotionController motionController, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final AStarWithTarget aStarWithTarget = (AStarWithTarget)aStarBase;
        return this.isGoalReached(ref, motionController, aStarNode.getPosition(), aStarWithTarget.getTargetPosition(), componentAccessor);
    }
    
    @Override
    public float estimateToGoal(@Nonnull final AStarBase aStarBase, @Nonnull final Vector3d fromPosition, final MotionController motionController) {
        return (float)((AStarWithTarget)aStarBase).getTargetPosition().distanceTo(fromPosition);
    }
    
    @Override
    public void findBestPath(@Nonnull final AStarBase aStarBase, final MotionController controller) {
        aStarBase.buildBestPath(AStarNode::getEstimateToGoal, (oldV, v) -> v < oldV, Float.MAX_VALUE);
    }
    
    @Override
    protected void onThrottling(final MotionController motionController, @Nonnull final Ref<EntityStore> ref, @Nonnull final Steering steering, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        super.onThrottling(motionController, ref, steering, componentAccessor);
        this.lookAtTarget(ref, steering, componentAccessor);
    }
    
    @Override
    protected void onDeferring(final MotionController motionController, @Nonnull final Ref<EntityStore> ref, @Nonnull final Steering steering, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        super.onDeferring(motionController, ref, steering, componentAccessor);
        this.lookAtTarget(ref, steering, componentAccessor);
    }
    
    protected void lookAtTarget(@Nonnull final Ref<EntityStore> ref, @Nonnull final Steering steering, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Vector3d position = transformComponent.getPosition();
        final Vector3f bodyRotation = transformComponent.getRotation();
        this.tempDirectionVector.assign(this.getLastTargetPosition()).subtract(position);
        steering.setYaw(NPCPhysicsMath.headingFromDirection(this.tempDirectionVector.x, this.tempDirectionVector.z, bodyRotation.getYaw()));
        steering.setPitch(NPCPhysicsMath.pitchFromDirection(this.tempDirectionVector.x, this.tempDirectionVector.y, this.tempDirectionVector.z, bodyRotation.getPitch()));
    }
    
    protected boolean canReachTarget(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, @Nonnull final Vector3d position, @Nonnull final Vector3d targetPosition, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (this.isBoundingBoxesOverlapping(position, targetPosition)) {
            return true;
        }
        final Vector3d direction = this.tempDirectionVector.assign(targetPosition).subtract(position);
        motionController.probeMove(ref, position, direction, this.probeMoveData, componentAccessor);
        return this.isBoundingBoxesOverlapping(this.probeMoveData.probePosition, targetPosition);
    }
    
    protected boolean isBoundingBoxesOverlapping(@Nonnull final Vector3d position, @Nonnull final Vector3d endPosition) {
        if (this.targetBoundingBox == null) {
            return this.containsPosition(position, endPosition);
        }
        return containsPosition(position.x, this.selfBoundingBox.min.x - this.targetBoundingBox.max.x, this.selfBoundingBox.max.x - this.targetBoundingBox.min.x, endPosition.x) && containsPosition(position.y, this.selfBoundingBox.min.y - this.targetBoundingBox.max.y, this.selfBoundingBox.max.y - this.targetBoundingBox.min.y, endPosition.y) && containsPosition(position.z, this.selfBoundingBox.min.z - this.targetBoundingBox.max.z, this.selfBoundingBox.max.z - this.targetBoundingBox.min.z, endPosition.z);
    }
    
    protected boolean containsPosition(@Nonnull final Vector3d position, @Nonnull final Vector3d endPosition) {
        return containsPosition(position.x, this.selfBoundingBox.min.x, this.selfBoundingBox.max.x, endPosition.x) && containsPosition(position.y, this.selfBoundingBox.min.y, this.selfBoundingBox.max.y, endPosition.y) && containsPosition(position.z, this.selfBoundingBox.min.z, this.selfBoundingBox.max.z, endPosition.z);
    }
    
    protected static boolean containsPosition(final double p, final double min, final double max, double v) {
        v -= p;
        return v >= min && v < max;
    }
}
