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

package com.hypixel.hytale.builtin.portals.ui;

import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.builtin.portals.utils.posqueries.predicates.FitsAPortal;
import java.util.concurrent.ThreadLocalRandom;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.builtin.portals.utils.posqueries.generators.SearchCircular;
import javax.annotation.Nullable;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import java.util.logging.Level;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Transform;
import com.hypixel.hytale.server.core.asset.type.portalworld.PortalSpawn;
import com.hypixel.hytale.server.core.universe.world.World;

public final class PortalSpawnFinder
{
    @Nullable
    public static Transform computeSpawnTransform(final World world, final PortalSpawn config) {
        Vector3d spawn = findSpawnByThrowingDarts(world, config);
        if (spawn == null) {
            spawn = findFallbackPositionOnGround(world, config);
            HytaleLogger.getLogger().at(Level.INFO).log("Had to use fallback spawn for portal spawn");
        }
        if (spawn == null) {
            HytaleLogger.getLogger().at(Level.INFO).log("Both dart and fallback spawn finder failed for portal spawn");
            return null;
        }
        final Vector3f direction = Vector3f.lookAt(spawn).scale(-1.0f);
        direction.setPitch(0.0f);
        direction.setRoll(0.0f);
        return new Transform(spawn.clone().add(0.0, 0.5, 0.0), direction);
    }
    
    @Nullable
    private static Vector3d findSpawnByThrowingDarts(final World world, final PortalSpawn config) {
        final Vector3d center = config.getCenter().toVector3d();
        center.setY(config.getCheckSpawnY());
        final int halfwayThrows = config.getChunkDartThrows() / 2;
        for (int chunkDart = 0; chunkDart < config.getChunkDartThrows(); ++chunkDart) {
            final Vector3d pointd = new SearchCircular(config.getMinRadius(), config.getMaxRadius(), 1).execute(world, center).orElse(null);
            if (pointd != null) {
                final Vector3i point = pointd.toVector3i();
                final WorldChunk chunk = world.getChunk(ChunkUtil.indexChunkFromBlock(point.x, point.z));
                final BlockType firstBlock = chunk.getBlockType(point.x, point.y, point.z);
                if (firstBlock != null) {
                    final BlockMaterial firstBlockMat = firstBlock.getMaterial();
                    if (firstBlockMat != BlockMaterial.Solid) {
                        final boolean checkIfPortalFitsNice = chunkDart < halfwayThrows;
                        final Vector3d spawn = findGroundWithinChunk(chunk, config, checkIfPortalFitsNice);
                        if (spawn != null) {
                            HytaleLogger.getLogger().at(Level.INFO).log("Found fragment spawn at " + String.valueOf(spawn) + " after " + (chunkDart + 1) + " chunk scan(s)");
                            return spawn;
                        }
                    }
                }
            }
        }
        return null;
    }
    
    @Nullable
    private static Vector3d findGroundWithinChunk(final WorldChunk chunk, final PortalSpawn config, final boolean checkIfPortalFitsNice) {
        final int chunkBlockX = ChunkUtil.minBlock(chunk.getX());
        final int chunkBlockZ = ChunkUtil.minBlock(chunk.getZ());
        final ThreadLocalRandom rand = ThreadLocalRandom.current();
        for (int i = 0; i < config.getChecksPerChunk(); ++i) {
            final int x = chunkBlockX + rand.nextInt(2, 14);
            final int z = chunkBlockZ + rand.nextInt(2, 14);
            final Vector3d point = findWithGroundBelow(chunk, x, config.getCheckSpawnY(), z, config.getScanHeight(), false);
            if (point != null) {
                if (!checkIfPortalFitsNice || FitsAPortal.check(chunk.getWorld(), point)) {
                    return point;
                }
            }
        }
        return null;
    }
    
    @Nullable
    private static Vector3d findWithGroundBelow(final WorldChunk chunk, final int x, final int y, final int z, final int scanHeight, final boolean fluidsAreAcceptable) {
        final World world = chunk.getWorld();
        final ChunkStore chunkStore = world.getChunkStore();
        final Ref<ChunkStore> chunkRef = chunk.getReference();
        final Store<ChunkStore> chunkStoreAccessor = chunkStore.getStore();
        final ChunkColumn chunkColumnComponent = chunkStoreAccessor.getComponent(chunkRef, ChunkColumn.getComponentType());
        final BlockChunk blockChunkComponent = chunkStoreAccessor.getComponent(chunkRef, BlockChunk.getComponentType());
        for (int dy = 0; dy < scanHeight; ++dy) {
            final Material selfMat = getMaterial(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, x, y - dy, z);
            final Material belowMat = getMaterial(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, x, y - dy - 1, z);
            final boolean selfValid = selfMat == Material.AIR || (fluidsAreAcceptable && selfMat == Material.FLUID);
            if (!selfValid) {
                break;
            }
            if (belowMat == Material.SOLID) {
                return new Vector3d(x, y - dy, z);
            }
        }
        return null;
    }
    
    private static Material getMaterial(@Nonnull final ComponentAccessor<ChunkStore> chunkStore, @Nonnull final ChunkColumn chunkColumnComponent, @Nonnull final BlockChunk blockChunkComponent, final double x, final double y, final double z) {
        final int blockX = (int)x;
        final int blockY = (int)y;
        final int blockZ = (int)z;
        final int fluidId = WorldUtil.getFluidIdAtPosition(chunkStore, chunkColumnComponent, blockX, blockY, blockZ);
        if (fluidId != 0) {
            return Material.FLUID;
        }
        final BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(blockY);
        final int blockId = blockSection.get(blockX, blockY, blockZ);
        final BlockType blockType = BlockType.getAssetMap().getAsset(blockId);
        if (blockType == null) {
            return Material.UNKNOWN;
        }
        return switch (blockType.getMaterial()) {
            default -> throw new MatchException(null, null);
            case Solid -> Material.SOLID;
            case Empty -> Material.AIR;
        };
    }
    
    @Nullable
    private static Vector3d findFallbackPositionOnGround(final World world, final PortalSpawn config) {
        final Vector3i center = config.getCenter();
        final WorldChunk centerChunk = world.getChunk(ChunkUtil.indexChunkFromBlock(center.x, center.z));
        return findWithGroundBelow(centerChunk, 0, 319, 0, 319, true);
    }
    
    private enum Material
    {
        SOLID, 
        FLUID, 
        AIR, 
        UNKNOWN;
    }
}
