package de.take_weiland.mods.commons.net;
import de.take_weiland.mods.commons.nbt.NBT;
import de.take_weiland.mods.commons.util.BlockPos;
import de.take_weiland.mods.commons.util.JavaUtils;
import de.take_weiland.mods.commons.util.SCReflector;
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.jetbrains.annotations.NotNull;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UTFDataFormatException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkPositionIndexes;
/**
* @author diesieben07
*/
final class MCDataOutputImpl extends OutputStream implements MCDataOutput {
private boolean locked = false;
private byte[] buf;
private int count;
MCDataOutputImpl(int initialCap) {
checkArgument(initialCap >= 0, "negative initial size");
buf = new byte[initialCap];
}
@Override
public int length() {
return count;
}
@Override
public void lock() {
locked = true;
}
@Override
public boolean isLocked() {
return locked;
}
@Override
public OutputStream asOutputStream() {
return this;
}
@Override
public byte[] backingArray() {
return buf;
}
@Override
public byte[] toByteArray() {
return Arrays.copyOf(buf, count);
}
@Override
public void writeTo(OutputStream stream) throws IOException {
stream.write(buf, 0, count);
}
@Override
public void writeTo(DataOutput out) throws IOException {
out.write(buf, 0, count);
}
@Override
public void writeTo(ByteBuffer buf) {
buf.put(this.buf, 0, count);
}
@Override
public void writeTo(WritableByteChannel channel) throws IOException {
channel.write(ByteBuffer.wrap(buf, 0, count));
}
final void ensureWritable(int bytesToWrite) {
if (locked) {
throw new IllegalStateException("Output locked!");
}
if ((count + bytesToWrite) - buf.length > 0) {
grow(bytesToWrite + count);
}
}
private void grow(int minCapacity) {
int oldCapacity = buf.length;
int newCapacity = oldCapacity << 1;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity < 0) {
if (minCapacity < 0)
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
buf = Arrays.copyOf(buf, newCapacity);
}
@Override
public void writeNulls(int n) {
ensureWritable(n);
count += n;
}
@Override
public void write(int b) {
writeByte(b);
}
@Override
public void write(@NotNull byte[] b) {
write(b, 0, b.length);
}
@Override
public void write(@NotNull byte[] b, int off, int len) {
ensureWritable(len);
System.arraycopy(b, off, buf, count, len);
count += len;
}
@Override
public void flush() { }
@Override
public void close() { }
@Override
public void writeBoolean(boolean v) {
write(v ? BufferConstants.BOOLEAN_TRUE : BufferConstants.BOOLEAN_FALSE);
}
@Override
public void writeBooleanBox(Boolean b) {
writeByte(b == null ? BufferConstants.BOOLEAN_NULL : (b ? BufferConstants.BOOLEAN_TRUE : BufferConstants.BOOLEAN_FALSE));
}
@Override
public void writeByte(int b) {
ensureWritable(1);
writeByteNBC(b);
}
private void writeByteNBC(int b) {
buf[count++] = (byte) b;
}
@Override
public void writeByteBox(Byte b) {
if (b == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(2);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeByteNBC(b);
}
}
@Override
public void writeShort(int s) {
ensureWritable(2);
writeShortNBC(s);
}
private void writeShortNBC(int s) {
writeByteNBC(s);
writeByteNBC(s >>> 8);
}
@Override
public void writeShortBox(Short s) {
if (s == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(3);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeShortNBC(s);
}
}
@Override
public void writeChar(int c) {
writeShort(c);
}
private void writeCharNBC(int c) {
writeShortNBC(c);
}
@Override
public void writeCharBox(Character c) {
if (c == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(3);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeCharNBC(c);
}
}
@Override
public void writeInt(int i) {
ensureWritable(4);
writeIntNBC(i);
}
private void writeIntNBC(int i) {
writeByteNBC(i);
writeByteNBC(i >>> 8);
writeByteNBC(i >>> 16);
writeByteNBC(i >>> 24);
}
@Override
public void writeIntBox(Integer i) {
if (i == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(5);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeIntNBC(i);
}
}
@Override
public void writeLong(long v) {
ensureWritable(8);
writeLongNBC(v);
}
private void writeLongNBC(long v) {
writeByteNBC((int) v);
writeByteNBC((int) (v >>> 8));
writeByteNBC((int) (v >>> 16));
writeByteNBC((int) (v >>> 24));
writeByteNBC((int) (v >>> 32));
writeByteNBC((int) (v >>> 40));
writeByteNBC((int) (v >>> 48));
writeByteNBC((int) (v >>> 56));
}
@Override
public void writeLongBox(Long l) {
if (l == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(9);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeLongNBC(l);
}
}
@Override
public void writeFloat(float v) {
writeInt(Float.floatToIntBits(v));
}
private void writeFloatNBC(float f) {
writeIntNBC(Float.floatToIntBits(f));
}
@Override
public void writeFloatBox(Float f) {
if (f == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(5);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeFloatNBC(f);
}
}
@Override
public void writeDouble(double v) {
writeLong(Double.doubleToLongBits(v));
}
private void writeDoubleNBC(double v) {
writeLongNBC(Double.doubleToLongBits(v));
}
@Override
public void writeDoubleBox(Double d) {
if (d == null) {
writeByte(BufferConstants.BOX_NULL);
} else {
ensureWritable(9);
writeByteNBC(BufferConstants.BOX_NONNULL);
writeDoubleNBC(d);
}
}
@Override
public void writeChars(@NotNull String s) {
int len = s.length();
ensureWritable(len << 1);
for (int i = 0; i < len; ++i) {
writeShortNBC(s.charAt(i));
}
}
@Override
public void writeUTF(@NotNull String s) {
char c;
int i;
int utfLen = 0;
int strLen = s.length();
for (i = 0; i < strLen; i++) {
c = s.charAt(i);
if (c >= 0x0001 && c <= 0x007f) {
utfLen++;
} else if (c >= 0x800) {
utfLen += 3;
} else {
utfLen += 2;
}
}
if (utfLen > 65535) {
throw new IllegalStateException(new UTFDataFormatException("Encoded String too long. " + utfLen + " > 65535"));
}
writeShort(utfLen);
ensureWritable(utfLen);
if (utfLen == strLen) {
for (i = 0; i < strLen; i++) {
writeByteNBC(s.charAt(i));
}
} else {
for (i = 0; i < strLen; i++) {
c = s.charAt(i);
if (c >= 0x0001 && c <= 0x007f) {
writeByteNBC(c);
} else if (c >= 0x800) {
writeByteNBC(0xE0 | ((c >> 12) & 0x0F));
writeByteNBC(0x80 | ((c >> 6) & 0x3F));
writeByteNBC(0x80 | (c & 0x3F));
} else {
writeByteNBC(0xC0 | ((c >> 6) & 0x1F));
writeByteNBC(0x80 | (c & 0x3F));
}
}
}
}
@Override
@Deprecated
public void writeBytes(@NotNull String s) {
int len = s.length();
ensureWritable(len);
for (int i = 0; i < len; i++) {
writeByteNBC(s.charAt(i));
}
}
private static int varIntLen(int i) {
return i < 0
? 5 // less than 0 always takes the full 32 bits (=5 bytes in VarInt)
: positiveVarIntLen(i);
}
private static int positiveVarIntLen(int i) {
return (i + 6) / 7; // divide by 7 and round up, see http://stackoverflow.com/a/7446742
}
@Override
public void writeVarInt(int i) {
ensureWritable(varIntLen(i));
writeVarIntNBC(i);
}
private void writePositiveVarInt(int i) {
ensureWritable(positiveVarIntLen(i));
writeVarIntNBC(i);
}
private void writeNegativeVarInt(int i) {
ensureWritable(5);
writeVarIntNBC(i);
}
private void writeVarIntNBC(int i) {
while ((i & ~BufferConstants.SEVEN_BITS) != 0) {
writeByteNBC(i & BufferConstants.SEVEN_BITS);
i >>>= 7;
}
writeByteNBC(i | (BufferConstants.BYTE_MSB));
}
@Override
public void writeString(String s) {
int len = s.length();
ensureWritable(positiveVarIntLen(len) + len << 1);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeShortNBC(s.charAt(i));
}
}
@Override
public void writeItemStack(ItemStack stack) {
if (stack == null) {
writeShort(-1);
} else {
ensureWritable(6); // 2 +2 + 1 + 1 (NBT needs at least 1 byte)
writeShortNBC(stack.itemID);
writeShortNBC(stack.getItemDamage());
writeByteNBC(stack.stackSize);
writeNBT(stack.stackTagCompound);
}
}
@Override
public void writeFluidStack(FluidStack stack) {
if (stack == null) {
writeVarInt(-1);
} else {
ensureWritable(varIntLen(stack.fluidID)
+ varIntLen(stack.amount) // technically amount is always >= 0, but we can't be sure
+ 1 /* NBT needs at least 1 byte*/ );
writeVarIntNBC(stack.fluidID);
writeVarIntNBC(stack.amount);
writeNBT(stack.tag);
}
}
@Override
public void writeNBT(NBTTagCompound nbt) {
if (nbt == null) {
writeByte(-1);
} else {
try {
for (NBTBase tag : NBT.asMap(nbt).values()) {
writeByte(tag.getId());
writeString(tag.getName());
SCReflector.instance.write(tag, this);
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public void writeItem(Item item) {
writePositiveVarInt(item == null ? BufferConstants.ITEM_NULL_ID : item.itemID);
}
@Override
public void writeBlock(Block block) {
writePositiveVarInt(block == null ? BufferConstants.BLOCK_NULL_ID : block.blockID);
}
@Override
public void writeCoords(int x, int y, int z) {
BlockPos.toByteStream(this, x, y, z);
}
@Override
public void writeUUID(UUID uuid) {
if (uuid == null) {
writeLong(BufferConstants.UUID_NULL_MSB);
} else {
ensureWritable(16);
writeLongNBC(uuid.getMostSignificantBits());
writeLongNBC(uuid.getLeastSignificantBits());
}
}
@Override
public <E extends Enum<E>> void writeEnum(E e) {
if (e != null) {
writePositiveVarInt(e.ordinal());
} else {
writeNegativeVarInt(-1);
}
}
@Override
public void writeBitSet(BitSet bitSet) {
if (bitSet == null) {
writeLongs(null);
} else {
writeLongs(bitSet.toLongArray());
}
}
@Override
public <E extends Enum<E>> void writeEnumSet(EnumSet<E> enumSet) {
if (enumSet == null) {
writeByte(0);
} else {
int numEnums = JavaUtils.getEnumConstantsShared(JavaUtils.getType(enumSet)).length;
if (numEnums <= 7) {
int b = 0;
for (E e : enumSet) {
b |= 2 << e.ordinal(); // actually: 1 << (ordinal + 1)
}
writeByte(b);
} else {
int numBytes = 1 + (numEnums >>> 3);
ensureWritable(numBytes);
int off = count;
for (E e : enumSet) {
int ord = e.ordinal() + 1;
buf[off + (ord & 7)] |= 1 << ord;
}
count += numBytes;
}
}
}
@Override
public void writeBooleans(boolean[] booleans) {
if (booleans == null) {
writeNegativeVarInt(-1);
} else {
writeBooleans0(booleans, 0, booleans.length);
}
}
@Override
public void writeBooleans(boolean[] booleans, int off, int len) {
checkPositionIndexes(off, off + len, booleans.length);
writeBooleans0(booleans, off, len);
}
private void writeBooleans0(boolean[] booleans, int off, int len) {
int bytesNeeded = (len + 7) >>> 3; // division by 8 and round up
ensureWritable(bytesNeeded + positiveVarIntLen(len));
writeVarIntNBC(len);
int currentByte = 0;
for (int idx = 0; idx < len; idx++) {
int bit = idx & 7;
currentByte |= (booleans[off + idx] ? 1 << bit : 0);
if (bit == 7) {
writeByteNBC(currentByte);
currentByte = 0;
}
}
if ((len & 7) != 0) {
writeByteNBC(currentByte);
}
}
@Override
public void writeBytes(byte[] bytes) {
if (bytes == null) {
writeNegativeVarInt(-1);
} else {
writeBytes0(bytes, 0, bytes.length);
}
}
@Override
public void writeBytes(byte[] bytes, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, bytes.length);
writeBytes0(bytes, off, len);
}
private void writeBytes0(byte[] bytes, int off, int len) {
ensureWritable(len + positiveVarIntLen(len));
writeVarIntNBC(len);
System.arraycopy(bytes, off, buf, count, len);
count += len;
}
@Override
public void writeShorts(short[] shorts) {
if (shorts == null) {
writeNegativeVarInt(-1);
} else {
writeShorts0(shorts, 0, shorts.length);
}
}
@Override
public void writeShorts(short[] shorts, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, shorts.length);
writeShorts0(shorts, off, len);
}
private void writeShorts0(short[] shorts, int off, int len) {
ensureWritable(positiveVarIntLen(len) + len << 1);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeShortNBC(shorts[off + i]);
}
}
@Override
public void writeInts(int[] ints) {
if (ints == null) {
writeNegativeVarInt(-1);
} else {
writeInts0(ints, 0, ints.length);
}
}
@Override
public void writeInts(int[] ints, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, ints.length);
writeInts0(ints, off, len);
}
private void writeInts0(int[] ints, int off, int len) {
ensureWritable(positiveVarIntLen(len) + len << 2);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeIntNBC(ints[i + off]);
}
}
@Override
public void writeLongs(long[] longs) {
if (longs == null) {
writeNegativeVarInt(-1);
} else {
writeLongs0(longs, 0, longs.length);
}
}
@Override
public void writeLongs(long[] longs, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, longs.length);
writeLongs0(longs, off, len);
}
private void writeLongs0(long[] longs, int off, int len) {
ensureWritable(positiveVarIntLen(len) + len << 3);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeLongNBC(longs[i + off]);
}
}
@Override
public void writeChars(char[] chars) {
if (chars == null) {
writeNegativeVarInt(-1);
} else {
writeChars0(chars, 0, chars.length);
}
}
@Override
public void writeChars(char[] chars, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, chars.length);
writeChars0(chars, off, len);
}
private void writeChars0(char[] chars, int off, int len) {
ensureWritable(positiveVarIntLen(len) + len << 1);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeCharNBC(chars[i + off]);
}
}
@Override
public void writeFloats(float[] floats) {
if (floats == null) {
writeNegativeVarInt(-1);
} else {
writeFloats0(floats, 0, floats.length);
}
}
@Override
public void writeFloats(float[] floats, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, floats.length);
writeFloats0(floats, off, len);
}
private void writeFloats0(float[] floats, int off, int len) {
ensureWritable(positiveVarIntLen(len) + len << 2);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeFloatNBC(floats[i + off]);
}
}
@Override
public void writeDoubles(double[] doubles) {
if (doubles == null) {
writeVarInt(-1);
} else {
writeDoubles0(doubles, 0, doubles.length);
}
}
@Override
public void writeDoubles(double[] doubles, int off, int len) {
checkArgument(len >= 0, "len must be >= 0");
checkPositionIndexes(off, off + len, doubles.length);
writeDoubles0(doubles, off, len);
}
private void writeDoubles0(double[] doubles, int off, int len) {
ensureWritable(positiveVarIntLen(len) + len << 3);
writeVarIntNBC(len);
for (int i = 0; i < len; i++) {
writeDoubleNBC(doubles[i + off]);
}
}
}