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

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

import java.awt.image.BufferedImage;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.math.vector.Vector2i;
import java.util.logging.Level;
import com.hypixel.hytale.server.worldgen.util.LogUtil;
import java.util.List;
import com.hypixel.hytale.math.util.FastRandom;
import com.hypixel.hytale.server.worldgen.zone.Zone;
import com.hypixel.hytale.math.util.MathUtil;
import javax.annotation.Nonnull;

public class ExactZoom
{
    @Nonnull
    private final PixelProvider source;
    @Nonnull
    private final PixelDistanceProvider distanceProvider;
    private final double zoomX;
    private final double zoomY;
    private final int offsetX;
    private final int offsetY;
    
    public ExactZoom(@Nonnull final PixelProvider source, final double zoomX, final double zoomY, final int offsetX, final int offsetY) {
        this.source = source;
        this.distanceProvider = new PixelDistanceProvider(source);
        this.zoomX = zoomX;
        this.zoomY = zoomY;
        this.offsetX = offsetX;
        this.offsetY = offsetY;
    }
    
    @Nonnull
    public PixelDistanceProvider getDistanceProvider() {
        return this.distanceProvider;
    }
    
    public boolean inBounds(double x, double y) {
        x += this.offsetX;
        y += this.offsetY;
        if (x < 0.0 || y < 0.0) {
            return false;
        }
        x /= this.zoomX;
        y /= this.zoomY;
        return this.source.getWidth() - 1 >= x && this.source.getHeight() - 1 >= y;
    }
    
    public int generate(double x, double y) {
        x += this.offsetX;
        y += this.offsetY;
        x /= this.zoomX;
        y /= this.zoomY;
        final int px = Math.max(0, Math.min(MathUtil.floor(x), this.source.getWidth() - 1));
        final int py = Math.max(0, Math.min(MathUtil.floor(y), this.source.getHeight() - 1));
        return this.source.getPixel(px, py);
    }
    
    public double distanceToNextPixel(double x, double y) {
        x += this.offsetX;
        y += this.offsetY;
        x /= this.zoomX;
        y /= this.zoomY;
        return this.zoomX * Math.sqrt(this.distanceProvider.distanceSqToDifferentPixel(x, y, (int)MathUtil.fastFloor(x), (int)MathUtil.fastFloor(y)));
    }
    
    public ExactZoom generateUniqueZones(final Zone.UniqueCandidate[] candidates, final FastRandom random, final List<Zone.Unique> zones) {
        final PixelProvider source = this.source.copy();
        for (int i = 0; i < candidates.length; ++i) {
            final Zone.UniqueCandidate candidate = candidates[i];
            final Vector2i pos = selectCandidatePosition(candidate, source, random);
            if (pos == null) {
                LogUtil.getLogger().at(Level.WARNING).log("Failed to place unique zone: %s", candidate.zone());
            }
            else {
                final int radius = candidate.zone().radius();
                final int radius2 = radius * radius;
                for (int dy = -radius; dy <= radius; ++dy) {
                    for (int dx = -radius; dx <= radius; ++dx) {
                        if (dx * dx + dy * dy <= radius2) {
                            source.setPixel(pos.x + dx, pos.y + dy, candidate.zone().color());
                        }
                    }
                }
                zones.add(new Zone.Unique(candidate.zone().zone(), CompletableFuture.completedFuture(new Vector2i((int)(pos.x * this.zoomX - this.offsetX), (int)(pos.y * this.zoomY - this.offsetY)))));
            }
        }
        return new ExactZoom(source, this.zoomX, this.zoomY, this.offsetX, this.offsetY);
    }
    
    public Zone.UniqueCandidate[] generateUniqueZoneCandidates(final Zone.UniqueEntry[] entries, final int maxPositions) {
        final Zone.UniqueCandidate[] candidates = new Zone.UniqueCandidate[entries.length];
        final ArrayList<Vector2i> positions = new ArrayList<Vector2i>();
        for (int i = 0; i < entries.length; ++i) {
            final Zone.UniqueEntry entry = entries[i];
            final int radius = entry.radius();
            final int searchRadius = radius + entry.padding();
            positions.clear();
            for (int iy = searchRadius; iy < this.source.getHeight() - 1 - searchRadius; ++iy) {
                for (int ix = searchRadius; ix < this.source.getWidth() - 1 - searchRadius; ++ix) {
                    if (testZoneFit(entry, this.source, ix, iy, searchRadius)) {
                        positions.add(new Vector2i(ix, iy));
                    }
                }
            }
            final int size = positions.size();
            if (size == 0) {
                throw new Error("No parent matches found for unique zone entry: " + String.valueOf(entry));
            }
            if (size > maxPositions) {
                final int n = Math.max(1, size / maxPositions);
                final int count = size / n;
                final Vector2i[] arr = new Vector2i[count];
                for (int j = 0; j < count; ++j) {
                    arr[j] = positions.get(j * n);
                }
                candidates[i] = new Zone.UniqueCandidate(entry, arr);
            }
            else {
                candidates[i] = new Zone.UniqueCandidate(entry, positions.toArray(Vector2i[]::new));
            }
        }
        return candidates;
    }
    
    @Nullable
    private static Vector2i selectCandidatePosition(final Zone.UniqueCandidate candidate, final PixelProvider source, final FastRandom random) {
        final Vector2i[] positions = candidate.positions();
        final int searchRadius = candidate.zone().radius() + candidate.zone().padding();
        for (int len = positions.length; len > 0; --len) {
            final int index = random.nextInt(len);
            final Vector2i pos = positions[index];
            if (testZoneFit(candidate.zone(), source, pos.x, pos.y, searchRadius)) {
                return pos;
            }
            final Vector2i back = positions[len - 1];
            positions[len - 1] = pos;
            positions[index] = back;
        }
        return null;
    }
    
    private static boolean testZoneFit(final Zone.UniqueEntry entry, final PixelProvider source, final int x, final int y, final int radius) {
        final int radius2 = radius * radius;
        for (int dy = -radius; dy <= radius; ++dy) {
            for (int dx = -radius; dx <= radius; ++dx) {
                if (dx * dx + dy * dy <= radius2 && !entry.matchesParent(source.getPixel(x + dx, y + dy))) {
                    return false;
                }
            }
        }
        return true;
    }
    
    public BufferedImage exportImage() {
        final BufferedImage image = new BufferedImage(this.source.getWidth(), this.source.getHeight(), 1);
        for (int x = 0; x < this.source.getWidth(); ++x) {
            for (int y = 0; y < this.source.getHeight(); ++y) {
                image.setRGB(x, y, this.source.getPixel(x, y));
            }
        }
        return image;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "ExactZoom{source=" + String.valueOf(this.source) + ", distanceProvider=" + String.valueOf(this.distanceProvider) + ", zoomX=" + this.zoomX + ", zoomY=" + this.zoomY + ", offsetX=" + this.offsetX + ", offsetY=" + this.offsetY;
    }
}
