package mhfc.net.common.util.io;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.ObjIntConsumer;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.client.model.ModelFormatException;
/**
* {@link IOInterface} implementations for {@link NBTTagCompound} as raw data type.
*
* <p>
* Usage: <code>
* // TODO: insert usage
* </code>
*
* @author WorldSEnder
*
*/
public class NBTIO {
private static <M, B> NBTIOOperation<M, B> combine(NBTReadOperation<B> read, NBTWriteOperation<M> write) {
return new NBTIOOperation<M, B>() {
@Override
public Optional<RuntimeException> read(B builder, NBTTagCompound tag) {
return read.read(builder, tag);
}
@Override
public void write(NBTTagCompound builtTag, M model) {
write.write(builtTag, model);
}
};
}
private static Optional<RuntimeException> illegalFormat(String message) {
return Optional.of(new ModelFormatException(message));
}
private static Optional<RuntimeException> success() {
return Optional.empty();
}
// === Interfaces
public static interface NBTReadOperation<B> {
// null if successful
Optional<RuntimeException> read(B builder, NBTTagCompound tag);
}
public static interface NBTWriteOperation<M> {
// always successful
void write(NBTTagCompound builtTag, M model);
}
public static interface NBTIOOperation<M, B> extends NBTReadOperation<B>, NBTWriteOperation<M> {}
// === Operations
public static <B> NBTReadOperation<B> readInt(String nbtTagName, ObjIntConsumer<B> application) {
return (b, tag) -> {
if (!tag.hasKey(nbtTagName, net.minecraftforge.common.util.Constants.NBT.TAG_INT)) {
return illegalFormat("no key " + nbtTagName + " of type int");
}
int i = tag.getInteger(nbtTagName);
application.accept(b, i);
return success();
};
}
public static <M> NBTWriteOperation<M> writeInt(String nbtTagName, ToIntFunction<M> retrieve) {
return (tag, model) -> {
tag.setInteger(nbtTagName, retrieve.applyAsInt(model));
};
}
public static <B, M> NBTIOOperation<M, B> readWriteInt(
String nbtTagName,
ObjIntConsumer<B> application,
ToIntFunction<M> retrieve) {
return combine(readInt(nbtTagName, application), writeInt(nbtTagName, retrieve));
}
public static <B, N> NBTReadOperation<B> readNested(
String nbtTagName,
BiConsumer<B, N> application,
ReadInterface<NBTTagCompound, N> reader) {
return (b, tag) -> {
if (!tag.hasKey(nbtTagName, net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND)) {
return illegalFormat("no key " + nbtTagName + " of type compound");
}
N nested = reader.read(tag.getCompoundTag(nbtTagName));
application.accept(b, nested);
return success();
};
}
public static <M, N> NBTWriteOperation<M> writeNested(
String nbtTagName,
Function<M, N> retrieve,
WriteInterface<NBTTagCompound, N> writer) {
return (tag, model) -> {
NBTTagCompound nestedTag = writer.write(retrieve.apply(model));
tag.setTag(nbtTagName, nestedTag);
};
}
public static <B, M, N> NBTIOOperation<M, B> readWriteNested(
String nbtTagName,
BiConsumer<B, N> application,
Function<M, N> retrieve,
IOInterface<NBTTagCompound, N> nestedIO) {
return combine(readNested(nbtTagName, application, nestedIO), writeNested(nbtTagName, retrieve, nestedIO));
}
/**
* Tries all reads after one another from the first to the last. Can be used for version control. The newest is
* implemented first, but will fail if an older version is stored.
*
* @param reads
* @return
*/
@SafeVarargs
public static <B> NBTReadOperation<B> cascadingRead(NBTReadOperation<B> first, NBTReadOperation<B>... reads) {
Objects.requireNonNull(first);
mhfc.net.common.util.Objects.requireNonNullDeep(reads);
return (b, tag) -> {
Optional<RuntimeException> lastExc = first.read(b, tag);
if (!lastExc.isPresent()) {
return success();
}
for (NBTReadOperation<B> r : reads) {
Optional<RuntimeException> result = lastExc = r.read(b, tag);
if (result.isPresent()) {
continue;
}
return success();
}
return lastExc;
};
}
@SafeVarargs
public static <B, M> NBTIOOperation<M, B> cascadingReadWrite(
NBTIOOperation<M, B> readWrite,
NBTReadOperation<B>... reads) {
//mhfc.net.common.util.Objects.requireNonNullDeep(reads);
return combine(cascadingRead(readWrite, reads), readWrite);
}
// === Builder classes
public static class NBTReaderBuilder<M, B> {
private Supplier<B> builderSupply;
private Function<B, M> finalOperation;
private List<BiConsumer<B, NBTTagCompound>> buildSteps;
private BiConsumer<B, NBTTagCompound> readSafe(NBTReadOperation<B> reader) {
Objects.requireNonNull(reader);
return (b, tag) -> {
Optional<RuntimeException> onError = reader.read(b, tag);
if (!onError.isPresent()) {
return;
}
throw onError.get();
};
}
public NBTReaderBuilder(Supplier<B> builderSupply, Function<B, M> finalOperation) {
this.builderSupply = Objects.requireNonNull(builderSupply);
this.finalOperation = Objects.requireNonNull(finalOperation);
this.buildSteps = new ArrayList<>();
}
public NBTReaderBuilder<M, B> with(NBTReadOperation<B> operation) {
this.buildSteps.add(readSafe(operation));
return this;
}
public ReadInterface<NBTTagCompound, M> buildReader() {
return new ReaderWithBuilder<>(builderSupply, buildSteps, finalOperation);
}
}
public static class NBTWriterBuilder<M> {
private List<BiConsumer<NBTTagCompound, M>> consumers;
private BiConsumer<NBTTagCompound, M> writeSafe(NBTWriteOperation<M> writer) {
return writer::write;
}
public NBTWriterBuilder() {
this.consumers = new ArrayList<>();
}
public NBTWriterBuilder<M> with(NBTWriteOperation<M> operation) {
this.consumers.add(writeSafe(operation));
return this;
}
public WriteInterface<NBTTagCompound, M> buildWriter() {
return new WriterWithBuilder<>(NBTTagCompound::new, consumers, Function.identity());
}
}
public static class NBTIOBuilder<M, B> {
private NBTReaderBuilder<M, B> readerBuild;
private NBTWriterBuilder<M> writerBuild;
public NBTIOBuilder(Supplier<B> builderSupply, Function<B, M> finalOperation) {
readerBuild = new NBTReaderBuilder<>(builderSupply, finalOperation);
writerBuild = new NBTWriterBuilder<>();
}
public NBTIOBuilder<M, B> with(NBTIOOperation<M, B> operation) {
this.readerBuild.with(operation);
this.writerBuild.with(operation);
return this;
}
public IOInterface<NBTTagCompound, M> build() {
return new CombinedReadWriter<>(readerBuild.buildReader(), writerBuild.buildWriter());
}
}
}