package de.take_weiland.mods.commons.net;
import com.google.common.primitives.Ints;
import de.take_weiland.mods.commons.nbt.NBT;
import de.take_weiland.mods.commons.util.*;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fluids.FluidStack;
import org.apache.commons.lang3.ArrayUtils;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.Map;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkPositionIndexes;
/**
* @author diesieben07
*/
@ParametersAreNonnullByDefault
class MCDataInputImpl extends InputStream implements MCDataInput {
private final byte[] buf;
private final int maxLen;
private final int initialPos;
private int pos;
private int markedPos = BufferConstants.NO_MARK;
MCDataInputImpl(byte[] buf, int off, int len) {
this.buf = buf;
this.pos = this.initialPos = off;
this.maxLen = len;
}
@Override
public InputStream asInputStream() {
return this;
}
@Override
public int pos() {
return pos - initialPos;
}
@Override
public int len() {
return maxLen;
}
@Override
public int available() {
return maxLen - pos;
}
@Override
public void seek(int pos) {
if (pos < 0) {
throw new IllegalArgumentException("pos must be >= 0");
} else if (pos > maxLen) {
throw new IndexOutOfBoundsException("pos must be < length");
} else {
this.pos = (pos + initialPos);
}
}
@Override
public int skipBytes(int n) {
if (n <= 0) {
return 0;
}
int avail = maxLen - pos;
if (n > avail) n = avail;
pos += n;
return n;
}
@Override
public long skip(long n) {
return skipBytes(Ints.saturatedCast(n));
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readlimit) {
markedPos = pos;
}
@Override
public void reset() {
if (markedPos == BufferConstants.NO_MARK) {
pos = initialPos;
} else {
pos = markedPos;
}
}
@Override
public void close() { }
// actual IO
final void checkAvailable(int bytes) {
if (maxLen - pos < bytes) {
throw new IllegalStateException("Read past end of buffer");
}
}
@Override
public int read() {
return (pos + initialPos) < maxLen ? buf[pos++] & 0xFF : -1;
}
@Override
public int read(byte[] b, int off, int len) {
checkPositionIndexes(off, off + len, buf.length);
int avail = maxLen - pos;
if (avail == 0) {
return -1;
}
int actualLen = Math.min(len, avail);
if (actualLen != 0) {
System.arraycopy(buf, pos, b, off, actualLen);
pos += actualLen;
}
return actualLen;
}
@Override
public int read(byte[] b) {
return read(b, 0, b.length);
}
@Override
@Nonnull
public String readUTF() {
try {
return DataInputStream.readUTF(this);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
public String readLine() {
StringBuilder sb = new StringBuilder();
int c;
loop:
while (true) {
switch (c = read()) {
case -1:
case '\n':
break loop;
case '\r':
// \r\n counts as one line break
if (pos != maxLen && buf[pos] == '\n') {
pos++;
}
break loop;
default:
sb.append((char) c);
break;
}
}
return c == -1 && sb.length() == 0 ? null : sb.toString();
}
// primitives
@Override
public boolean readBoolean() {
return readByte() != BufferConstants.BOOLEAN_FALSE;
}
@Override
public byte readByte() {
checkAvailable(1);
return buf[pos++];
}
@Override
public int readUnsignedByte() {
return readByte() & 0xFF;
}
@Override
public short readShort() {
checkAvailable(2);
return readShortNBC();
}
final short readShortNBC() {
int pos = this.pos;
this.pos = pos + 2;
return (short) (this.buf[pos] & 0xFF | this.buf[pos + 1] << 8);
}
@Override
public int readUnsignedShort() {
return readShort() & 0xFFFF;
}
@Override
public char readChar() {
checkAvailable(2);
return readCharNBC();
}
final char readCharNBC() {
return (char) ((this.buf[pos++] & 0xFF) | (this.buf[pos++] & 0xFF) << 8);
}
@Override
public int readInt() {
checkAvailable(4);
return readIntNBC();
}
final int readIntNBC() {
return (this.buf[pos++] & 0xFF)
| (this.buf[pos++] & 0xFF) << 8
| (this.buf[pos++] & 0xFF) << 16
| (this.buf[pos++] & 0xFF) << 24;
}
@Override
public long readLong() {
checkAvailable(8);
return readLongNBC();
}
final long readLongNBC() {
return (long) this.buf[this.pos++] & 0xFF
| (long) (this.buf[this.pos++] & 0xFF) << 8
| (long) (this.buf[this.pos++] & 0xFF) << 16
| (long) (this.buf[this.pos++] & 0xFF) << 24
| (long) (this.buf[this.pos++] & 0xFF) << 32
| (long) (this.buf[this.pos++] & 0xFF) << 40
| (long) (this.buf[this.pos++] & 0xFF) << 48
| (long) (this.buf[this.pos++] & 0xFF) << 56;
}
@Override
public float readFloat() {
return Float.intBitsToFloat(readInt());
}
final float readFloatNBC() {
return Float.intBitsToFloat(readIntNBC());
}
@Override
public double readDouble() {
return Double.longBitsToDouble(readLong());
}
final double readDoubleNBC() {
return Double.longBitsToDouble(readLongNBC());
}
@Override
public int readVarInt() {
int res = 0;
int read;
int step = 0;
do {
read = readByte();
res |= (read & BufferConstants.SEVEN_BITS) << step;
step += 7;
} while ((read & BufferConstants.BYTE_MSB) == 0);
return res;
}
// primitive boxes
@Override
public Boolean readBooleanBox() {
int b = readByte();
return b == BufferConstants.BOOLEAN_NULL ? null : b != BufferConstants.BOOLEAN_FALSE;
}
@Override
public Byte readByteBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readByte();
}
}
@Override
public Short readShortBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readShort();
}
}
@Override
public Character readCharBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readChar();
}
}
@Override
public Integer readIntBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readInt();
}
}
@Override
public Long readLongBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readLong();
}
}
@Override
public Float readFloatBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readFloat();
}
}
@Override
public Double readDoubleBox() {
if (readByte() == BufferConstants.BOX_NULL) {
return null;
} else {
return readDouble();
}
}
// array stuff
@Override
public void readFully(@NotNull byte[] b) {
readFully(b, 0, b.length);
}
@Override
public void readFully(@NotNull byte[] b, int off, int len) {
checkPositionIndexes(off, off + len, b.length);
checkAvailable(len);
System.arraycopy(buf, pos, b, off, len);
pos += len;
}
@Override
public boolean[] readBooleans() {
return readBooleans(null);
}
@Override
public boolean[] readBooleans(@Nullable boolean[] b) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(b);
} else {
if (b == null || b.length != len) {
b = new boolean[len];
}
checkAvailable(((len - 1) >> 3) + 1); // works for len > 0
int currentByte = 0;
for (int idx = 0; idx < len; idx++) {
int bit = (idx & 7); // idx % 8
if (bit == 0) {
currentByte = readUnsignedByte();
}
b[idx] = (currentByte & (1 << bit)) != 0;
}
return b;
}
}
@Override
public byte[] readBytes() {
return readBytes(null);
}
@Override
public byte[] readBytes(@Nullable byte[] b) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(b);
} else {
if (b == null || b.length != len) {
b = new byte[len];
}
checkAvailable(len);
System.arraycopy(buf, pos, b, 0, len);
pos += len;
return b;
}
}
@Override
public short[] readShorts() {
return readShorts(null);
}
@Override
public short[] readShorts(@Nullable short[] arr) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(arr);
} else {
checkAvailable(len << 1);
if (arr == null || arr.length < len) {
arr = new short[len];
}
for (int i = 0; i < len; i++) {
arr[i] = readShortNBC();
}
return arr;
}
}
@Override
public int[] readInts() {
return readInts(null);
}
@Override
public int[] readInts(@Nullable int[] arr) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(arr);
} else {
checkAvailable(len << 2);
if (arr == null || arr.length < len) {
arr = new int[len];
}
for (int i = 0; i < len; i++) {
arr[i] = readIntNBC();
}
return arr;
}
}
@Override
public long[] readLongs() {
return readLongs(null);
}
@Override
public long[] readLongs(@Nullable long[] arr) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(arr);
} else {
checkAvailable(len << 3);
if (arr == null || arr.length < len) {
arr = new long[len];
}
for (int i = 0; i < len; i++) {
arr[i] = readLongNBC();
}
return arr;
}
}
@Override
public char[] readChars() {
return readChars(null);
}
@Override
public char[] readChars(@Nullable char[] arr) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(arr);
} else {
checkAvailable(len << 1);
if (arr == null || arr.length < len) {
arr = new char[len];
}
for (int i = 0; i < len; ++i) {
arr[i] = readCharNBC();
}
return arr;
}
}
@Override
public float[] readFloats() {
return readFloats(null);
}
@Override
public float[] readFloats(@Nullable float[] arr) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(arr);
} else {
checkAvailable(len << 2);
if (arr == null || arr.length < len) {
arr = new float[len];
}
for (int i = 0; i < len; ++i) {
arr[i] = readFloatNBC();
}
return arr;
}
}
@Override
public double[] readDoubles() {
return readDoubles(null);
}
@Override
public double[] readDoubles(@Nullable double[] b) {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return ArrayUtils.nullToEmpty(b);
} else {
checkAvailable(len << 3);
if (b == null || b.length < len) {
b = new double[len];
}
for (int i = 0; i < len; ++i) {
b[i] = readDoubleNBC();
}
return b;
}
}
// misc stuff
@Override
public <E extends Enum<E>> E readEnum(Class<E> clazz) {
int e = readVarInt();
return e < 0 ? null : JavaUtils.byOrdinal(clazz, e);
}
@Override
public BitSet readBitSet() {
long[] words = readLongs();
if (words == null) {
return null;
} else {
return BitSet.valueOf(words);
}
}
@Override
public <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> enumClass) {
int readByte = readByte();
if (readByte == 0) {
return null;
} else {
E[] universe = JavaUtils.getEnumConstantsShared(enumClass);
EnumSet<E> set = EnumSet.noneOf(enumClass);
int numEnums = universe.length;
int numExtraBytes = numEnums >>> 3;
readByte >>= 1;
if (numExtraBytes == 0) {
for (int i = 0; i < numEnums; i++) {
if ((readByte & (1 << i)) != 0) {
set.add(universe[i]);
}
}
} else {
for (int i = 0; i < 7; i++) {
if ((readByte & (1 << i)) != 0) {
set.add(universe[i]);
}
}
numEnums -= 7;
for (int b = 0; b < numExtraBytes; b++) {
readByte = readByte();
int left = Math.min(8, numEnums);
for (int i = 0; i < left; i++) {
if ((readByte & (1 << i)) != 0) {
set.add(universe[(b << 3) + i + 1]);
}
}
numEnums -= 8;
}
}
return set;
}
}
@Override
public String readString() {
int len = readVarInt();
if (len < 0) {
return null;
} else if (len == 0) {
return "";
} else {
checkAvailable(len << 1);
byte[] buf = this.buf;
int pos = this.pos;
char[] chars = new char[len];
for (int i = 0; i < len; ++i) {
chars[i] = (char) (buf[pos] | buf[pos + 1] << 8);
pos += 2;
}
this.pos = pos;
return SCReflector.instance.createStringShared(chars, true);
}
}
@Override
public ItemStack readItemStack() {
int id = readShort();
if (id < 0) {
return null;
} else {
int dmg = readShort();
int size = readByte();
ItemStack stack = new ItemStack(id, size, dmg);
stack.stackTagCompound = readNBT();
return stack;
}
}
@Override
public FluidStack readFluidStack() {
int id = readVarInt();
if (id < 0) {
return null;
} else {
return new FluidStack(id, readVarInt(), readNBT());
}
}
@Override
public UUID readUUID() {
long msb = readLong();
if (msb == BufferConstants.UUID_NULL_MSB) {
return null;
} else {
return new UUID(msb, readLong());
}
}
@Override
public NBTTagCompound readNBT() {
int id = readByte();
if (id == -1) {
return null;
} else {
NBTTagCompound nbt = new NBTTagCompound();
Map<String, NBTBase> map = NBT.asMap(nbt);
try {
while (id != 0) {
String name = readString();
NBTBase tag = NBTBase.newTag((byte) id, name);
SCReflector.instance.load(nbt, this, 1);
map.put(tag.getName(), tag);
id = readByte();
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
return nbt;
}
}
@Override
public Item readItem() {
int id = readVarInt();
try {
return id == BufferConstants.ITEM_NULL_ID ? null : Item.itemsList[id];
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalStateException("Invalid ItemID " + id);
}
}
@Override
public Block readBlock() {
int id = readVarInt();
try {
return id == BufferConstants.BLOCK_NULL_ID ? null : Block.blocksList[id];
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalStateException("Invalid BlockID " + id);
}
}
@Override
public BlockPos readCoords() {
// return BlockPos.streamSerializer().read(this);
throw new UnsupportedOperationException();
}
}