package mhfc.net.common.world.controller;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import com.sk89q.worldedit.function.operation.Operation;
import mhfc.net.MHFCMain;
import mhfc.net.common.eventhandler.MHFCTickHandler;
import mhfc.net.common.eventhandler.TickPhase;
import mhfc.net.common.util.Operations;
import mhfc.net.common.world.MHFCWorldData;
import mhfc.net.common.world.MHFCWorldData.AreaInformation;
import mhfc.net.common.world.area.ActiveAreaAdapter;
import mhfc.net.common.world.area.AreaConfiguration;
import mhfc.net.common.world.area.IActiveArea;
import mhfc.net.common.world.area.IArea;
import mhfc.net.common.world.area.IAreaType;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.common.ForgeChunkManager.Ticket;
import net.minecraftforge.common.ForgeChunkManager.Type;
public class AreaManager implements IAreaManager {
private static class Active extends ActiveAreaAdapter {
private IArea area;
private IAreaType type;
private AreaManager ref;
public Active(IArea area, IAreaType type, AreaManager ref) {
this.area = Objects.requireNonNull(area);
this.type = Objects.requireNonNull(type);
this.ref = Objects.requireNonNull(ref);
this.area.onAcquire();
}
@Override
public IArea getArea() {
return area;
}
@Override
public IAreaType getType() {
return type;
}
@Override
protected void onDismiss() {
this.area.onDismiss();
ref.dismiss(this);
}
}
protected Map<IAreaType, List<IArea>> nonactiveAreas = new HashMap<>();
private MHFCWorldData saveData;
protected final World world;
protected Ticket loadingTicket;
public AreaManager(World world, MHFCWorldData saveData) {
this.world = Objects.requireNonNull(world);
this.saveData = Objects.requireNonNull(saveData);
Collection<AreaInformation> loadedAreas = this.saveData.getAllSpawnedAreas();
for (AreaInformation info : loadedAreas) {
IArea loadingArea = info.type.provideForLoading(world, info.config);
this.nonactiveAreas.computeIfAbsent(info.type, (k) -> new ArrayList<>()).add(loadingArea);
}
}
private Ticket getLoadingTicket() {
if (loadingTicket == null) {
loadingTicket = ForgeChunkManager.requestTicket(MHFCMain.instance(), world, Type.NORMAL);
}
return loadingTicket;
}
private void dismiss(IActiveArea active) {
this.nonactiveAreas.get(active.getType()).add(active.getArea());
}
@Override
public CompletionStage<IActiveArea> getUnusedInstance(IAreaType type) {
Optional<IArea> chosen = nonactiveAreas.computeIfAbsent(type, (k) -> new ArrayList<>()).stream()
.filter(a -> !a.isUnusable()).findFirst();
if (chosen.isPresent()) {
Active active = new Active(chosen.get(), type, this);
nonactiveAreas.get(type).remove(active.getArea());
return CompletableFuture.completedFuture(active);
}
AreaConfiguration config = newArea(type);
CornerPosition position = config.getPosition();
final Operation plan = type.populate(world, config);
final ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(position.posX, position.posY);
ForgeChunkManager.forceChunk(getLoadingTicket(), chunkPos);
final CompletableFuture<IActiveArea> areaFuture = new CompletableFuture<>();
final Operation operation = Operations.withCallback(Operations.timingOperation(plan, 20), o -> {
areaFuture.complete(new Active(type.provideForLoading(world, config), type, this));
ForgeChunkManager.unforceChunk(getLoadingTicket(), chunkPos);
MHFCMain.logger().debug("Area of type {} completed", type);
}, o -> {
areaFuture.cancel(true);
ForgeChunkManager.unforceChunk(getLoadingTicket(), chunkPos);
MHFCMain.logger().debug("Area of type {} cancelled", type);
});
areaFuture.whenComplete((a, ex) -> {
if (ex != null) {
operation.cancel();
}
});
MHFCTickHandler.instance.registerOperation(TickPhase.SERVER_PRE, operation);
return areaFuture;
}
private AreaConfiguration newArea(IAreaType type) {
return saveData.newArea(type, type.configForNewArea());
}
}