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

package com.hypixel.hytale.server.worldgen.prefab.unique;

import com.hypixel.hytale.server.worldgen.container.CoverContainer;
import com.hypixel.hytale.server.worldgen.util.BlockFluidEntry;
import com.hypixel.hytale.procedurallib.condition.ICoordinateRndCondition;
import com.hypixel.hytale.server.worldgen.container.WaterContainer;
import com.hypixel.hytale.server.worldgen.biome.Biome;
import com.hypixel.hytale.server.worldgen.chunk.MaskProvider;
import com.hypixel.hytale.server.worldgen.chunk.ZoneBiomeResult;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.util.TrigMathUtil;
import java.util.logging.Level;
import com.hypixel.hytale.server.worldgen.util.LogUtil;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.worldgen.container.UniquePrefabContainer;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.worldgen.chunk.ChunkGenerator;
import com.hypixel.hytale.math.vector.Vector2i;
import javax.annotation.Nullable;
import java.util.Random;
import com.hypixel.hytale.server.worldgen.loader.WorldGenPrefabSupplier;
import com.hypixel.hytale.common.map.IWeightedMap;
import com.hypixel.hytale.server.worldgen.prefab.PrefabCategory;

public class UniquePrefabGenerator
{
    private static final int UNIQUE_ZONE_PLACEMENT_HEURISTIC_ITERATIONS = 8;
    protected final String name;
    protected final PrefabCategory category;
    protected final IWeightedMap<WorldGenPrefabSupplier> prefabs;
    protected final UniquePrefabConfiguration configuration;
    protected final int zoneIndex;
    
    public UniquePrefabGenerator(final String name, final PrefabCategory category, final IWeightedMap<WorldGenPrefabSupplier> prefabs, final UniquePrefabConfiguration configuration, final int zoneIndex) {
        this.name = name;
        this.category = category;
        this.prefabs = prefabs;
        this.configuration = configuration;
        this.zoneIndex = zoneIndex;
    }
    
    public String getName() {
        return this.name;
    }
    
    public PrefabCategory getCategory() {
        return this.category;
    }
    
    public IWeightedMap<WorldGenPrefabSupplier> getPrefabs() {
        return this.prefabs;
    }
    
    @Nullable
    public WorldGenPrefabSupplier generatePrefab(final Random random) {
        return this.prefabs.get(random);
    }
    
    @Nonnull
    public Vector3i generate(final int seed, @Nullable final Vector2i position, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final Random random, final int maxFailed, @Nonnull final UniquePrefabContainer.UniquePrefabEntry[] entries) {
        if (position != null) {
            return this.forceUniqueZonePlacement(seed, position, chunkGenerator);
        }
        int failed = 0;
        Vector3i vec;
        while ((vec = this.tryPlacement(seed, chunkGenerator, random, entries)) == null && ++failed <= maxFailed) {}
        if (vec == null) {
            LogUtil.getLogger().at(Level.SEVERE).log("Failed to generate Unique-Prefab '%s' with anchor '%s', maxDistance: %s", this.name, this.configuration.getAnchor(), this.configuration.getMaxDistance());
            vec = this.forceGeneration(seed, chunkGenerator);
            LogUtil.getLogger().at(Level.WARNING).log("FORCED Unique-Prefab '%s' at %s after %s attempts!", this.name, vec, failed);
        }
        else {
            LogUtil.getLogger().at(Level.FINE).log("Generated Unique-Prefab '%s' at %s after %s attempts!", this.name, vec, failed);
        }
        return vec;
    }
    
    @Nullable
    protected Vector3i tryPlacement(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final Random random, @Nonnull final UniquePrefabContainer.UniquePrefabEntry[] entries) {
        double x = this.configuration.getAnchor().getX();
        double z = this.configuration.getAnchor().getY();
        final double distance = random.nextDouble() * this.configuration.getMaxDistance();
        final float angle = random.nextFloat() * 6.2831855f;
        x += TrigMathUtil.cos(angle) * distance;
        z += TrigMathUtil.sin(angle) * distance;
        final int lx = MathUtil.floor(x);
        final int lz = MathUtil.floor(z);
        for (final UniquePrefabContainer.UniquePrefabEntry entry : entries) {
            if (entry != null) {
                final double dx = entry.getPosition().x - x;
                final double dz = entry.getPosition().z - z;
                final double distance2 = dx * dx + dz * dz;
                if (distance2 <= entry.getExclusionRadiusSquared() || distance2 <= this.configuration.getExclusionRadiusSquared()) {
                    return null;
                }
            }
        }
        if (!this.isMatchingNoiseDensity(seed, lx, lz)) {
            return null;
        }
        final ZoneBiomeResult result = chunkGenerator.getZoneBiomeResultAt(seed, lx, lz);
        if (result.getZoneResult().getZone().id() != this.zoneIndex) {
            return null;
        }
        if (!this.configuration.isValidParentBiome(result.getBiome())) {
            return null;
        }
        if (result.zoneResult.getBorderDistance() < this.configuration.getZoneBorderExclusion()) {
            return null;
        }
        final int height = this.getHeight(seed, chunkGenerator, result.getBiome(), lx, lz);
        if (!this.isMatchingHeight(seed, lx, lz, random, height)) {
            return null;
        }
        if (!this.isMatchingParentBlock(seed, lx, height, lz, random, result)) {
            return null;
        }
        return new Vector3i(lx, height, lz);
    }
    
    @Nonnull
    protected Vector3i forceGeneration(final int seed, @Nonnull final ChunkGenerator chunkGenerator) {
        final double x = this.configuration.getAnchor().getX();
        final double z = this.configuration.getAnchor().getY();
        final int lx = MathUtil.floor(x);
        final int lz = MathUtil.floor(z);
        final ZoneBiomeResult result = chunkGenerator.getZoneBiomeResultAt(seed, lx, lz);
        final int height = this.getHeight(seed, chunkGenerator, result.getBiome(), lx, lz);
        return new Vector3i(lx, height, lz);
    }
    
    @Nonnull
    protected Vector3i forceUniqueZonePlacement(final int seed, @Nonnull final Vector2i position, @Nonnull final ChunkGenerator chunkGenerator) {
        final MaskProvider maskProvider = chunkGenerator.getZonePatternProvider().getMaskProvider();
        int x = position.x;
        int z = position.y;
        for (int i = 0; i < 8; ++i) {
            final int px = MathUtil.floor(maskProvider.getX(seed, x, z));
            final int pz = MathUtil.floor(maskProvider.getY(seed, x, z));
            final int dx = px - position.x;
            final int dz = pz - position.y;
            x -= dx / 2;
            z -= dz / 2;
        }
        final ZoneBiomeResult result = chunkGenerator.getZoneBiomeResultAt(seed, x, z);
        final int height = this.getHeight(seed, chunkGenerator, result.getBiome(), x, z);
        return new Vector3i(x, height, z);
    }
    
    protected int getHeight(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final Biome biome, final int x, final int z) {
        final WaterContainer waterContainer = biome.getWaterContainer();
        if (waterContainer.hasEntries() && this.configuration.isOnWater()) {
            return waterContainer.getMaxHeight(seed, x, z);
        }
        return chunkGenerator.getHeight(seed, x, z);
    }
    
    protected boolean isMatchingHeight(final int seed, final int x, final int z, final Random random, final int y) {
        final ICoordinateRndCondition heightCondition = this.configuration.getHeightCondition();
        return heightCondition == null || heightCondition.eval(seed, x, z, y, random);
    }
    
    protected boolean isMatchingNoiseDensity(final int seed, final int x, final int z) {
        return this.configuration.getMapCondition().eval(seed, x, z);
    }
    
    protected boolean isMatchingParentBlock(final int seed, final int x, final int y, final int z, @Nonnull final Random random, @Nonnull final ZoneBiomeResult zoneAndBiomeResult) {
        final BlockFluidEntry groundCover = this.getCoverInGroundAt(seed, x, y, z, random, zoneAndBiomeResult.getBiome());
        if (!groundCover.equals(BlockFluidEntry.EMPTY) && !this.configuration.isValidParentBlock(groundCover.blockId(), groundCover.fluidId())) {
            return false;
        }
        final BlockFluidEntry block = zoneAndBiomeResult.getBiome().getLayerContainer().getTopBlockAt(seed, x, z);
        return this.configuration.isValidParentBlock(block.blockId(), block.fluidId());
    }
    
    protected BlockFluidEntry getCoverInGroundAt(final int seed, final int x, final int y, final int z, @Nonnull final Random random, @Nonnull final Biome biome) {
        final CoverContainer.CoverContainerEntry[] entries;
        final CoverContainer.CoverContainerEntry[] coverContainerEntries = entries = biome.getCoverContainer().getEntries();
        for (final CoverContainer.CoverContainerEntry coverContainerEntry : entries) {
            if (y < 320 && this.isMatchingCover(seed, coverContainerEntry, random, x, y, z)) {
                final CoverContainer.CoverContainerEntry.CoverContainerEntryPart part = coverContainerEntry.get(random);
                if (part.getOffset() == -1) {
                    return part.getEntry();
                }
            }
        }
        return BlockFluidEntry.EMPTY;
    }
    
    protected boolean isMatchingCover(final int seed, @Nonnull final CoverContainer.CoverContainerEntry coverContainerEntry, @Nonnull final Random random, final int x, final int y, final int z) {
        return random.nextDouble() < coverContainerEntry.getCoverDensity() && coverContainerEntry.getMapCondition().eval(seed, x, z) && coverContainerEntry.getHeightCondition().eval(seed, x, z, y, random);
    }
    
    public UniquePrefabConfiguration getConfiguration() {
        return this.configuration;
    }
}
