package mhfc.net.common.worldedit;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.regions.CuboidRegion;
public class RegionSpliterator implements Spliterator<CuboidRegion> {
private CuboidRegion region;
private int maxSizePerDim;
public RegionSpliterator(CuboidRegion region, int minSizePerDim) {
if (minSizePerDim <= 0) {
throw new IllegalArgumentException("size must be greater than 0");
}
this.region = Objects.requireNonNull(region);
this.maxSizePerDim = minSizePerDim;
}
private int ceiledDiv(int x, int n) {
// Ceiled integer division:
int answer = x / n;
if (x % n != 0) {
answer++;
}
return answer;
}
/**
* @param size
* must be greater than maxSizePerDim
* @return a new size for this region, the rest is for the other one
* @see #getSplits(int)
*/
private int split(int size) {
// We want to find the next smaller sizeN = maxSizePerDim * 2^n < size
// for maximum n and split there. This will result in a predictable number of splits
if (size < maxSizePerDim) {
throw new IllegalArgumentException("size must be great than maxSizePerDim");
}
return size >> 1;
}
/**
* Calculates how many splits will be made along an axis with size size
*
* @param size
* the size in the polled dimension
* @return the number of spliterators that will result in that direction
* @see #split(int)
*/
private int getSplits(int size) {
if (size < 0) {
throw new IllegalArgumentException("size must be greater or equal 0");
}
// FIXME: fix this, gives wrong sizes
return ceiledDiv(size, maxSizePerDim);
}
private CuboidRegion splitRegion() {
if (region == null) {
return null;
}
Vector minPoint = region.getMinimumPoint();
Vector maxPoint = region.getMaximumPoint();
int sizeX = region.getWidth(), sizeY = region.getHeight(), sizeZ = region.getLength();
Vector maxFirst;
Vector minSecond;
if (sizeX > maxSizePerDim) {
int split = split(sizeX);
maxFirst = minPoint.add(split - 1, sizeY - 1, sizeZ - 1);
minSecond = minPoint.add(split, 0, 0);
} else if (sizeY > maxSizePerDim) {
int split = split(sizeY);
maxFirst = minPoint.add(sizeX - 1, split - 1, sizeZ - 1);
minSecond = minPoint.add(0, split, 0);
} else if (sizeZ > maxSizePerDim) {
int split = split(sizeZ);
maxFirst = minPoint.add(sizeX - 1, sizeY - 1, split - 1);
minSecond = minPoint.add(0, 0, split);
} else {
return null;
}
CuboidRegion ours = new CuboidRegion(minPoint, maxFirst);
CuboidRegion other = new CuboidRegion(minSecond, maxPoint);
region = ours;
return other;
}
@Override
public boolean tryAdvance(Consumer<? super CuboidRegion> action) {
if (region == null) {
return false;
}
action.accept(region);
region = null;
return true;
}
@Override
public RegionSpliterator trySplit() {
if (region == null) {
return null;
}
CuboidRegion splitOff = splitRegion();
if (splitOff == null) {
return null;
}
RegionSpliterator newSplit = new RegionSpliterator(splitOff, maxSizePerDim);
return newSplit;
}
@Override
public long estimateSize() {
return region == null
? 0
: 1L * getSplits(region.getHeight()) * getSplits(region.getWidth()) * getSplits(region.getLength());
}
@Override
public int characteristics() {
return DISTINCT | SIZED | NONNULL | IMMUTABLE | SUBSIZED;
}
}