package tc.oc.commons.bukkit.util;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import gnu.trove.impl.Constants;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.iterator.TIterator;
import gnu.trove.iterator.TLongIntIterator;
import gnu.trove.map.TLongIntMap;
import gnu.trove.map.hash.TLongIntHashMap;
import gnu.trove.procedure.TLongIntProcedure;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.material.MaterialData;
import org.bukkit.util.BlockVector;
import static tc.oc.commons.bukkit.util.BlockUtils.decodePos;
import static tc.oc.commons.bukkit.util.BlockUtils.encodePos;
import static tc.oc.commons.bukkit.util.MaterialUtils.decodeMaterial;
import static tc.oc.commons.bukkit.util.MaterialUtils.decodeMetadata;
import static tc.oc.commons.bukkit.util.MaterialUtils.decodeTypeId;
import static tc.oc.commons.bukkit.util.MaterialUtils.encodeMaterial;
import static tc.oc.commons.bukkit.util.MaterialUtils.encodeMaterialSet;
/**
* A map of {@link BlockVector} to {@link MaterialData}, implemented entirely with
* primitive collections, so key/value objects are never created except when calling
* a method that returns them. This container can be used to represent an arbitrary
* set of block states in a very efficient way, though tile entities cannot be stored.
*
* The methods used to encode and decode contained data can be found
* in {@link BlockUtils} and {@link MaterialUtils}.
*/
public class BlockMaterialMap implements Map<BlockVector, MaterialData> {
public static final long NO_KEY = BlockUtils.ENCODED_NULL_POS;
public static final int NO_VALUE = MaterialUtils.ENCODED_NULL_MATERIAL;
private final TLongIntMap map;
public BlockMaterialMap(TLongIntMap map) {
this.map = map;
}
public BlockMaterialMap(int capacity) {
this(new TLongIntHashMap(capacity, Constants.DEFAULT_LOAD_FACTOR, NO_KEY, NO_VALUE));
}
public BlockMaterialMap() {
this(Constants.DEFAULT_CAPACITY);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
public boolean containsKey(long encodedPos) {
return map.containsKey(encodedPos);
}
public boolean containsKey(int x, int y, int z) {
return map.containsKey(encodePos(x, y, z));
}
public boolean containsKey(BlockVector pos) {
return containsKey(encodePos(pos));
}
@Override
public boolean containsKey(Object pos) {
return pos instanceof BlockVector && containsKey((BlockVector) pos);
}
public boolean containsValue(int encodedMaterial) {
return map.containsValue(encodedMaterial);
}
public boolean containsValue(int typeId, byte metadata) {
return containsValue(encodeMaterial(typeId, metadata));
}
public boolean containsValue(MaterialData material) {
return containsValue(encodeMaterial(material));
}
@Override
public boolean containsValue(Object material) {
return material instanceof MaterialData && containsValue((MaterialData) material);
}
public int getEncoded(long encodedPos) {
return map.get(encodedPos);
}
public int getEncoded(int x, int y, int z) {
return getEncoded(encodePos(x, y, z));
}
public int getEncoded(BlockVector pos) {
return getEncoded(encodePos(pos));
}
public int getTypeId(long encodedPos) {
return decodeTypeId(getEncoded(encodedPos));
}
public int getTypeId(int x, int y, int z) {
return getTypeId(encodePos(x, y, z));
}
public int getTypeId(BlockVector pos) {
return getTypeId(encodePos(pos));
}
public byte getMetadata(long encodedPos) {
return decodeMetadata(getEncoded(encodedPos));
}
public byte getMetadata(int x, int y, int z) {
return getMetadata(encodePos(x, y, z));
}
public byte getMetadata(BlockVector pos) {
return getMetadata(encodePos(pos));
}
public MaterialData get(long encodedPos) {
return decodeMaterial(getEncoded(encodedPos));
}
public MaterialData get(int x, int y, int z) {
return get(encodePos(x, y, z));
}
public MaterialData get(BlockVector pos) {
return get(encodePos(pos));
}
@Override
public MaterialData get(Object pos) {
return pos instanceof BlockVector ? get((BlockVector) pos) : null;
}
public int putEncoded(long encodedPos, int encodedMaterial) {
return map.put(encodedPos, encodedMaterial);
}
public int putEncoded(int x, int y, int z, int encodedMaterial) {
return putEncoded(encodePos(x, y, z), encodedMaterial);
}
public int putEncoded(BlockVector pos, int encodedMaterial) {
return putEncoded(encodePos(pos), encodedMaterial);
}
public int putEncoded(long encodedPos, int typeId, byte metadata) {
return putEncoded(encodedPos, encodeMaterial(typeId, metadata));
}
public int putEncoded(int x, int y, int z, int typeId, byte metadata) {
return putEncoded(encodePos(x, y, z), typeId, metadata);
}
public int putEncoded(BlockVector pos, int typeId, byte metadata) {
return putEncoded(encodePos(pos), typeId, metadata);
}
public int putEncoded(long encodedPos, MaterialData material) {
return putEncoded(encodedPos, encodeMaterial(material));
}
public int putEncoded(int x, int y, int z, MaterialData material) {
return putEncoded(encodePos(x, y, z), material);
}
public int putEncoded(BlockVector pos, MaterialData material) {
return putEncoded(encodePos(pos), material);
}
public int putEncoded(BlockState state) {
return putEncoded(encodePos(state.getX(), state.getY(), state.getZ()), encodeMaterial(state.getMaterialData()));
}
public MaterialData put(long encodedPos, int encodedMaterial) {
return decodeMaterial(putEncoded(encodedPos, encodedMaterial));
}
public MaterialData put(int x, int y, int z, int encodedMaterial) {
return decodeMaterial(putEncoded(x, y, z, encodedMaterial));
}
public MaterialData put(BlockVector pos, int encodedMaterial) {
return decodeMaterial(putEncoded(pos, encodedMaterial));
}
public MaterialData put(long encodedPos, int typeId, byte metadata) {
return decodeMaterial(putEncoded(encodedPos, typeId, metadata));
}
public MaterialData put(int x, int y, int z, int typeId, byte metadata) {
return decodeMaterial(putEncoded(x, y, z, typeId, metadata));
}
public MaterialData put(BlockVector pos, int typeId, byte metadata) {
return decodeMaterial(putEncoded(pos, typeId, metadata));
}
public MaterialData put(long encodedPos, MaterialData material) {
return decodeMaterial(putEncoded(encodedPos, material));
}
public MaterialData put(int x, int y, int z, MaterialData material) {
return decodeMaterial(putEncoded(x, y, z, material));
}
@Override
public MaterialData put(BlockVector pos, MaterialData material) {
return decodeMaterial(putEncoded(pos, material));
}
public MaterialData put(BlockState state) {
return put(encodePos(state), encodeMaterial(state.getMaterialData()));
}
public int removeEncoded(long encodedPos) {
return map.remove(encodedPos);
}
public int removeEncoded(int x, int y, int z) {
return removeEncoded(encodePos(x, y, z));
}
public int removeEncoded(BlockVector pos) {
return removeEncoded(encodePos(pos));
}
public MaterialData remove(long encodedPos) {
return decodeMaterial(removeEncoded(encodedPos));
}
public MaterialData remove(int x, int y, int z) {
return decodeMaterial(removeEncoded(x, y, z));
}
public MaterialData remove(BlockVector pos) {
return decodeMaterial(removeEncoded(pos));
}
@Override
public MaterialData remove(Object pos) {
return pos instanceof BlockVector ? remove((BlockVector) pos) : null;
}
public boolean remove(BlockState state) {
long encodedPos = encodePos(state);
int encodedMaterial = getEncoded(encodedPos);
if(encodedMaterial == encodeMaterial(state)) {
removeEncoded(encodedPos);
return true;
} else {
return false;
}
}
@Override
public void clear() {
map.clear();
}
@Override
public void putAll(Map<? extends BlockVector, ? extends MaterialData> m) {
for(Entry<? extends BlockVector, ? extends MaterialData> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public Set<BlockVector> keySet() {
return new BlockVectorSet(map.keySet());
}
@Override
public Collection<MaterialData> values() {
return new ValueCollection();
}
@Override
public Set<Entry<BlockVector, MaterialData>> entrySet() {
return new EntrySet();
}
/**
* Return a set of {@link BlockState}s backed by this map. The returned set is
* writable and shares all state with the original container. Iterating over
* the set is not recommended, as creating {@link BlockState}s from arbitrary
* data is rather inefficient.
*/
public Set<BlockState> asBlockStates(World world) {
return new BlockStateSet(world);
}
public long getEncodedKeyAt(int n) {
return map.keys()[n];
}
/**
* Get the key at position N for some arbitrary ordering. The order is constant
* as long as the state of the container does not change, but is otherwise
* undefined.
*/
public BlockVector getKeyAt(int n) {
return decodePos(getEncodedKeyAt(n));
}
abstract class IteratorWrapper<T, I extends TIterator> implements Iterator<T> {
final I iter;
protected IteratorWrapper(I iter) {
this.iter = iter;
}
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public void remove() {
iter.remove();
}
}
class ValueIterator extends IteratorWrapper<MaterialData, TIntIterator> {
public ValueIterator() {
super(map.valueCollection().iterator());
}
@Override
public MaterialData next() {
return decodeMaterial(iter.next());
}
}
class EntryIterator extends IteratorWrapper<Map.Entry<BlockVector, MaterialData>, TLongIntIterator> {
protected EntryIterator() {
super(map.iterator());
}
@Override
public Entry<BlockVector, MaterialData> next() {
iter.advance();
return new AbstractMap.SimpleEntry<>(decodePos(iter.key()), decodeMaterial(iter.value()));
}
}
class ValueCollection implements Collection<MaterialData> {
@Override
public int size() {
return BlockMaterialMap.this.size();
}
@Override
public boolean isEmpty() {
return BlockMaterialMap.this.isEmpty();
}
@Override
public boolean contains(Object material) {
return BlockMaterialMap.this.containsValue(material);
}
@Override
public Iterator<MaterialData> iterator() {
return new ValueIterator();
}
@Override
public Object[] toArray() {
return toArray(new MaterialData[size()]);
}
@Override
public <T> T[] toArray(T[] a) {
Class<T> type = (Class<T>) a.getClass().getComponentType();
if(size() <= a.length) {
MaterialData[] materials = (MaterialData[]) a; // avoid casting every element
int i = 0;
for(MaterialData material : this) {
materials[i++] = material;
}
if(i < materials.length) materials[i] = null;
return (T[]) materials;
} else {
return toArray((T[]) Array.newInstance(type, size()));
}
}
@Override
public boolean containsAll(Collection<?> materials) {
return BlockMaterialMap.this.map.valueCollection().containsAll(encodeMaterialSet(materials));
}
@Override
public boolean remove(Object material) {
return material instanceof MaterialData && BlockMaterialMap.this.map.valueCollection().remove(encodeMaterial((MaterialData) material));
}
@Override
public boolean removeAll(Collection<?> c) {
return BlockMaterialMap.this.map.valueCollection().removeAll(encodeMaterialSet(c));
}
@Override
public boolean add(MaterialData material) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends MaterialData> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
return BlockMaterialMap.this.map.valueCollection().retainAll(encodeMaterialSet(c));
}
@Override
public void clear() {
BlockMaterialMap.this.clear();
}
}
abstract class SetView<T> implements Set<T> {
@Override
public int size() {
return BlockMaterialMap.this.size();
}
@Override
public boolean isEmpty() {
return BlockMaterialMap.this.isEmpty();
}
abstract Class<?> entryArrayType();
abstract T castEntry(Object obj);
abstract long encodedKey(T entry);
abstract int encodedValue(T entry);
@Override
public boolean contains(Object obj) {
T entry = castEntry(obj);
return entry != null && encodedKey(entry) == encodedValue(entry);
}
@Override
public boolean containsAll(Collection<?> c) {
for(Object o : c) {
if(!contains(o)) return false;
}
return true;
}
@Override
public Object[] toArray() {
return toArray((T[]) Array.newInstance(entryArrayType(), size()));
}
@Override
public <E> E[] toArray(E[] a) {
Class<E> type = (Class<E>) a.getClass().getComponentType();
if(size() <= a.length) {
T[] entries = (T[]) entryArrayType().cast(a); // avoid casting every element
int i = 0;
for(T entry : this) {
entries[i++] = entry;
}
if(i < entries.length) entries[i] = null;
return (E[]) entries;
} else {
return toArray((E[]) Array.newInstance(type, size()));
}
}
@Override
public boolean add(T entry) {
int oldMaterial = putEncoded(encodedKey(entry), encodedValue(entry));
return oldMaterial != encodedValue(entry);
}
@Override
public boolean addAll(Collection<? extends T> c) {
boolean changed = false;
for(T entry : c) {
if(add(entry)) changed = true;
}
return changed;
}
@Override
public boolean remove(Object obj) {
T entry = castEntry(obj);
if(entry == null) return false;
long encodedPos = encodedKey(entry);
int encodedMaterial = getEncoded(encodedPos);
if(encodedMaterial == encodedValue(entry)) {
removeEncoded(encodedPos);
return true;
} else {
return false;
}
}
@Override
public boolean removeAll(Collection<?> entries) {
boolean changed = false;
for(Object obj : entries) {
if(remove(obj)) changed = true;
}
return changed;
}
@Override
public boolean retainAll(final Collection<?> entries) {
return map.retainEntries(new TLongIntProcedure() {
@Override
public boolean execute(long encodedPos, int encodedMaterial) {
for(Object obj : entries) {
T entry = castEntry(obj);
if(entry != null && encodedPos == encodedKey(entry) && encodedMaterial == encodedValue(entry)) {
return true;
}
}
return false;
}
});
}
@Override
public void clear() {
BlockMaterialMap.this.clear();
}
}
class EntrySet extends SetView<Entry<BlockVector, MaterialData>> {
@Override
Class<?> entryArrayType() {
return Entry[].class;
}
@Override
Entry<BlockVector, MaterialData> castEntry(Object obj) {
if(!(obj instanceof Entry)) return null;
Entry entry = (Entry) obj;
if(entry.getKey() instanceof BlockVector && entry.getValue() instanceof MaterialData) {
return (Entry<BlockVector, MaterialData>) entry;
} else {
return null;
}
}
@Override
long encodedKey(Entry<BlockVector, MaterialData> entry) {
return encodePos(entry.getKey());
}
@Override
int encodedValue(Entry<BlockVector, MaterialData> entry) {
return encodeMaterial(entry.getValue());
}
@Override
public Iterator<Entry<BlockVector, MaterialData>> iterator() {
return new EntryIterator();
}
}
class BlockStateSet extends SetView<BlockState> {
final World world;
BlockStateSet(World world) {
this.world = world;
}
@Override
Class<?> entryArrayType() {
return BlockState[].class;
}
@Override
BlockState castEntry(Object obj) {
return obj instanceof BlockState ? (BlockState) obj : null;
}
@Override
long encodedKey(BlockState entry) {
return encodePos(entry.getX(), entry.getY(), entry.getZ());
}
@Override
int encodedValue(BlockState entry) {
return encodeMaterial(entry.getTypeId(), entry.getRawData());
}
@Override
public Iterator<BlockState> iterator() {
return new BlockStateIterator();
}
class BlockStateIterator extends IteratorWrapper<BlockState, TLongIntIterator> {
protected BlockStateIterator() {
super(map.iterator());
}
@Override
public BlockState next() {
Block block = BlockUtils.blockAt(world, iter.key());
if(block == null) return null;
BlockState state = block.getState();
state.setTypeId(decodeTypeId(iter.value()));
state.setRawData(decodeMetadata(iter.value()));
return state;
}
}
}
}