package openmods.structured;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
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.UpdateSingle;
public class StructuredDataMaster<C extends IStructureContainer<E>, E extends IStructureElement> extends StructuredData<C, E> {
public static final int CONSISTENCY_CHECK_PERIOD = 10;
private final Set<Integer> newContainers = Sets.newTreeSet();
private final Set<Integer> deletedContainers = Sets.newTreeSet();
private final Set<Integer> modifiedElements = Sets.newTreeSet();
private byte checkCount;
private int nextElementId;
private int nextContainerId;
private boolean fullUpdateNeeded;
public StructuredDataMaster() {
super();
}
public StructuredDataMaster(IStructureObserver<C, E> observer) {
super(observer);
}
public synchronized void appendUpdateCommands(List<Command> commands) {
if (fullUpdateNeeded) {
createFullCommands(commands);
fullUpdateNeeded = false;
} else {
createUpdateCommands(commands);
}
clearUpdates();
}
public synchronized void appendFullCommands(List<Command> commands) {
createFullCommands(commands);
}
private void createFullCommands(List<Command> commands) {
commands.add(Command.RESET_INST);
if (!containers.isEmpty()) {
appendContainersCreate(commands, containers.keySet());
commands.add(createConsistencyCheck());
}
}
private void createUpdateCommands(List<Command> commands) {
boolean addCheck = (checkCount++) % CONSISTENCY_CHECK_PERIOD == 0;
if (!deletedContainers.isEmpty()) {
addCheck = true;
Command.Delete delete = new Delete();
delete.idList.addAll(deletedContainers);
commands.add(delete);
newContainers.removeAll(deletedContainers);
}
if (!newContainers.isEmpty()) {
addCheck = true;
Set<Integer> newElements = appendContainersCreate(commands, newContainers);
modifiedElements.removeAll(newElements);
}
if (!modifiedElements.isEmpty()) {
Command.UpdateSingle update = new UpdateSingle();
update.idList.addAll(modifiedElements);
update.elementPayload = createElementPayload(modifiedElements);
commands.add(update);
}
if (addCheck) commands.add(createConsistencyCheck());
}
private synchronized Set<Integer> appendContainersCreate(List<Command> commands, final Set<Integer> containersToSend) {
Set<Integer> newElements = Sets.newTreeSet();
Command.Create create = new Create();
for (Integer containerId : containersToSend) {
C container = containers.get(containerId);
SortedSet<Integer> containerContents = containerToElement.get(containerId);
newElements.addAll(containerContents);
int firstContainerElement = containerContents.first();
create.containers.add(new ContainerInfo(containerId, container.getType(), firstContainerElement));
}
create.containerPayload = createContainerPayload(containersToSend);
create.elementPayload = createElementPayload(newElements);
commands.add(create);
return newElements;
}
private synchronized ConsistencyCheck createConsistencyCheck() {
ConsistencyCheck check = new ConsistencyCheck();
SortedSet<Integer> containers = containerToElement.keySet();
if (!containers.isEmpty()) {
check.containerCount = containers.size();
check.minContainerId = containers.first();
check.maxContainerId = containers.last();
}
if (!elements.isEmpty()) {
check.elementCount = elements.size();
check.minElementId = elements.firstKey();
check.maxElementId = elements.lastKey();
}
return check;
}
@Override
public void removeAll() {
super.removeAll();
observer.onStructureUpdate();
fullUpdateNeeded = true;
clearUpdates();
checkCount = 0;
nextElementId = 0;
nextContainerId = 0;
}
private synchronized void clearUpdates() {
newContainers.clear();
deletedContainers.clear();
modifiedElements.clear();
}
public boolean hasUpdates() {
return fullUpdateNeeded || !(newContainers.isEmpty() && deletedContainers.isEmpty() && modifiedElements.isEmpty());
}
public synchronized void markElementModified(int elementId) {
final E element = elements.get(elementId);
Preconditions.checkArgument(element != null, "No element with id %s", elementId);
modifiedElements.add(elementId);
final int containerId = elementToContainer.get(elementId);
Preconditions.checkState(containerId != NULL, "Inconsistent state for element %s", elementId);
final C container = containers.get(containerId);
Preconditions.checkState(container != null, "Inconsistent state for element %s, container %s", elementId, containerId);
observer.onContainerUpdated(containerId, container);
observer.onElementUpdated(containerId, container, elementId, element);
observer.onDataUpdate();
}
public synchronized int addContainer(C container) {
int containerId = nextContainerId++;
nextElementId = addContainer(containerId, container, nextElementId);
newContainers.add(containerId);
observer.onStructureUpdate();
return containerId;
}
@Override
public synchronized SortedSet<Integer> removeContainer(int containerId) {
SortedSet<Integer> removedElements = super.removeContainer(containerId);
boolean isNewContainer = newContainers.remove(containerId);
if (!isNewContainer) deletedContainers.add(containerId);
modifiedElements.removeAll(removedElements);
observer.onStructureUpdate();
return removedElements;
}
private byte[] createContainerPayload(Set<Integer> containerIds) {
try {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
for (Integer id : containerIds) {
final C c = containers.get(id);
if (c instanceof ICustomCreateData) ((ICustomCreateData)c).writeCustomDataFromStream(output);
}
return output.toByteArray();
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
private byte[] createElementPayload(Collection<Integer> ids) {
try {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
for (Integer id : ids) {
E element = elements.get(id);
element.writeToStream(output);
}
return output.toByteArray();
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
}