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

package com.hypixel.hytale.server.core.entity;

import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.server.combat.Knockback;
import com.hypixel.hytale.server.core.entity.knockback.KnockbackComponent;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
import com.hypixel.hytale.server.core.modules.physics.component.Velocity;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockGathering;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import java.util.Iterator;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemTool;
import java.util.List;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.util.TargetUtil;
import com.hypixel.hytale.math.block.BlockSphereUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3i;
import java.util.function.Consumer;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.selector.Selector;
import java.util.Objects;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.concurrent.ThreadLocalRandom;
import java.util.Set;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.CommandBuffer;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.math.vector.Vector3d;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
import com.hypixel.hytale.math.vector.Vector3f;

public class ExplosionUtils
{
    private static final boolean DEBUG_SHAPES = false;
    private static final Vector3f DEBUG_POTENTIAL_TARGET_COLOR;
    private static final int DEBUG_POTENTIAL_TARGET_TIME = 5;
    private static final float DEBUG_BLOCK_HIT_SCALE = 1.1f;
    private static final float DEBUG_BLOCK_HIT_TIME = 2.0f;
    private static final float DEBUG_BLOCK_HIT_ALPHA = 0.25f;
    private static final Vector3f DEBUG_BLOCK_RADIUS_COLOR;
    private static final Vector3f DEBUG_ENTITY_RADIUS_COLOR;
    private static final int DEBUG_BLOCK_RADIUS_TIME = 5;
    private static final int DEBUG_ENTITY_RADIUS_TIME = 5;
    
    public static void performExplosion(@Nonnull final Damage.Source damageSource, @Nonnull final Vector3d position, @Nonnull final ExplosionConfig config, @Nullable final Ref<EntityStore> ignoreRef, @Nonnull final CommandBuffer<EntityStore> commandBuffer, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        if (!config.damageBlocks && !config.damageEntities) {
            return;
        }
        final Set<Ref<EntityStore>> targetRefs = new ObjectOpenHashSet<Ref<EntityStore>>();
        final Vector3d blockPosition = new Vector3d(Math.floor(position.x) + 0.5, Math.floor(position.y) + 0.5, Math.floor(position.z) + 0.5);
        processTargetBlocks(blockPosition, config, ignoreRef, targetRefs, commandBuffer, chunkStore);
        if (config.damageEntities) {
            processTargetEntities(config, position, damageSource, ignoreRef, targetRefs, commandBuffer);
        }
    }
    
    private static void processTargetBlocks(@Nonnull final Vector3d position, @Nonnull final ExplosionConfig config, @Nullable final Ref<EntityStore> ignoreRef, @Nonnull final Set<Ref<EntityStore>> targetRefs, @Nonnull final CommandBuffer<EntityStore> commandBuffer, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        final ThreadLocalRandom random = ThreadLocalRandom.current();
        final World world = commandBuffer.getExternalData().getWorld();
        int explosionBlockRadius = config.blockDamageRadius;
        if (config.damageEntities && config.entityDamageRadius > config.blockDamageRadius) {
            explosionBlockRadius = (int)config.entityDamageRadius;
        }
        final List<Ref<EntityStore>> potentialTargets = new ObjectArrayList<Ref<EntityStore>>();
        if (config.damageEntities) {
            final double range = config.entityDamageRadius;
            final List<Ref<EntityStore>> obj = potentialTargets;
            Objects.requireNonNull(obj);
            Selector.selectNearbyEntities(commandBuffer, position, range, (Consumer<Ref<EntityStore>>)obj::add, e -> ignoreRef == null || !e.equals(ignoreRef));
        }
        if (!config.damageBlocks && potentialTargets.isEmpty()) {
            return;
        }
        final ItemTool itemTool = config.itemTool;
        final Set<Vector3i> targetBlocks = new ObjectOpenHashSet<Vector3i>();
        final int posX = MathUtil.floor(position.x);
        final int posY = MathUtil.floor(position.y);
        final int posZ = MathUtil.floor(position.z);
        BlockSphereUtil.forEachBlock(posX, posY, posZ, explosionBlockRadius, null, (x, y, z, aVoid) -> {
            targetBlocks.add(new Vector3i(x, y, z));
            return true;
        });
        final Set<Vector3i> avoidBlocks = new ObjectOpenHashSet<Vector3i>();
        for (final Vector3i targetBlock : targetBlocks) {
            final Vector3d targetBlockPosition = targetBlock.toVector3d().add(0.5, 0.5, 0.5);
            int setBlockSettings = 1028;
            if (random.nextFloat() > config.blockDropChance) {
                setBlockSettings |= 0x800;
            }
            final double distance = position.distanceTo(targetBlockPosition);
            if (distance > 0.0) {
                if (Double.isNaN(distance)) {
                    continue;
                }
                final Vector3d direction = targetBlockPosition.clone().subtract(position);
                final Vector3i targetBlockPos = TargetUtil.getTargetBlock(world, (id, fluidId) -> isValidTargetBlock(id, config.damageBlocks), position.x, position.y, position.z, direction.x, direction.y, direction.z, distance);
                if (targetBlockPos == null) {
                    if (!config.damageEntities) {
                        continue;
                    }
                    final Vector3d entityHitPos = position.clone().add(direction);
                    collectPotentialTargets(targetRefs, potentialTargets, entityHitPos, position, commandBuffer);
                }
                else {
                    if (avoidBlocks.contains(targetBlockPos)) {
                        continue;
                    }
                    final Vector3d targetBlockPosD = targetBlockPos.toVector3d().add(0.5, 0.5, 0.5);
                    if (config.damageEntities) {
                        collectPotentialTargets(targetRefs, potentialTargets, targetBlockPosD, position, commandBuffer);
                    }
                    final float damageDistance = (float)position.distanceTo(targetBlockPosD);
                    final float damageScale = calculateBlockDamageScale(damageDistance, (float)explosionBlockRadius, config.blockDamageFalloff);
                    final long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPos.x, targetBlockPos.z);
                    final Ref<ChunkStore> chunkReference = chunkStore.getExternalData().getChunkReference(chunkIndex);
                    if (chunkReference == null) {
                        continue;
                    }
                    final boolean canDamageBlock = distance <= config.blockDamageRadius;
                    if (config.damageBlocks && (!canDamageBlock || BlockHarvestUtils.performBlockDamage(targetBlockPos, null, itemTool, damageScale, setBlockSettings, chunkReference, commandBuffer, chunkStore))) {
                        continue;
                    }
                    avoidBlocks.add(targetBlockPos);
                }
            }
        }
    }
    
    private static boolean isValidTargetBlock(final int blockTypeId, final boolean damageBlocks) {
        if (blockTypeId == 0 || blockTypeId == 1) {
            return false;
        }
        if (damageBlocks) {
            return true;
        }
        final BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeId);
        if (blockType == null) {
            return false;
        }
        final BlockGathering gathering = blockType.getGathering();
        return gathering == null || !gathering.isSoft();
    }
    
    private static void collectPotentialTargets(@Nonnull final Set<Ref<EntityStore>> targetRefs, @Nonnull final List<Ref<EntityStore>> potentialTargetRefs, @Nonnull final Vector3d startPosition, @Nonnull final Vector3d endPosition, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        final World world = commandBuffer.getExternalData().getWorld();
        for (final Ref<EntityStore> potentialTarget : potentialTargetRefs) {
            if (!processPotentialEntity(potentialTarget, startPosition, endPosition, commandBuffer) || targetRefs.add(potentialTarget)) {}
        }
    }
    
    private static boolean processPotentialEntity(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d startPosition, @Nonnull final Vector3d endPosition, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        final BoundingBox boundingBoxComponent = commandBuffer.getComponent(ref, BoundingBox.getComponentType());
        if (boundingBoxComponent == null) {
            return false;
        }
        final TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType());
        if (transformComponent == null) {
            return false;
        }
        final Vector3d entityPosition = transformComponent.getPosition();
        final Box boundingBox = boundingBoxComponent.getBoundingBox().clone().offset(entityPosition);
        return boundingBox.intersectsLine(startPosition, endPosition);
    }
    
    private static float calculateBlockDamageScale(final float distance, final float radius, final float fallOff) {
        if (distance >= radius) {
            return 0.0f;
        }
        final float normalizedDistance = distance / radius;
        return 1.0f - (float)Math.pow(normalizedDistance, fallOff);
    }
    
    private static void processTargetEntities(@Nonnull final ExplosionConfig config, @Nonnull final Vector3d position, @Nonnull final Damage.Source damageSource, @Nullable final Ref<EntityStore> ignoreRef, @Nonnull final Set<Ref<EntityStore>> targetRefs, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        for (final Ref<EntityStore> targetRef : targetRefs) {
            processTargetEntity(config, targetRef, position, damageSource, commandBuffer);
        }
    }
    
    private static void processTargetEntity(@Nonnull final ExplosionConfig config, @Nonnull final Ref<EntityStore> targetRef, @Nonnull final Vector3d position, @Nonnull final Damage.Source damageSource, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        final float entityDamageRadius = config.entityDamageRadius;
        final float explosionDamage = config.entityDamage;
        final float explosionFalloff = config.entityDamageFalloff;
        final TransformComponent targetTransformComponent = commandBuffer.getComponent(targetRef, TransformComponent.getComponentType());
        assert targetTransformComponent != null;
        final Velocity targetVelocityComponent = commandBuffer.getComponent(targetRef, Velocity.getComponentType());
        assert targetVelocityComponent != null;
        final Vector3d targetPosition = targetTransformComponent.getPosition();
        final Vector3d diff = targetPosition.clone().subtract(position);
        final double distance = diff.length();
        final float damage = (float)(explosionDamage * Math.pow(1.0 - distance / entityDamageRadius, explosionFalloff));
        if (damage > 0.0f) {
            DamageSystems.executeDamage(targetRef, commandBuffer, new Damage(damageSource, DamageCause.ENVIRONMENT, damage));
        }
        final Knockback knockbackConfig = config.knockback;
        if (knockbackConfig != null) {
            final ComponentType<EntityStore, KnockbackComponent> knockbackComponentType = KnockbackComponent.getComponentType();
            KnockbackComponent knockbackComponent = commandBuffer.getComponent(targetRef, knockbackComponentType);
            if (knockbackComponent == null) {
                knockbackComponent = new KnockbackComponent();
                commandBuffer.putComponent(targetRef, knockbackComponentType, knockbackComponent);
            }
            final Vector3d direction = diff.clone().normalize();
            knockbackComponent.setVelocity(knockbackConfig.calculateVector(position, (float)direction.y, targetPosition));
            knockbackComponent.setVelocityType(knockbackConfig.getVelocityType());
            knockbackComponent.setVelocityConfig(knockbackConfig.getVelocityConfig());
            knockbackComponent.setDuration(knockbackConfig.getDuration());
        }
    }
    
    static {
        DEBUG_POTENTIAL_TARGET_COLOR = new Vector3f(1.0f, 1.0f, 0.0f);
        DEBUG_BLOCK_RADIUS_COLOR = new Vector3f(1.0f, 0.5f, 0.5f);
        DEBUG_ENTITY_RADIUS_COLOR = new Vector3f(0.5f, 1.0f, 0.5f);
    }
}
