package gregtech.common.gui.modularui.widget;
import java.io.IOException;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.network.PacketBuffer;
import com.gtnewhorizons.modularui.api.widget.ISyncedWidget;
import com.gtnewhorizons.modularui.api.widget.Widget;
import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
import com.gtnewhorizons.modularui.common.widget.MultiChildWidget;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import gregtech.api.gui.modularui.IDataFollowerWidget;
import gregtech.api.util.ISerializableObject;
/**
* Controls state of child widgets with specific data, and allows centralized control of multiple widgets. e.g. clicking
* button B will set machine mode to B, so button A, whose state is bound to the mode, will be automatically deactivated
* by this widget.
* This widget wraps data and handles validation, e.g. tell client to close GUI when tile is broken or cover is removed.
*
* Data can be anything, e.g. {@link ISerializableObject} or machine recipe mode.
*
* @param Data type stored in this widget
* @see IDataFollowerWidget
*/
public abstract class DataControllerWidget extends MultiChildWidget implements ISyncedWidget {
private final Supplier dataGetter;
private final Function dataSetter;
protected T lastData;
private boolean needsUpdate;
/**
* @param dataGetter () -> data this widget handles
* @param dataSetter data to set -> if setting data is successful
*/
public DataControllerWidget(Supplier dataGetter, Function dataSetter) {
this.dataGetter = dataGetter;
this.dataSetter = dataSetter;
}
protected T getLastData() {
return lastData;
}
@Override
public void onPostInit() {
// client _should_ have received initial cover data from `GT_UIInfos#openCoverUI`
lastData = dataGetter.get();
if (NetworkUtils.isClient()) {
updateChildren(true);
}
}
@Override
public void detectAndSendChanges(boolean init) {
T actualValue = dataGetter.get();
if (actualValue == null) {
// data is in invalid state e.g. tile is broken, cover is removed
getWindow().tryClose();
return;
}
if (init || !actualValue.equals(getLastData())) {
// init sync or someone else edited data
lastData = actualValue;
syncDataToClient(actualValue);
}
}
protected void syncDataToClient(T data) {
syncToClient(0, buffer -> writeToPacket(buffer, data));
}
protected void syncDataToServer(T data) {
syncToServer(0, buffer -> writeToPacket(buffer, data));
updateChildren();
}
@Override
public void readOnClient(int id, PacketBuffer buf) throws IOException {
if (id == 0) {
lastData = readFromPacket(buf);
dataSetter.apply(getLastData());
updateChildren();
}
}
@Override
public void readOnServer(int id, PacketBuffer buf) throws IOException {
if (id == 0) {
lastData = readFromPacket(buf);
if (dataSetter.apply(getLastData())) {
markForUpdate();
} else {
getWindow().closeWindow();
}
}
}
@SuppressWarnings("unchecked")
@SideOnly(Side.CLIENT)
protected void updateChildren(boolean postInit) {
for (Widget child : getChildren()) {
if (child instanceof IDataFollowerWidget) {
((IDataFollowerWidget) child).updateState(getLastData());
if (postInit) {
((IDataFollowerWidget) child).onPostInit();
}
}
}
}
@SideOnly(Side.CLIENT)
protected void updateChildren() {
updateChildren(false);
}
protected abstract void writeToPacket(PacketBuffer buffer, T data);
protected abstract T readFromPacket(PacketBuffer buffer) throws IOException;
@Override
public void markForUpdate() {
needsUpdate = true;
}
@Override
public void unMarkForUpdate() {
needsUpdate = false;
}
@Override
public boolean isMarkedForUpdate() {
return needsUpdate;
}
/**
* @param widget widget to add that implements {@link IDataFollowerWidget}
* @param dataToStateGetter given data -> state of the widget to add
* @param dataUpdater (current data, state of the widget to add) -> new data to set
* @param applyForWidget methods to call for the widget to add
* @param state type stored in the widget to add
* @param widget type to add
*/
public > DataControllerWidget addFollower(W widget,
Function dataToStateGetter, BiFunction dataUpdater, Consumer applyForWidget) {
widget.setDataToStateGetter(dataToStateGetter);
widget.setStateSetter(state -> {
T newData = dataUpdater.apply(getLastData(), state);
lastData = newData;
dataSetter.apply(getLastData());
syncDataToServer(newData);
});
applyForWidget.accept(widget);
addChild(widget);
return this;
}
}