package ua.com.minersstudios.whomine.util.misc;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
final class WorldRegion extends RandomAccessFile
{
WorldRegion(final File file, final String mode) throws FileNotFoundException
{
super(file, mode);
}
WorldRegion(final String name, final String mode) throws FileNotFoundException
{
super(name, mode);
}
private final int fourKiBSector = 4096;
private final int offsetSize = 3;
private final int timestampSize = 4;
private final int lengthSize = 4;
private final int sectorSizeStartsAfter = 3;
private final int compressionTypeStartsAfter = 4;
private final int dataStartsAfter = 5;
private int locateChunkInsideHeader(final int x, final int z) { return 4 * ((x & 31) + (z & 31) * 32); }
int getChunkOffset(final int x, final int z) throws IOException
{
byte[] rawChunkOffset = new byte[offsetSize];
readFully(rawChunkOffset, rawChunkOffset.length, locateChunkInsideHeader(x, z));
return ByteBuffer.allocate(rawChunkOffset.length).order(ByteOrder.BIG_ENDIAN).put(rawChunkOffset).getInt();
}
byte getChunkSectorCount(final int x, final int z) throws IOException
{
final long previousFilePointer = getFilePointer();
seek(locateChunkInsideHeader(x, z) + sectorSizeStartsAfter);
byte chunkSectorCount = readByte();
seek(previousFilePointer);
return chunkSectorCount;
}
int getChunkModificationDate(final int x, final int z) throws IOException
{
byte[] rawChunkModificationDate = new byte[timestampSize];
readFully(rawChunkModificationDate, rawChunkModificationDate.length, fourKiBSector | locateChunkInsideHeader(x, z));
return ByteBuffer.allocate(rawChunkModificationDate.length).order(ByteOrder.BIG_ENDIAN).put(rawChunkModificationDate).getInt();
}
private int getChunkRemainingLength(final int x, final int z) throws IOException
{
byte[] rawChunkRemainingSize = new byte[lengthSize];
readFully(rawChunkRemainingSize, rawChunkRemainingSize.length, getChunkOffset(x, z) * fourKiBSector);
return ByteBuffer.allocate(rawChunkRemainingSize.length).order(ByteOrder.BIG_ENDIAN).put(rawChunkRemainingSize).getInt();
}
CompressionType getChunkCompressionType(final int x, final int z) throws IOException
{
final long previousFilePointer = getFilePointer();
seek(getChunkOffset(x, z) * fourKiBSector + compressionTypeStartsAfter);
byte rawChunkCompressionType = readByte();
seek(previousFilePointer);
return CompressionType.valueOf(rawChunkCompressionType);
}
byte[] getChunkDataPadded(final int x, final int z) throws IOException
{
byte[] rawChunkData = new byte[getChunkSectorCount(x, z) * fourKiBSector];
readFully(rawChunkData, rawChunkData.length, getChunkOffset(x, z) * fourKiBSector);
return rawChunkData;
}
byte[] getChunkData(final int x, final int z) throws IOException
{
assert getChunkCompressionType(x, z) != CompressionType.EXTERNAL;
byte[] rawChunkData = new byte[getChunkRemainingLength(x, z) - 1];
readFully(rawChunkData, rawChunkData.length, getChunkOffset(x, z) * fourKiBSector + dataStartsAfter);
return rawChunkData;
}
@RequiredArgsConstructor
enum CompressionType
{
RFC1952(1),
RFC1950(2),
NONE(3),
EXTERNAL(129);
private final int value;
private final static List<CompressionType> values;
static
{
values = new ArrayList<>();
for (CompressionType ct : values()) values.add(ct.ordinal(), ct);
}
public static CompressionType valueOf(final int value)
{
return value < EXTERNAL.ordinal() ? values.get(value) : EXTERNAL;
}
}
}