package tc.oc.pgm.regions;
import java.util.Iterator;
import java.util.Optional;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import com.google.common.collect.Iterators;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import org.bukkit.geometry.Vec3;
import org.bukkit.util.BlockVector;
import org.bukkit.geometry.Cuboid;
import org.bukkit.util.ImVector;
import org.bukkit.util.Vector;
import tc.oc.commons.bukkit.util.BlockUtils;
import tc.oc.commons.bukkit.util.ChunkPosition;
import tc.oc.commons.core.util.Streams;
import tc.oc.pgm.features.FeatureDefinition;
import tc.oc.pgm.features.FeatureInfo;
import tc.oc.pgm.filters.matcher.TypedFilter;
import tc.oc.pgm.filters.query.ILocationQuery;
/**
* Represents an arbitrary region in a Bukkit world.
*/
@FeatureInfo(name = "region")
public interface Region extends TypedFilter<ILocationQuery> {
/**
* @return The smallest cuboid that entirely contains this region
*/
Cuboid getBounds();
/**
* True only if this region contains no points at all times.
*
* Note that a zero-volume region is not necessarily empty.
*/
default boolean isEmpty() {
return false;
}
/**
* True only if this region contains all points at all times.
*/
default boolean isEverywhere() {
return false;
}
@Override
default boolean matches(ILocationQuery query) {
return contains(query.blockCenter());
}
@Override
default boolean isDynamic() {
return true;
}
/**
* Test if the region contains the given point
*/
boolean contains(Vector point);
default boolean contains(Vec3 point) {
return contains(ImVector.copyOf(point.isFine() ? point.fineCopy()
: point.blockCenter()));
}
default boolean contains(BlockVector blockPos) {
return contains(blockPos.blockCenter());
}
default boolean contains(Block block) {
return contains(ImVector.centerOf(block));
}
default boolean contains(BlockState block) {
return contains(ImVector.centerOf(block));
}
default boolean contains(Location point) {
return contains(point.position());
}
default boolean contains(Entity entity) {
return contains(entity.getLocation());
}
/**
* Test if moving from the first point to the second crosses into the region
*/
default boolean enters(BlockVector from, BlockVector to) {
return !contains(from) && contains(to);
}
default boolean enters(Optional<BlockVector> from, BlockVector to) {
return from.isPresent() ? enters(from.get(), to)
: contains(to);
}
/**
* Test if moving from the first point to the second crosses out of the region
*/
default boolean exits(BlockVector from, BlockVector to) {
return contains(from) && !contains(to);
}
default boolean exits(Optional<BlockVector> from, BlockVector to) {
return from.isPresent() ? exits(from.get(), to)
: !contains(to);
}
default boolean canGetRandom() {
return false;
}
default Vector getRandom(Random random) {
throw new UnsupportedOperationException("Cannot generate a random point in " + this.getClass().getSimpleName());
}
default boolean isBlockBounded() {
return false;
}
default org.bukkit.region.BlockRegion blockRegion() {
return getBounds().blockRegion().filter(this::contains);
}
@Deprecated
default Iterator<BlockVector> getBlockVectorIterator() {
return Iterators.transform(
blockRegion().mutableIterator(),
BlockVector::new
);
}
@Deprecated
default Iterable<BlockVector> getBlockVectors() {
return this::getBlockVectorIterator;
}
@Deprecated
default Stream<BlockVector> blockPositions() {
return Streams.of(getBlockVectorIterator());
}
default Iterable<Block> getBlocks(World world) {
return () -> Iterators.transform(
blockRegion().mutableIterator(),
world::getBlockAt
);
}
default Stream<Block> blocks(World world) {
return blockPositions().map(pos -> BlockUtils.blockAt(world, pos));
}
default long blockVolume() {
return blockPositions().count();
}
default Stream<ChunkPosition> chunkPositions() {
final Cuboid bounds = getBounds();
if(!bounds.isBlockFinite()) {
throw new UnsupportedOperationException("Cannot enumerate chunks in unbounded region type " + getClass().getSimpleName());
}
final ChunkPosition
min = ChunkPosition.ofBlock(bounds.minimumBlockInside()),
max = ChunkPosition.ofBlock(bounds.maximumBlockInside());
return IntStream.rangeClosed(min.x(), max.x())
.mapToObj(x -> IntStream.rangeClosed(min.z(), max.z())
.mapToObj(z -> new ChunkPosition(x, z)))
.flatMap(Function.identity());
}
default Stream<BlockState> tileEntities(World world) {
return chunkPositions().flatMap(cp -> Stream.of(cp.getChunk(world).getTileEntities()))
.filter(this::contains);
}
default Stream<Entity> entities(World world) {
return chunkPositions().flatMap(cp -> Stream.of(cp.getChunk(world).getEntities()))
.filter(this::contains);
}
abstract class Impl extends FeatureDefinition.Impl implements Region {}
}