package openmods.structured;
import com.google.common.base.Throwables;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import java.io.DataInput;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import openmods.structured.Command.ConsistencyCheck;
import openmods.structured.Command.ContainerInfo;
import openmods.structured.Command.Create;
import openmods.structured.Command.Delete;
import openmods.structured.Command.Reset;
import openmods.structured.Command.Update;
public abstract class StructuredDataSlave<C extends IStructureContainer<E>, E extends IStructureElement> extends StructuredData<C, E> {
public static class ConsistencyCheckFailed extends RuntimeException {
private static final long serialVersionUID = 1L;
public ConsistencyCheckFailed(String message) {
super(message);
}
public ConsistencyCheckFailed(String format, Object... args) {
super(String.format(format, args));
}
public ConsistencyCheckFailed(Throwable cause, String format, Object... args) {
super(String.format(format, args), cause);
}
}
public final IStructureContainerFactory<C> factory;
protected StructuredDataSlave(IStructureContainerFactory<C> factory, IStructureObserver<C, E> observer) {
super(observer);
this.factory = factory;
}
protected StructuredDataSlave(IStructureContainerFactory<C> factory) {
super();
this.factory = factory;
}
protected abstract void onConsistencyCheckFail();
public void interpretCommandList(List<Command> commands) {
Multimap<Integer, Integer> updatedContainers = HashMultimap.create();
boolean isStructureUpdated = false;
observer.onUpdateStarted();
for (Command c : commands) {
try {
if (c.isEnd()) break;
else if (c instanceof ConsistencyCheck) {
final ConsistencyCheck msg = (ConsistencyCheck)c;
SortedSet<Integer> containers = containerToElement.keySet();
final int containerCount = containers.size();
final int minContainerId = containerCount == 0? 0 : containers.first();
final int maxContainerId = containerCount == 0? 0 : containers.last();
final int elementCount = elements.size();
final int minElementId = elementCount == 0? 0 : elements.firstKey();
final int maxElementId = elementCount == 0? 0 : elements.lastKey();
if (msg.containerCount != containerCount ||
msg.minContainerId != minContainerId ||
msg.maxContainerId != maxContainerId ||
msg.elementCount != elementCount ||
msg.minElementId != minElementId ||
msg.maxElementId != maxElementId)
throw new ConsistencyCheckFailed("Validation packet not matched");
} else if (c instanceof Reset) {
removeAll();
isStructureUpdated = true;
} else if (c instanceof Create) {
final Create msg = (Create)c;
SortedSet<Integer> containers = Sets.newTreeSet();
SortedSet<Integer> elements = Sets.newTreeSet();
final ByteArrayDataInput input = ByteStreams.newDataInput(msg.containerPayload);
for (ContainerInfo info : msg.containers) {
SortedSet<Integer> newElementsId = createAndAddContainer(input, info.type, info.id, info.start);
elements.addAll(newElementsId);
updatedContainers.putAll(info.id, newElementsId);
containers.add(info.id);
}
if (input.skipBytes(1) != 0) throw new ConsistencyCheckFailed("Container payload not fully consumed");
readElementPayload(elements, msg.elementPayload);
isStructureUpdated = true;
} else if (c instanceof Delete) {
final Delete msg = (Delete)c;
for (int i : msg.idList)
removeContainer(i);
isStructureUpdated = true;
} else if (c instanceof Update) {
final Update msg = (Update)c;
readElementPayload(msg.idList, msg.elementPayload);
for (Integer elementId : msg.idList) {
int containerId = elementToContainer.get(elementId);
if (containerId == NULL) throw new ConsistencyCheckFailed("Orphaned element %d", elementId);
updatedContainers.put(containerId, elementId);
}
}
} catch (ConsistencyCheckFailed e) {
onConsistencyCheckFail();
break;
}
}
if (isStructureUpdated) observer.onStructureUpdate();
for (Map.Entry<Integer, Collection<Integer>> e : updatedContainers.asMap().entrySet()) {
final Integer containerId = e.getKey();
final C container = containers.get(containerId);
observer.onContainerUpdated(containerId, container);
for (Integer elementId : e.getValue()) {
final E element = elements.get(elementId);
observer.onElementUpdated(containerId, container, elementId, element);
}
}
if (!updatedContainers.isEmpty()) observer.onDataUpdate();
observer.onUpdateFinished();
}
private SortedSet<Integer> createAndAddContainer(ByteArrayDataInput input, int type, int containerId, int start) {
C container = factory.createContainer(type);
try {
if (container instanceof ICustomCreateData) ((ICustomCreateData)container).readCustomDataFromStream(input);
} catch (IOException e) {
throw new ConsistencyCheckFailed(e, "Failed to read element %d, type %d", containerId, type);
}
if (containerToElement.containsEntry(containerId, start)) throw new ConsistencyCheckFailed("Container %d already exists", containerId);
addContainer(containerId, container, start);
return containerToElement.get(containerId);
}
private void readElementPayload(SortedSet<Integer> ids, byte[] payload) {
try {
DataInput input = ByteStreams.newDataInput(payload);
for (Integer id : ids) {
final E element = elements.get(id);
if (element == null) throw new ConsistencyCheckFailed("Element %d not found", id);
element.readFromStream(input);
}
if (input.skipBytes(1) != 0) throw new ConsistencyCheckFailed("Element payload not fully consumed");
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
}