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

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

import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.math.util.MathUtil;
import java.util.Arrays;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.util.Comparator;
import java.util.PriorityQueue;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.ints.IntArrayList;

public class DistanceTransform
{
    private static final IntArrayList EMPTY_LIST;
    private static final int[] DX;
    private static final int[] DY;
    private static final double[] COST;
    
    public static void apply(@Nonnull final IntMap source, @Nonnull final DoubleMap dest, final double radius) {
        if (radius <= 0.0) {
            throw new IllegalArgumentException("radius must be > 0");
        }
        final int width = source.width;
        final int height = source.height;
        final int size = width * height;
        final Int2ObjectOpenHashMap<IntArrayList> regions = new Int2ObjectOpenHashMap<IntArrayList>();
        final Int2ObjectOpenHashMap<IntArrayList> boundaries = new Int2ObjectOpenHashMap<IntArrayList>();
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                final int index = source.index(x, y);
                final int value = source.at(index);
                regions.computeIfAbsent(value, k -> new IntArrayList()).add(index);
                for (int i = 0; i < 4; ++i) {
                    final int nx = x + DistanceTransform.DX[i];
                    final int ny = y + DistanceTransform.DY[i];
                    if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                        final int neighborIndex = source.index(nx, ny);
                        if (source.at(neighborIndex) != value) {
                            boundaries.computeIfAbsent(value, k -> new IntArrayList()).add(index);
                            break;
                        }
                    }
                }
            }
        }
        final double[] dist = new double[size];
        final PriorityQueue<Node> queue = new PriorityQueue<Node>(Node::sort);
        for (final Int2ObjectMap.Entry<IntArrayList> entry : regions.int2ObjectEntrySet()) {
            final int id = entry.getIntKey();
            final IntArrayList region = entry.getValue();
            final IntArrayList boundary = boundaries.getOrDefault(id, DistanceTransform.EMPTY_LIST);
            if (boundary.isEmpty()) {
                for (int j = 0; j < region.size(); ++j) {
                    dest.set(region.getInt(j), 1.0);
                }
            }
            else {
                Arrays.fill(dist, radius);
                for (int j = 0; j < boundary.size(); ++j) {
                    final int index2 = boundary.getInt(j);
                    dist[index2] = 0.0;
                    queue.offer(new Node(index2, 0.0));
                }
                while (!queue.isEmpty()) {
                    final Node node = queue.poll();
                    final int index2 = node.index;
                    if (node.distance > dist[index2]) {
                        continue;
                    }
                    final int cx = index2 % width;
                    final int cy = index2 / width;
                    for (int k = 0; k < DistanceTransform.DX.length; ++k) {
                        final int nx2 = cx + DistanceTransform.DX[k];
                        final int ny2 = cy + DistanceTransform.DY[k];
                        if (nx2 >= 0 && nx2 < width && ny2 >= 0) {
                            if (ny2 < height) {
                                final int neighborIndex2 = source.index(nx2, ny2);
                                final int neighborId = source.at(neighborIndex2);
                                if (neighborId == id) {
                                    final double distance = node.distance + DistanceTransform.COST[k];
                                    if (distance < dist[neighborIndex2]) {
                                        dist[neighborIndex2] = distance;
                                        queue.offer(new Node(neighborIndex2, distance));
                                    }
                                }
                            }
                        }
                    }
                }
                for (int j = 0; j < region.size(); ++j) {
                    final int index2 = region.getInt(j);
                    final double value2 = MathUtil.clamp(dist[index2], 0.0, radius);
                    dest.set(index2, value2 / radius);
                }
            }
        }
    }
    
    static {
        EMPTY_LIST = new IntArrayList();
        DX = new int[] { -1, 1, 0, 0, -1, -1, 1, 1 };
        DY = new int[] { 0, 0, -1, 1, -1, 1, -1, 1 };
        COST = new double[] { 1.0, 1.0, 1.0, 1.0, Math.sqrt(2.0), Math.sqrt(2.0), Math.sqrt(2.0), Math.sqrt(2.0) };
    }
    
    record Node(int index, double distance) {
        public static int sort(final Node a, final Node b) {
            return Double.compare(a.distance, b.distance);
        }
    }
}
