package openmods.structured;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import openmods.utils.ByteUtils;
import openmods.utils.CollectionUtils;
public abstract class Command {
public static final Comparator<Command> COMPARATOR = new Comparator<Command>() {
@Override
public int compare(Command o1, Command o2) {
return o1.type().compareTo(o2.type());
}
};
public static class CommandList extends ArrayList<Command> {
private static final long serialVersionUID = 8317603452787461684L;
public void readFromStream(DataInput input) throws IOException {
while (true) {
Command command = Command.createFromStream(input);
if (command.isEnd()) return;
add(command);
}
}
public void writeToStream(DataOutput output) throws IOException {
Collections.sort(this, Command.COMPARATOR);
for (Command c : this) {
c.writeToStream(output);
if (c.isEnd()) return;
}
END_INST.writeToStream(output);
}
}
public enum Type { // DO NOT REORDER
RESET {
@Override
public Reset create() {
return RESET_INST;
}
},
DELETE {
@Override
public Delete create() {
return new Delete();
}
},
CREATE {
@Override
public Create create() {
return new Create();
}
},
UPDATE_SINGLE {
@Override
public UpdateSingle create() {
return new UpdateSingle();
}
},
UPDATE_BULK {
@Override
public UpdateBulk create() {
return new UpdateBulk();
}
},
CONSISTENCY_CHECK {
@Override
public ConsistencyCheck create() {
return new ConsistencyCheck();
}
},
END { // must be last!
@Override
public EmptyCommand create() {
return END_INST;
}
};
public abstract Command create();
public static final Type[] TYPES = values();
}
public static class ConsistencyCheck extends Command {
public int elementCount;
public int minElementId;
public int maxElementId;
public int containerCount;
public int minContainerId;
public int maxContainerId;
@Override
public Type type() {
return Type.CONSISTENCY_CHECK;
}
@Override
protected void readDataFromStream(DataInput input) {
elementCount = ByteUtils.readVLI(input);
minElementId = ByteUtils.readVLI(input);
maxElementId = ByteUtils.readVLI(input);
containerCount = ByteUtils.readVLI(input);
minContainerId = ByteUtils.readVLI(input);
maxContainerId = ByteUtils.readVLI(input);
}
@Override
protected void writeDataToStream(DataOutput output) {
ByteUtils.writeVLI(output, elementCount);
ByteUtils.writeVLI(output, minElementId);
ByteUtils.writeVLI(output, maxElementId);
ByteUtils.writeVLI(output, containerCount);
ByteUtils.writeVLI(output, minContainerId);
ByteUtils.writeVLI(output, maxContainerId);
}
@Override
public String dumpContents() {
return "[elementCount=" + elementCount + ", minElementId=" + minElementId + ", maxElementId=" + maxElementId + ", containerCount=" + containerCount + ", minContainerId=" + minContainerId + ", maxContainerId=" + maxContainerId + "]";
}
}
public abstract static class EmptyCommand extends Command {
@Override
protected void readDataFromStream(DataInput input) {}
@Override
protected void writeDataToStream(DataOutput output) {}
}
public static final class Reset extends EmptyCommand {
@Override
public Type type() {
return Type.RESET;
}
}
static final Reset RESET_INST = new Reset();
private static final EmptyCommand END_INST = new EmptyCommand() {
@Override
public Type type() {
return Type.END;
}
@Override
public boolean isEnd() {
return true;
}
};
public static class ContainerInfo {
public final int id;
public final int type;
public final int start;
public ContainerInfo(int id, int type, int start) {
this.id = id;
this.type = type;
this.start = start;
}
@Override
public String toString() {
return "[id=" + id + ", type=" + type + ", start=" + start + "]";
}
}
public static class Delete extends Command {
public final SortedSet<Integer> idList = Sets.newTreeSet();
@Override
public Type type() {
return Type.DELETE;
}
@Override
protected void readDataFromStream(DataInput input) {
CollectionUtils.readSortedIdList(input, idList);
}
@Override
protected void writeDataToStream(DataOutput output) {
CollectionUtils.writeSortedIdList(output, idList);
}
@Override
public String dumpContents() {
return String.valueOf(idList);
}
}
public static class Create extends Command {
public final List<ContainerInfo> containers = Lists.newArrayList();
byte[] containerPayload;
byte[] elementPayload;
@Override
public Type type() {
return Type.CREATE;
}
@Override
protected void readDataFromStream(DataInput input) throws IOException {
int elemCount = ByteUtils.readVLI(input);
int currentContainerId = 0;
int currentElementId = 0;
for (int i = 0; i < elemCount; i++) {
currentContainerId += ByteUtils.readVLI(input);
int type = ByteUtils.readVLI(input);
currentElementId += ByteUtils.readVLI(input);
containers.add(new ContainerInfo(currentContainerId, type, currentElementId));
}
containerPayload = readChunk(input);
elementPayload = readChunk(input);
}
@Override
protected void writeDataToStream(DataOutput output) throws IOException {
ByteUtils.writeVLI(output, containers.size());
int prevContainerId = 0;
int prevElementId = 0;
for (ContainerInfo info : containers) {
int deltaContainerId = info.id - prevContainerId;
Preconditions.checkArgument(deltaContainerId >= 0, "Container ids must be sorted in ascending order");
int deltaElementId = info.start - prevElementId;
Preconditions.checkArgument(deltaElementId >= 0, "Element ids must be sorted in ascending order");
ByteUtils.writeVLI(output, deltaContainerId);
ByteUtils.writeVLI(output, info.type);
ByteUtils.writeVLI(output, deltaElementId);
prevContainerId = info.id;
prevElementId = info.start;
}
writeChunk(output, containerPayload);
writeChunk(output, elementPayload);
}
@Override
public String dumpContents() {
return String.format("%s -> %s", containers,
(elementPayload == null? "<null>" : Integer.toString(elementPayload.length)));
}
}
public abstract static class Update extends Command {
public final SortedSet<Integer> idList = Sets.newTreeSet();
byte[] elementPayload;
@Override
protected void readDataFromStream(DataInput input) throws IOException {
elementPayload = readChunk(input);
}
@Override
protected void writeDataToStream(DataOutput output) throws IOException {
writeChunk(output, elementPayload);
}
}
public static class UpdateSingle extends Update {
@Override
public Type type() {
return Type.UPDATE_SINGLE;
}
@Override
protected void readDataFromStream(DataInput input) throws IOException {
CollectionUtils.readSortedIdList(input, idList);
super.readDataFromStream(input);
}
@Override
protected void writeDataToStream(DataOutput output) throws IOException {
CollectionUtils.writeSortedIdList(output, idList);
super.writeDataToStream(output);
}
@Override
public String dumpContents() {
return String.format("%s -> %s", idList,
(elementPayload == null? "<null>" : Integer.toString(elementPayload.length)));
}
}
// TODO Implement
public static class UpdateBulk extends Update {
@Override
public Type type() {
return Type.UPDATE_BULK;
}
@Override
protected void readDataFromStream(DataInput input) throws IOException {
super.readDataFromStream(input);
}
@Override
protected void writeDataToStream(DataOutput output) throws IOException {
super.writeDataToStream(output);
}
}
public abstract Type type();
protected abstract void readDataFromStream(DataInput input) throws IOException;
protected abstract void writeDataToStream(DataOutput output) throws IOException;
public static Command createFromStream(DataInput input) throws IOException {
int id = ByteUtils.readVLI(input);
Type type = Type.TYPES[id];
Command command = type.create();
command.readDataFromStream(input);
return command;
}
public void writeToStream(DataOutput output) throws IOException {
ByteUtils.writeVLI(output, type().ordinal());
writeDataToStream(output);
}
protected static byte[] readChunk(DataInput input) throws IOException {
int size = ByteUtils.readVLI(input);
byte[] chunk = new byte[size];
input.readFully(chunk);
return chunk;
}
protected static void writeChunk(DataOutput output, byte[] chunk) throws IOException {
ByteUtils.writeVLI(output, chunk.length);
output.write(chunk);
}
protected boolean isEnd() {
return false;
}
public String dumpContents() {
return "";
}
@Override
public String toString() {
return type() + ": " + dumpContents();
}
}