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

package com.hypixel.hytale.server.core.modules.projectile.config;

import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.modules.physics.component.Velocity;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.math.util.NearestBlockUtil;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.modules.collision.BlockData;
import com.hypixel.hytale.server.core.modules.collision.BlockContactData;
import com.hypixel.hytale.server.core.entity.InteractionChain;
import com.hypixel.hytale.server.core.meta.DynamicMetaStore;
import com.hypixel.hytale.server.core.entity.Entity;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.math.vector.Vector4d;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.server.core.modules.interaction.InteractionModule;
import com.hypixel.hytale.server.core.entity.InteractionManager;
import com.hypixel.hytale.server.core.entity.LivingEntity;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.entity.EntityUtils;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdaterSymplecticEuler;
import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderStandardState;
import com.hypixel.hytale.server.core.modules.physics.util.ForceProvider;
import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderEntity;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyState;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdater;
import java.util.UUID;
import com.hypixel.hytale.math.vector.Vector3d;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.modules.physics.RestingSupport;
import com.hypixel.hytale.server.core.modules.collision.BlockTracker;
import com.hypixel.hytale.server.core.modules.collision.EntityRefCollisionProvider;
import com.hypixel.hytale.server.core.modules.collision.BlockCollisionProvider;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.server.core.modules.collision.IBlockCollisionConsumer;

public class StandardPhysicsProvider implements IBlockCollisionConsumer, Component<EntityStore>
{
    public static final int WATER_DETECTION_EXTREMA_COUNT = 2;
    public static final double MIN_BOUNCE_EPSILON = 0.4;
    public static final double MIN_BOUNCE_EPSILON_SQUARED = 0.16000000000000003;
    @Nonnull
    protected static final HytaleLogger LOGGER;
    @Nonnull
    protected final BlockCollisionProvider blockCollisionProvider;
    @Nonnull
    protected final EntityRefCollisionProvider entityCollisionProvider;
    @Nonnull
    protected final BlockTracker triggerTracker;
    @Nonnull
    protected final RestingSupport restingSupport;
    @Nullable
    protected World world;
    @Nonnull
    protected final Vector3d velocity;
    @Nonnull
    protected final Vector3d position;
    @Nonnull
    protected final Vector3d movement;
    protected final Vector3d nextMovement;
    protected boolean bounced;
    protected int bounces;
    protected boolean onGround;
    protected boolean provideCharacterCollisions;
    @Nullable
    protected final UUID creatorUuid;
    @Nonnull
    protected final StandardPhysicsConfig physicsConfig;
    protected final Vector3d tempVector;
    @Nullable
    protected BounceConsumer bounceConsumer;
    @Nullable
    protected ImpactConsumer impactConsumer;
    protected boolean movedInsideSolid;
    protected final Vector3d moveOutOfSolidVelocity;
    protected final Vector3d contactPosition;
    protected final Vector3d contactNormal;
    protected double collisionStart;
    protected final PhysicsBodyStateUpdater stateUpdater;
    protected final PhysicsBodyState stateBefore;
    protected final PhysicsBodyState stateAfter;
    protected double displacedMass;
    protected double subSurfaceVolume;
    protected double enterFluid;
    protected double leaveFluid;
    protected boolean inFluid;
    protected int velocityExtremaCount;
    @Nonnull
    protected STATE state;
    protected ForceProviderEntity forceProviderEntity;
    protected ForceProvider[] forceProviders;
    protected final ForceProviderStandardState forceProviderStandardState;
    protected double dragMultiplier;
    protected double dragOffset;
    protected final BlockTracker fluidTracker;
    protected boolean isSliding;
    @Deprecated(forRemoval = true)
    protected BoundingBox boundingBox;
    
    @Nonnull
    public static ComponentType<EntityStore, StandardPhysicsProvider> getComponentType() {
        return ProjectileModule.get().getStandardPhysicsProviderComponentType();
    }
    
    public StandardPhysicsProvider(@Nonnull final BoundingBox boundingBox, @Nullable final UUID creatorUuid, @Nonnull final StandardPhysicsConfig physicsConfig, @Nonnull final Vector3d initialForce, final boolean predicted) {
        this.nextMovement = new Vector3d();
        this.bounces = 0;
        this.provideCharacterCollisions = true;
        this.tempVector = new Vector3d();
        this.moveOutOfSolidVelocity = new Vector3d();
        this.contactPosition = new Vector3d();
        this.contactNormal = new Vector3d();
        this.stateUpdater = new PhysicsBodyStateUpdaterSymplecticEuler();
        this.stateBefore = new PhysicsBodyState();
        this.stateAfter = new PhysicsBodyState();
        this.velocityExtremaCount = Integer.MAX_VALUE;
        this.state = STATE.ACTIVE;
        this.forceProviderStandardState = new ForceProviderStandardState();
        this.fluidTracker = new BlockTracker();
        this.creatorUuid = creatorUuid;
        this.physicsConfig = physicsConfig;
        (this.blockCollisionProvider = new BlockCollisionProvider()).setRequestedCollisionMaterials(6);
        this.blockCollisionProvider.setReportOverlaps(true);
        this.entityCollisionProvider = new EntityRefCollisionProvider();
        this.triggerTracker = new BlockTracker();
        this.restingSupport = new RestingSupport();
        this.velocity = new Vector3d();
        this.position = new Vector3d();
        this.movement = new Vector3d();
        this.boundingBox = boundingBox;
        (this.forceProviderEntity = new ForceProviderEntity(boundingBox)).setDensity(physicsConfig.density);
        this.forceProviders = new ForceProvider[] { this.forceProviderEntity };
        this.forceProviderStandardState.nextTickVelocity.assign(initialForce);
        this.recomputeDragFactors(boundingBox);
        if (!predicted) {
            this.impactConsumer = ((ref, position, targetRef, collisionDetailName, commandBuffer) -> {
                if (creatorUuid == null) {
                    return;
                }
                else {
                    final Ref<EntityStore> creatorRef = commandBuffer.getExternalData().getRefFromUUID(creatorUuid);
                    if (creatorRef != null && creatorRef.isValid()) {
                        final Entity patt0$temp = EntityUtils.getEntity(creatorRef, commandBuffer);
                        if (patt0$temp instanceof final LivingEntity livingEntity) {
                            final InteractionManager interactionManagerComponent = commandBuffer.getComponent(creatorRef, InteractionModule.get().getInteractionManagerComponent());
                            if (interactionManagerComponent == null) {
                                commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                                return;
                            }
                            else {
                                final InteractionContext context = InteractionContext.forProxyEntity(interactionManagerComponent, livingEntity, ref);
                                final DynamicMetaStore<InteractionContext> metaStore = context.getMetaStore();
                                metaStore.putMetaObject(Interaction.TARGET_ENTITY, targetRef);
                                metaStore.putMetaObject(Interaction.HIT_LOCATION, new Vector4d(position.x, position.y, position.z, 1.0));
                                metaStore.putMetaObject(Interaction.HIT_DETAIL, collisionDetailName);
                                final InteractionType interactionType = (targetRef != null) ? InteractionType.ProjectileHit : InteractionType.ProjectileMiss;
                                final String rootInteractionId = context.getRootInteractionId(interactionType);
                                if (rootInteractionId == null) {
                                    return;
                                }
                                else {
                                    final RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId);
                                    if (rootInteraction == null) {
                                        return;
                                    }
                                    else {
                                        final InteractionChain chain = interactionManagerComponent.initChain(interactionType, context, rootInteraction, true);
                                        interactionManagerComponent.queueExecuteChain(chain);
                                        return;
                                    }
                                }
                            }
                        }
                    }
                    commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                    return;
                }
            });
            this.bounceConsumer = ((ref, position, commandBuffer) -> {
                if (creatorUuid != null) {
                    final Ref<EntityStore> creatorRef2 = commandBuffer.getExternalData().getRefFromUUID(creatorUuid);
                    if (creatorRef2 != null && creatorRef2.isValid()) {
                        final Entity patt0$temp2 = EntityUtils.getEntity(creatorRef2, commandBuffer);
                        if (patt0$temp2 instanceof final LivingEntity livingEntity2) {
                            final InteractionManager interactionManagerComponent2 = commandBuffer.getComponent(creatorRef2, InteractionModule.get().getInteractionManagerComponent());
                            if (interactionManagerComponent2 == null) {
                                commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                                return;
                            }
                            else {
                                final InteractionContext context2 = InteractionContext.forProxyEntity(interactionManagerComponent2, livingEntity2, ref);
                                context2.getMetaStore().putMetaObject(Interaction.HIT_LOCATION, new Vector4d(position.x, position.y, position.z, 1.0));
                                final InteractionType interactionType2 = InteractionType.ProjectileBounce;
                                final String rootInteractionId2 = context2.getRootInteractionId(interactionType2);
                                if (rootInteractionId2 == null) {
                                    return;
                                }
                                else {
                                    final RootInteraction rootInteraction2 = RootInteraction.getRootInteractionOrUnknown(rootInteractionId2);
                                    if (rootInteraction2 == null) {
                                        return;
                                    }
                                    else {
                                        final InteractionChain chain2 = interactionManagerComponent2.initChain(interactionType2, context2, rootInteraction2, true);
                                        interactionManagerComponent2.queueExecuteChain(chain2);
                                        return;
                                    }
                                }
                            }
                        }
                    }
                    commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                }
            });
        }
    }
    
    @Nonnull
    @Override
    public Result onCollision(final int blockX, final int blockY, final int blockZ, @Nonnull final Vector3d direction, @Nonnull final BlockContactData contactData, @Nonnull final BlockData blockData, @Nonnull final Box collider) {
        final BlockMaterial blockMaterial = blockData.getBlockType().getMaterial();
        if (this.physicsConfig.moveOutOfSolidSpeed > 0.0 && contactData.isOverlapping() && blockMaterial == BlockMaterial.Solid) {
            final Vector3i nearestBlock = NearestBlockUtil.findNearestBlock(this.position, (block, w) -> {
                final WorldChunk worldChunk = w.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(block.getX(), block.getZ()));
                return worldChunk != null && worldChunk.getBlockType(block).getMaterial() != BlockMaterial.Solid;
            }, this.world);
            if (nearestBlock != null) {
                this.tempVector.assign(nearestBlock.x, nearestBlock.y, nearestBlock.z);
                this.tempVector.add(0.5, 0.5, 0.5);
                this.tempVector.subtract(this.position);
                this.tempVector.setLength(this.physicsConfig.moveOutOfSolidSpeed);
            }
            else {
                this.tempVector.assign(0.0, this.physicsConfig.moveOutOfSolidSpeed, 0.0);
            }
            this.moveOutOfSolidVelocity.add(this.tempVector);
            this.movedInsideSolid = true;
            return Result.CONTINUE;
        }
        if (blockData.getFluidId() != 0 && !this.fluidTracker.isTracked(blockX, blockY, blockZ)) {
            final double collisionStart = contactData.getCollisionStart();
            final double collisionEnd = contactData.getCollisionEnd();
            if (collisionStart < this.enterFluid) {
                this.enterFluid = collisionStart;
            }
            if (collisionEnd > this.leaveFluid) {
                this.leaveFluid = collisionEnd;
            }
            if (collisionEnd <= collisionStart) {
                return Result.CONTINUE;
            }
            final double density = 1000.0;
            final double volume = PhysicsMath.volumeOfIntersection(this.boundingBox.getBoundingBox(), this.contactPosition, collider, blockX, blockY, blockZ);
            this.subSurfaceVolume += volume;
            this.displacedMass += volume * density;
            this.fluidTracker.trackNew(blockX, blockY, blockZ);
            return Result.CONTINUE;
        }
        else {
            if (contactData.isOverlapping()) {
                return Result.CONTINUE;
            }
            final double surfaceAlignment = direction.dot(contactData.getCollisionNormal());
            if (blockMaterial != BlockMaterial.Solid || surfaceAlignment == 0.0) {}
            if (surfaceAlignment >= 0.0) {
                return Result.CONTINUE;
            }
            this.contactPosition.assign(contactData.getCollisionPoint());
            this.contactNormal.assign(contactData.getCollisionNormal());
            if (this.physicsConfig.allowRolling) {
                final Vector3d remaining = this.stateBefore.position.clone().add(this.movement).subtract(this.contactPosition);
                if (!remaining.equals(Vector3d.ZERO)) {
                    final double t = remaining.dot(this.contactNormal);
                    this.nextMovement.assign(remaining);
                    this.nextMovement.addScaled(this.contactNormal, -t);
                    this.isSliding = true;
                }
            }
            this.collisionStart = contactData.getCollisionStart();
            this.bounced = true;
            return Result.STOP;
        }
    }
    
    @Nonnull
    @Override
    public Result probeCollisionDamage(final int blockX, final int blockY, final int blockZ, final Vector3d direction, final BlockContactData collisionData, final BlockData blockData) {
        return Result.CONTINUE;
    }
    
    @Override
    public void onCollisionDamage(final int blockX, final int blockY, final int blockZ, final Vector3d direction, final BlockContactData collisionData, final BlockData blockData) {
    }
    
    @Nonnull
    @Override
    public Result onCollisionSliceFinished() {
        return Result.CONTINUE;
    }
    
    @Override
    public void onCollisionFinished() {
    }
    
    public void finishTick(@Nonnull final TransformComponent position, @Nonnull final Velocity velocity) {
        position.setPosition(this.position);
        velocity.set(this.velocity);
        this.world = null;
        this.entityCollisionProvider.clear();
    }
    
    public void rotateBody(final double dt, @Nonnull final Vector3f bodyRotation) {
        if (!this.physicsConfig.computeYaw && !this.physicsConfig.computePitch) {
            return;
        }
        final double vx = this.stateAfter.velocity.x;
        final double vz = this.stateAfter.velocity.z;
        if (vx * vx + vz * vz <= 1.0000000000000002E-10) {
            return;
        }
        switch (this.physicsConfig.rotationMode) {
            case Velocity: {
                if (this.physicsConfig.computeYaw) {
                    bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz)));
                }
                if (this.physicsConfig.computePitch) {
                    bodyRotation.setPitch(PhysicsMath.pitchFromDirection(vx, this.stateAfter.velocity.y, vz));
                    break;
                }
                break;
            }
            case VelocityDamped: {
                if (this.physicsConfig.computeYaw) {
                    bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz)));
                }
                if (this.physicsConfig.computePitch) {
                    final float pitch = bodyRotation.getPitch();
                    float targetPitch = PhysicsMath.pitchFromDirection(vx, this.velocity.y, vz);
                    float delta = PhysicsMath.normalizeTurnAngle(targetPitch - pitch);
                    final float maxDelta = (float)(this.velocity.squaredLength() * dt * this.physicsConfig.speedRotationFactor);
                    if (delta > maxDelta) {
                        targetPitch = pitch + maxDelta;
                        delta = maxDelta;
                    }
                    else if (delta < -maxDelta) {
                        targetPitch = pitch - maxDelta;
                        delta = maxDelta;
                    }
                    bodyRotation.setPitch(targetPitch);
                    this.forceProviderStandardState.externalForce.addScaled(this.stateAfter.velocity, delta * -this.physicsConfig.rotationForce);
                    break;
                }
                break;
            }
            case VelocityRoll: {
                bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz)));
                bodyRotation.setPitch(bodyRotation.getPitch() - (float)this.stateBefore.velocity.length() * this.physicsConfig.rollingSpeed);
                break;
            }
        }
    }
    
    public boolean isOnGround() {
        return this.onGround;
    }
    
    public boolean isSwimming() {
        return this.velocityExtremaCount <= 0;
    }
    
    public double getDragCoefficient(final double density) {
        return this.dragMultiplier * density + this.dragOffset;
    }
    
    protected void recomputeDragFactors(@Nonnull final BoundingBox boundingBoxComponent) {
        final Box boundingBox = boundingBoxComponent.getBoundingBox();
        final double area = boundingBox.width() * boundingBox.depth();
        final double mass = this.forceProviderEntity.getMass(boundingBox.getVolume());
        final double drag1 = PhysicsMath.computeDragCoefficient(this.physicsConfig.terminalVelocityAir, area, mass, this.physicsConfig.gravity);
        final double drag2 = PhysicsMath.computeDragCoefficient(this.physicsConfig.terminalVelocityWater, area, mass, this.physicsConfig.gravity);
        this.dragMultiplier = (drag2 - drag1) / (this.physicsConfig.densityWater - this.physicsConfig.densityAir);
        this.dragOffset = drag1 - this.dragMultiplier * this.physicsConfig.densityAir;
    }
    
    @Nonnull
    public STATE getState() {
        return this.state;
    }
    
    public void setState(@Nonnull final STATE state) {
        this.state = state;
    }
    
    @Nonnull
    public StandardPhysicsConfig getPhysicsConfig() {
        return this.physicsConfig;
    }
    
    @Nonnull
    public ForceProviderStandardState getForceProviderStandardState() {
        return this.forceProviderStandardState;
    }
    
    @Nonnull
    public RestingSupport getRestingSupport() {
        return this.restingSupport;
    }
    
    public void setWorld(@Nullable final World world) {
        this.world = world;
    }
    
    @Nonnull
    public Vector3d getPosition() {
        return this.position;
    }
    
    @Nonnull
    public Vector3d getVelocity() {
        return this.velocity;
    }
    
    @Nonnull
    public Vector3d getMovement() {
        return this.movement;
    }
    
    @Nonnull
    public Vector3d getNextMovement() {
        return this.nextMovement;
    }
    
    @Nonnull
    public ForceProviderEntity getForceProviderEntity() {
        return this.forceProviderEntity;
    }
    
    @Nonnull
    public ForceProvider[] getForceProviders() {
        return this.forceProviders;
    }
    
    @Nonnull
    public PhysicsBodyStateUpdater getStateUpdater() {
        return this.stateUpdater;
    }
    
    @Nonnull
    public PhysicsBodyState getStateBefore() {
        return this.stateBefore;
    }
    
    @Nonnull
    public PhysicsBodyState getStateAfter() {
        return this.stateAfter;
    }
    
    public boolean isProvidesCharacterCollisions() {
        return this.provideCharacterCollisions;
    }
    
    @Nullable
    public UUID getCreatorUuid() {
        return this.creatorUuid;
    }
    
    @Nonnull
    public EntityRefCollisionProvider getEntityCollisionProvider() {
        return this.entityCollisionProvider;
    }
    
    public boolean isBounced() {
        return this.bounced;
    }
    
    public void setBounced(final boolean bounced) {
        this.bounced = bounced;
    }
    
    public int getBounces() {
        return this.bounces;
    }
    
    public void incrementBounces() {
        ++this.bounces;
    }
    
    @Nonnull
    public Vector3d getMoveOutOfSolidVelocity() {
        return this.moveOutOfSolidVelocity;
    }
    
    public boolean isMovedInsideSolid() {
        return this.movedInsideSolid;
    }
    
    public void setMovedInsideSolid(final boolean movedInsideSolid) {
        this.movedInsideSolid = movedInsideSolid;
    }
    
    public double getDisplacedMass() {
        return this.displacedMass;
    }
    
    public void setDisplacedMass(final double displacedMass) {
        this.displacedMass = displacedMass;
    }
    
    public double getSubSurfaceVolume() {
        return this.subSurfaceVolume;
    }
    
    public void setSubSurfaceVolume(final double subSurfaceVolume) {
        this.subSurfaceVolume = subSurfaceVolume;
    }
    
    public double getEnterFluid() {
        return this.enterFluid;
    }
    
    public void setEnterFluid(final double enterFluid) {
        this.enterFluid = enterFluid;
    }
    
    public double getLeaveFluid() {
        return this.leaveFluid;
    }
    
    public void setLeaveFluid(final double leaveFluid) {
        this.leaveFluid = leaveFluid;
    }
    
    public double getCollisionStart() {
        return this.collisionStart;
    }
    
    public void setCollisionStart(final double collisionStart) {
        this.collisionStart = collisionStart;
    }
    
    @Nonnull
    public Vector3d getContactPosition() {
        return this.contactPosition;
    }
    
    @Nonnull
    public Vector3d getContactNormal() {
        return this.contactNormal;
    }
    
    public boolean isSliding() {
        return this.isSliding;
    }
    
    public void setSliding(final boolean sliding) {
        this.isSliding = sliding;
    }
    
    @Nonnull
    public BlockCollisionProvider getBlockCollisionProvider() {
        return this.blockCollisionProvider;
    }
    
    @Nonnull
    public BlockTracker getTriggerTracker() {
        return this.triggerTracker;
    }
    
    @Nonnull
    public BlockTracker getFluidTracker() {
        return this.fluidTracker;
    }
    
    public boolean isInFluid() {
        return this.inFluid;
    }
    
    public void setInFluid(final boolean inFluid) {
        this.inFluid = inFluid;
    }
    
    public int getVelocityExtremaCount() {
        return this.velocityExtremaCount;
    }
    
    public void setVelocityExtremaCount(final int velocityExtremaCount) {
        this.velocityExtremaCount = velocityExtremaCount;
    }
    
    public void decrementVelocityExtremaCount() {
        --this.velocityExtremaCount;
    }
    
    public void setOnGround(final boolean onGround) {
        this.onGround = onGround;
    }
    
    @Nullable
    public ImpactConsumer getImpactConsumer() {
        return this.impactConsumer;
    }
    
    @Nullable
    public BounceConsumer getBounceConsumer() {
        return this.bounceConsumer;
    }
    
    @Nonnull
    @Override
    public Component<EntityStore> clone() {
        return this;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public enum STATE
    {
        ACTIVE, 
        RESTING, 
        INACTIVE;
    }
}
