package crazypants.enderio.conduit.liquid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;
import crazypants.enderio.Log;
import crazypants.enderio.conduit.AbstractConduitNetwork;
import crazypants.enderio.conduit.ConnectionMode;
import crazypants.enderio.config.Config;
import crazypants.util.BlockCoord;
import crazypants.util.FluidUtil;
import crazypants.util.RoundRobinIterator;
public class EnderLiquidConduitNetwork extends AbstractConduitNetwork<ILiquidConduit, EnderLiquidConduit> {
public static final int MAX_EXTRACT_PER_TICK = Config.enderFluidConduitExtractRate;
public static final int MAX_IO_PER_TICK = Config.enderFluidConduitMaxIoRate;
List<NetworkTank> tanks = new ArrayList<NetworkTank>();
Map<NetworkTankKey, NetworkTank> tankMap = new HashMap<NetworkTankKey, NetworkTank>();
Map<NetworkTank, RoundRobinIterator<NetworkTank>> iterators;
public EnderLiquidConduitNetwork() {
super(EnderLiquidConduit.class);
}
@Override
public Class<ILiquidConduit> getBaseConduitType() {
return ILiquidConduit.class;
}
public void connectionChanged(EnderLiquidConduit con, ForgeDirection conDir) {
NetworkTankKey key = new NetworkTankKey(con, conDir);
NetworkTank tank = new NetworkTank(con, conDir);
tanks.remove(tank); // remove old tank, NB: =/hash is only calced on location and dir
tankMap.remove(key);
tanks.add(tank);
tankMap.put(key, tank);
}
public boolean extractFrom(EnderLiquidConduit con, ForgeDirection conDir) {
NetworkTank tank = getTank(con, conDir);
if(tank == null || !tank.isValid()) {
return false;
}
FluidStack drained = tank.externalTank.drain(conDir.getOpposite(), MAX_EXTRACT_PER_TICK, false);
if(drained == null || drained.amount <= 0 || !matchedFilter(drained, con, conDir, true)) {
return false;
}
int amountAccepted = fillFrom(tank, drained, true);
if(amountAccepted <= 0) {
return false;
}
drained = tank.externalTank.drain(conDir.getOpposite(), amountAccepted, true);
if(drained == null || drained.amount <= 0) {
return false;
}
if(drained.amount != amountAccepted) {
Log.warn("EnderLiquidConduit.extractFrom: Extracted fluid volume is not equal to inserted volume. Drained=" + drained.amount + " filled="
+ amountAccepted + " Fluid: " + drained + " Accepted=" + amountAccepted);
}
return true;
}
private NetworkTank getTank(EnderLiquidConduit con, ForgeDirection conDir) {
return tankMap.get(new NetworkTankKey(con, conDir));
}
public int fillFrom(EnderLiquidConduit con, ForgeDirection conDir, FluidStack resource, boolean doFill) {
return fillFrom(getTank(con, conDir), resource, doFill);
}
public int fillFrom(NetworkTank tank, FluidStack resource, boolean doFill) {
if(resource == null || tank == null || !matchedFilter(resource, tank.con, tank.conDir, true)) {
return 0;
}
resource = resource.copy();
resource.amount = Math.min(resource.amount, MAX_IO_PER_TICK);
int filled = 0;
int remaining = resource.amount;
//TODO: Only change starting pos of iterator is doFill is true so a false then true returns the same
for (NetworkTank target : getIteratorForTank(tank)) {
if(!target.equals(tank) && target.acceptsOuput && target.isValid() && matchedFilter(resource, target.con, target.conDir, false)) {
int vol = target.externalTank.fill(target.tankDir, resource.copy(), doFill);
remaining -= vol;
filled += vol;
if(remaining <= 0) {
return filled;
}
resource.amount = remaining;
}
}
return filled;
}
private boolean matchedFilter(FluidStack drained, EnderLiquidConduit con, ForgeDirection conDir, boolean isInput) {
if(drained == null || con == null || conDir == null) {
return false;
}
FluidFilter filter = con.getFilter(conDir, isInput);
if(filter == null || filter.isEmpty()) {
return true;
}
return filter.matchesFilter(drained);
}
private Iterable<NetworkTank> getIteratorForTank(NetworkTank tank) {
if(iterators == null) {
iterators = new HashMap<NetworkTank, RoundRobinIterator<NetworkTank>>();
}
RoundRobinIterator<NetworkTank> res = iterators.get(tank);
if(res == null) {
res = new RoundRobinIterator<NetworkTank>(tanks);
iterators.put(tank, res);
}
return res;
}
public FluidTankInfo[] getTankInfo(EnderLiquidConduit con, ForgeDirection conDir) {
List<FluidTankInfo> res = new ArrayList<FluidTankInfo>(tanks.size());
NetworkTank tank = getTank(con, conDir);
for (NetworkTank target : tanks) {
if(!target.equals(tank) && target.isValid()) {
FluidTankInfo[] tTanks = target.externalTank.getTankInfo(target.tankDir);
if(tTanks != null) {
for (FluidTankInfo info : tTanks) {
res.add(info);
}
}
}
}
return res.toArray(new FluidTankInfo[res.size()]);
}
static class NetworkTankKey {
ForgeDirection conDir;
BlockCoord conduitLoc;
public NetworkTankKey(EnderLiquidConduit con, ForgeDirection conDir) {
this(con.getLocation(), conDir);
}
public NetworkTankKey(BlockCoord conduitLoc, ForgeDirection conDir) {
this.conDir = conDir;
this.conduitLoc = conduitLoc;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((conDir == null) ? 0 : conDir.hashCode());
result = prime * result + ((conduitLoc == null) ? 0 : conduitLoc.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
NetworkTankKey other = (NetworkTankKey) obj;
if(conDir != other.conDir)
return false;
if(conduitLoc == null) {
if(other.conduitLoc != null)
return false;
} else if(!conduitLoc.equals(other.conduitLoc))
return false;
return true;
}
}
static class NetworkTank {
EnderLiquidConduit con;
ForgeDirection conDir;
IFluidHandler externalTank;
ForgeDirection tankDir;
BlockCoord conduitLoc;
boolean acceptsOuput;
public NetworkTank(EnderLiquidConduit con, ForgeDirection conDir) {
this.con = con;
this.conDir = conDir;
conduitLoc = con.getLocation();
tankDir = conDir.getOpposite();
externalTank = FluidUtil.getExternalFluidHandler(con.getBundle().getWorld(), conduitLoc.getLocation(conDir));
acceptsOuput = con.getConnectionMode(conDir).acceptsOutput();
}
public boolean isValid() {
return externalTank != null && con.getConnectionMode(conDir) != ConnectionMode.DISABLED;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((conDir == null) ? 0 : conDir.hashCode());
result = prime * result + ((conduitLoc == null) ? 0 : conduitLoc.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
NetworkTank other = (NetworkTank) obj;
if(conDir != other.conDir)
return false;
if(conduitLoc == null) {
if(other.conduitLoc != null)
return false;
} else if(!conduitLoc.equals(other.conduitLoc))
return false;
return true;
}
}
}