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

package com.hypixel.hytale.server.worldgen.climate;

import java.util.concurrent.CompletionStage;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.logging.Level;
import com.hypixel.hytale.server.worldgen.util.LogUtil;
import java.util.List;
import com.hypixel.hytale.server.worldgen.zone.Zone;
import java.util.Map;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.vector.Vector2i;

public class UniqueClimateGenerator
{
    public static final UniqueClimateGenerator EMPTY;
    private static final int[] EMPTY_PARENTS;
    private static final int MAX_PARENT_DEPTH = 10;
    private static final Vector2i DEFAULT_ORIGIN;
    private static final Vector2i[] EMPTY_POSITIONS;
    protected final Entry[] entries;
    protected final Unique[] zones;
    
    public UniqueClimateGenerator(@Nonnull final Entry[] entries) {
        this(entries, Unique.EMPTY_ARRAY);
    }
    
    public UniqueClimateGenerator(@Nonnull final Entry[] entries, @Nonnull final Unique[] zones) {
        this.entries = entries;
        this.zones = zones;
    }
    
    public Entry[] entries() {
        return this.entries;
    }
    
    public Unique[] zones() {
        return this.zones;
    }
    
    public int generate(final int x, final int y) {
        for (int i = 0; i < this.zones.length; ++i) {
            if (this.zones[i].contains(x, y)) {
                return this.zones[i].color;
            }
        }
        return -1;
    }
    
    public Zone.UniqueCandidate[] getCandidates(final Map<String, Zone> zoneLookup) {
        if (this.entries.length == 0) {
            return Zone.UniqueCandidate.EMPTY_ARRAY;
        }
        final Zone.UniqueCandidate[] candidates = new Zone.UniqueCandidate[this.entries.length];
        for (int i = 0; i < this.entries.length; ++i) {
            final Entry entry = this.entries[i];
            final Zone zone = zoneLookup.get(entry.zone);
            if (zone == null) {
                throw new Error("Could not find zone: " + entry.zone);
            }
            candidates[i] = new Zone.UniqueCandidate(new Zone.UniqueEntry(zone, entry.color, UniqueClimateGenerator.EMPTY_PARENTS, entry.radius, 0), UniqueClimateGenerator.EMPTY_POSITIONS);
        }
        return candidates;
    }
    
    public UniqueClimateGenerator apply(final int seed, @Nonnull final Zone.UniqueCandidate[] candidates, @Nonnull final ClimateNoise noise, @Nonnull final ClimateGraph graph, @Nonnull final List<Zone.Unique> collector) {
        if (candidates.length != this.entries.length) {
            LogUtil.getLogger().at(Level.WARNING).log("Mismatched unique climate generator candidates: expected %d, got %d", this.entries.length, candidates.length);
            return this;
        }
        final Unique[] unique = new Unique[candidates.length];
        final Object2ObjectOpenHashMap<String, Unique> lookup = new Object2ObjectOpenHashMap<String, Unique>();
        for (int it = 0; it < 10 && lookup.size() < unique.length; ++it) {
            for (int i = 0; i < this.entries.length; ++i) {
                if (unique[i] == null) {
                    final Entry entry = this.entries[i];
                    final Unique parent = lookup.get(entry.parent);
                    if (entry.parent.isEmpty() || parent != null) {
                        final CompletableFuture<Vector2i> position = findZonePosition(seed, UniqueClimateGenerator.DEFAULT_ORIGIN, entry, parent, noise, graph);
                        unique[i] = new Unique(entry.color, entry.radius, position);
                        collector.add(new Zone.Unique(candidates[i].zone().zone(), position));
                        lookup.put(entry.zone, unique[i]);
                    }
                }
            }
        }
        return new UniqueClimateGenerator(this.entries, unique);
    }
    
    public UniqueClimateGenerator apply(final int seed, @Nonnull final ClimateNoise noise, @Nonnull final ClimateGraph graph) {
        final Unique[] unique = new Unique[this.entries.length];
        final Object2ObjectOpenHashMap<String, Unique> lookup = new Object2ObjectOpenHashMap<String, Unique>();
        for (int it = 0; it < 10 && lookup.size() < unique.length; ++it) {
            for (int i = 0; i < this.entries.length; ++i) {
                if (unique[i] == null) {
                    final Entry entry = this.entries[i];
                    final Unique parent = lookup.get(entry.parent);
                    if (entry.parent.isEmpty() || parent != null) {
                        final CompletableFuture<Vector2i> position = findZonePosition(seed, UniqueClimateGenerator.DEFAULT_ORIGIN, entry, parent, noise, graph);
                        unique[i] = new Unique(entry.color, entry.radius, position);
                        lookup.put(entry.zone, unique[i]);
                    }
                }
            }
        }
        if (lookup.size() < unique.length) {
            LogUtil.getLogger().at(Level.WARNING).log("Could not resolve all unique climate zones, resolved %d out of %d", lookup.size(), unique.length);
            final Entry[] newEntries = new Entry[lookup.size()];
            final Unique[] newUnique = new Unique[lookup.size()];
            int index = 0;
            for (int j = 0; j < unique.length; ++j) {
                if (unique[j] != null) {
                    newEntries[index] = this.entries[j];
                    newUnique[index] = unique[j];
                    ++index;
                }
            }
            return new UniqueClimateGenerator(newEntries, newUnique);
        }
        return new UniqueClimateGenerator(this.entries, unique);
    }
    
    protected static CompletableFuture<Vector2i> findZonePosition(final int seed, final Vector2i origin, @Nonnull final Entry entry, @Nullable final Unique parent, @Nonnull final ClimateNoise noise, @Nonnull final ClimateGraph graph) {
        if (parent != null) {
            return parent.position.thenCompose(pos -> findZonePosition(seed, pos, entry, null, noise, graph));
        }
        return ClimateSearch.search(seed, origin.x + entry.origin.x, origin.y + entry.origin.y, entry.minDistance, entry.maxDistance, entry.rule, noise, graph).thenApply(result -> {
            LogUtil.getLogger().at(Level.INFO).log("Found location for unique zone '%s' -> %s", entry.zone, result.pretty());
            return result.position();
        });
    }
    
    static {
        EMPTY = new UniqueClimateGenerator(Entry.EMPTY_ARRAY, Unique.EMPTY_ARRAY);
        EMPTY_PARENTS = new int[0];
        DEFAULT_ORIGIN = new Vector2i(0, 0);
        EMPTY_POSITIONS = new Vector2i[0];
    }
    
    record Entry(@Nonnull String zone, @Nonnull String parent, int color, int radius, @Nonnull Vector2i origin, int minDistance, int maxDistance, @Nonnull ClimateSearch.Rule rule) {
        public static final Entry[] EMPTY_ARRAY;
        public static final String DEFAULT_PARENT = "";
        
        @Nonnull
        public String zone() {
            return this.zone;
        }
        
        @Nonnull
        public String parent() {
            return this.parent;
        }
        
        @Nonnull
        public Vector2i origin() {
            return this.origin;
        }
        
        @Nonnull
        public ClimateSearch.Rule rule() {
            return this.rule;
        }
        
        static {
            EMPTY_ARRAY = new Entry[0];
        }
    }
    
    record Unique(int color, int radius, int radius2, @Nonnull CompletableFuture<Vector2i> position) {
        public static final Unique[] EMPTY_ARRAY;
        
        public Unique(final int color, final int radius, @Nonnull final CompletableFuture<Vector2i> position) {
            this(color, radius, radius * radius, position);
        }
        
        public boolean contains(final int x, final int y) {
            final Vector2i pos = this.position.join();
            final int dx = x - pos.x;
            final int dy = y - pos.y;
            return dx >= -this.radius && dy >= -this.radius && dx <= this.radius && dy <= this.radius && dx * dx + dy * dy <= this.radius2;
        }
        
        @Nonnull
        public CompletableFuture<Vector2i> position() {
            return this.position;
        }
        
        static {
            EMPTY_ARRAY = new Unique[0];
        }
    }
}
