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

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

import com.hypixel.hytale.server.worldgen.climate.util.DistanceTransform;
import com.hypixel.hytale.math.util.MathUtil;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import com.hypixel.hytale.server.worldgen.climate.util.DoubleMap;
import com.hypixel.hytale.server.worldgen.climate.util.IntMap;
import javax.annotation.Nonnull;

public class ClimateGraph
{
    public static final int RESOLUTION = 512;
    private static final double ONE_MINUS_EPS;
    private final double width;
    private final double height;
    private final double fadeRadius;
    private final double fadeDistance;
    private final FadeMode fadeMode;
    @Nonnull
    private final ClimateType[] parents;
    @Nonnull
    private final ClimateType[] children;
    @Nonnull
    private final ClimateType[] id2TypeLookup;
    @Nonnull
    private final IntMap table;
    @Nonnull
    private final DoubleMap fade;
    @Nonnull
    private final Object2IntMap<ClimateType> type2IdLookup;
    
    public ClimateGraph(final int resolution, @Nonnull final ClimateType[] parents, @Nonnull final FadeMode fadeMode, final double fadeRadius, final double fadeDistance) {
        this.table = new IntMap(resolution, resolution);
        this.fade = new DoubleMap(resolution, resolution);
        this.width = resolution * ClimateGraph.ONE_MINUS_EPS;
        this.height = resolution * ClimateGraph.ONE_MINUS_EPS;
        final AtomicInteger typeCount = new AtomicInteger();
        ClimateType.walk(parents, climate -> typeCount.getAndIncrement());
        this.fadeMode = fadeMode;
        this.fadeRadius = fadeRadius;
        this.fadeDistance = fadeDistance;
        this.parents = parents;
        this.children = new ClimateType[typeCount.intValue() - parents.length];
        this.id2TypeLookup = new ClimateType[typeCount.intValue()];
        this.type2IdLookup = new Object2IntOpenHashMap<ClimateType>();
        final AtomicInteger typeIndex = new AtomicInteger();
        final AtomicInteger childIndex = new AtomicInteger();
        for (final ClimateType parent : parents) {
            this.id2TypeLookup[typeIndex.get()] = parent;
            this.type2IdLookup.put(parent, typeIndex.get());
            typeIndex.getAndIncrement();
            ClimateType.walk(parent.children, climate -> {
                this.children[childIndex.get()] = climate;
                this.id2TypeLookup[typeIndex.get()] = climate;
                this.type2IdLookup.put(climate, typeIndex.get());
                typeIndex.getAndIncrement();
                childIndex.getAndIncrement();
                return;
            });
        }
        this.populateTable(this.table, this.fade);
    }
    
    public double fadeRadius() {
        return this.fadeRadius;
    }
    
    public double fadeDistance() {
        return this.fadeDistance;
    }
    
    public FadeMode fadeMode() {
        return this.fadeMode;
    }
    
    public void refresh() {
        this.populateTable(this.table, this.fade);
    }
    
    public ClimateType[] getParents() {
        return this.parents;
    }
    
    public ClimateType[] getChildren() {
        return this.children;
    }
    
    public IntMap getTable() {
        return this.table;
    }
    
    public DoubleMap getFade() {
        return this.fade;
    }
    
    public int indexOf(final double x, final double y) {
        final int cx = MathUtil.floor(x * this.width);
        final int cy = MathUtil.floor(y * this.height);
        return this.table.index(cx, cy);
    }
    
    public int getId(final int index) {
        return this.table.at(index);
    }
    
    public double getFade(final int index) {
        return this.fade.at(index) * this.fadeDistance;
    }
    
    public double getFadeRaw(final int index) {
        return this.fade.at(index);
    }
    
    public int getId(final double x, final double y) {
        final int cx = MathUtil.floor(x * this.width);
        final int cy = MathUtil.floor(y * this.height);
        return this.table.at(cx, cy);
    }
    
    public double getFade(final double x, final double y) {
        return this.getFadeRaw(x, y) * this.fadeDistance;
    }
    
    public double getFadeRaw(final double x, final double y) {
        final int cx = MathUtil.floor(x * this.width);
        final int cy = MathUtil.floor(y * this.height);
        return this.fade.at(cx, cy);
    }
    
    public ClimateType getType(final double x, final double y) {
        final int cx = MathUtil.floor(x * this.width);
        final int cy = MathUtil.floor(y * this.height);
        final int id = this.table.at(cx, cy);
        return this.id2TypeLookup[id];
    }
    
    public boolean validate(final int id) {
        return (id & 0x1FFFFFFF) < this.id2TypeLookup.length;
    }
    
    public ClimateType getType(final int id) {
        return this.id2TypeLookup[id & 0x1FFFFFFF];
    }
    
    private void populateTable(final IntMap table, final DoubleMap fade) {
        if (this.fadeMode == FadeMode.PARENTS) {
            for (int x = 0; x < table.width; ++x) {
                for (int y = 0; y < table.height; ++y) {
                    this.populatePixel(x, y, table, false);
                }
            }
            DistanceTransform.apply(table, fade, this.fadeRadius);
        }
        for (int x = 0; x < table.width; ++x) {
            for (int y = 0; y < table.height; ++y) {
                this.populatePixel(x, y, table, true);
            }
        }
        if (this.fadeMode == FadeMode.CHILDREN) {
            DistanceTransform.apply(table, fade, this.fadeRadius);
        }
    }
    
    private void populatePixel(final int x, final int y, final IntMap table, final boolean recursive) {
        ClimateType selected = null;
        ClimateType[] climates = this.parents;
        while (climates.length > 0) {
            ClimateType nearest = null;
            double minDist1 = Double.MAX_VALUE;
            double minDist2 = Double.MAX_VALUE;
            for (final ClimateType climate : climates) {
                for (final ClimatePoint point : climate.points) {
                    final double dx = x - point.temperature * this.width;
                    final double dy = y - point.intensity * this.height;
                    final double dist2 = MathUtil.lengthSquared(dx, dy) / point.modifier;
                    if (dist2 < minDist1) {
                        minDist2 = minDist1;
                        minDist1 = dist2;
                        nearest = climate;
                    }
                    else if (dist2 < minDist2) {
                        minDist2 = dist2;
                    }
                }
            }
            if (nearest == null) {
                break;
            }
            selected = nearest;
            climates = nearest.children;
            if (!recursive) {
                break;
            }
        }
        if (selected == null) {
            table.set(x, y, -1);
            return;
        }
        table.set(x, y, this.type2IdLookup.getOrDefault(selected, -1));
    }
    
    static {
        ONE_MINUS_EPS = 1.0 - MathUtil.EPSILON_DOUBLE;
    }
    
    public enum FadeMode
    {
        PARENTS, 
        CHILDREN;
    }
}
