package crazypants.enderio.machine.capbank.network;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import cofh.api.energy.IEnergyContainerItem;
import com.enderio.core.common.util.BlockCoord;
import com.enderio.core.common.util.RoundRobinIterator;
import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent;
import crazypants.enderio.conduit.ConduitNetworkTickHandler;
import crazypants.enderio.conduit.ConduitNetworkTickHandler.TickListener;
import crazypants.enderio.conduit.ConnectionMode;
import crazypants.enderio.conduit.power.IPowerConduit;
import crazypants.enderio.machine.IoMode;
import crazypants.enderio.machine.RedstoneControlMode;
import crazypants.enderio.machine.capbank.CapBankType;
import crazypants.enderio.machine.capbank.TileCapBank;
import crazypants.enderio.machine.capbank.packet.PacketNetworkEnergyResponse;
import crazypants.enderio.machine.capbank.packet.PacketNetworkStateResponse;
import crazypants.enderio.network.PacketHandler;
import crazypants.enderio.power.IPowerInterface;
import crazypants.enderio.power.IPowerStorage;
import crazypants.enderio.power.PerTickIntAverageCalculator;
public class CapBankNetwork implements ICapBankNetwork {
private static final int IO_CAP = 2000000000;
private final List<TileCapBank> capBanks = new ArrayList<TileCapBank>();
private final Set<EnergyReceptor> receptors = new HashSet<EnergyReceptor>();
private RoundRobinIterator<EnergyReceptor> receptorIterator;
private final int id;
private int maxIO;
private int maxInput = -1;
private int maxOutput = -1;
private long timeAtLastApply;
private long energyStored;
private long prevEnergyStored = -1;
private long energyReceived;
private long energySend;
private long maxEnergyStored;
private CapBankType type;
private final Set<BlockCoord> redstoneRecievers = new HashSet<BlockCoord>();
private RedstoneControlMode inputControlMode = RedstoneControlMode.IGNORE;
private RedstoneControlMode outputControlMode = RedstoneControlMode.IGNORE;
private boolean inputRedstoneConditionMet = true;
private boolean outputRedstoneConditionMet = true;
private final TickListener tickListener = new TickReciever();
private final PerTickIntAverageCalculator powerTrackerIn = new PerTickIntAverageCalculator(2);
private final PerTickIntAverageCalculator powerTrackerOut = new PerTickIntAverageCalculator(2);
private final InventoryImpl inventory = new InventoryImpl();
private boolean firstUpate = true;
public CapBankNetwork(int id) {
this.id = id;
}
//--------- Network Management
public void init(TileCapBank cap, Collection<TileCapBank> neighbours, World world) {
if(world.isRemote) {
throw new UnsupportedOperationException();
}
type = cap.getType();
inputControlMode = cap.getInputControlMode();
outputControlMode = cap.getOutputControlMode();
for (TileCapBank con : neighbours) {
ICapBankNetwork network = con.getNetwork();
if(network != null) {
network.destroyNetwork();
}
}
setNetwork(world, cap);
}
protected void setNetwork(World world, TileCapBank cap) {
if(cap == null) {
return;
}
Set<TileCapBank> work = new HashSet<TileCapBank>();
for(;;) {
ICapBankNetwork network = cap.getNetwork();
if(network != this) {
if(network != null) {
network.destroyNetwork();
}
if(cap.setNetwork(this)) {
addMember(cap);
NetworkUtil.getNeigbours(cap, work);
}
}
if(work.isEmpty()) {
return;
}
Iterator<TileCapBank> iter = work.iterator();
cap = iter.next();
iter.remove();
}
}
@Override
public void destroyNetwork() {
distributeEnergyToBanks();
TileCapBank cap = null;
for (TileCapBank cb : capBanks) {
cb.setNetwork(null);
if(cap == null) {
cap = cb;
}
}
capBanks.clear();
if(cap != null) {
PacketHandler.INSTANCE.sendToAll(new PacketNetworkStateResponse(this, true));
}
}
@Override
public Collection<TileCapBank> getMembers() {
return capBanks;
}
@Override
public void addMember(TileCapBank cap) {
if(!capBanks.contains(cap)) {
capBanks.add(cap);
long newIO = maxIO + cap.getType().getMaxIO();
if(newIO > IO_CAP) {
newIO = IO_CAP;
}
maxIO = (int) newIO;
energyStored += cap.getEnergyStored();
maxEnergyStored += cap.getMaxEnergyStored();
if(maxInput == -1) {
maxInput = cap.getMaxInputOverride();
}
if(maxOutput == -1) {
maxOutput = cap.getMaxOutputOverride();
}
cap.setInputControlMode(inputControlMode);
cap.setOutputControlMode(outputControlMode);
List<EnergyReceptor> recs = cap.getReceptors();
if(!recs.isEmpty()) {
addReceptors(recs);
}
if(inventory.isEmtpy()) {
inventory.setCapBank(cap);
} else if(!InventoryImpl.isInventoryEmtpy(cap)) {
if(inventory.isEmtpy()) {
inventory.setCapBank(cap);
} else {
cap.dropItems();
}
}
}
}
@Override
public InventoryImpl getInventory() {
return inventory;
}
@Override
public int getId() {
return id;
}
@Override
public NetworkState getState() {
return new NetworkState(this);
}
//--------- Tick Handling
@Override
public void onUpdateEntity(TileCapBank tileCapBank) {
World world = tileCapBank.getWorldObj();
if(world == null) {
return;
}
if(world.isRemote) {
return;
}
long curTime = world.getTotalWorldTime();
if(curTime != timeAtLastApply) {
timeAtLastApply = curTime;
ConduitNetworkTickHandler.instance.addListener(tickListener);
}
}
private void doNetworkTick() {
chargeItems(inventory.getStacks());
transmitEnergy();
if(energyStored != prevEnergyStored) {
distributeEnergyToBanks();
}
powerTrackerIn.tick(energyReceived);
powerTrackerOut.tick(energySend);
prevEnergyStored = energyStored;
energyReceived = 0;
energySend = 0;
if(firstUpate) {
if(!capBanks.isEmpty()) {
PacketHandler.sendToAllAround(new PacketNetworkStateResponse(this), capBanks.get(0));
PacketHandler.sendToAllAround(new PacketNetworkEnergyResponse(this), capBanks.get(0));
}
firstUpate = false;
}
}
private void transmitEnergy() {
if(!outputRedstoneConditionMet) {
return;
}
if(receptors.isEmpty()) {
return;
}
int available = getEnergyAvailableForTick(getMaxOutput());
if(available <= 0) {
return;
}
if(receptorIterator == null) {
List<EnergyReceptor> rl = new ArrayList<EnergyReceptor>(receptors);
receptorIterator = new RoundRobinIterator<EnergyReceptor>(rl);
}
int totalSent = 0;
Iterator<EnergyReceptor> iter = receptorIterator.iterator();
while (available > 0 && iter.hasNext()) {
int sent = sendPowerTo(iter.next(), available);
totalSent += sent;
available -= sent;
}
addEnergy(-totalSent);
}
protected int getEnergyAvailableForTick(int limit) {
int available;
if(energyStored > limit) {
available = limit;
} else {
available = (int) energyStored;
}
return available;
}
private int sendPowerTo(EnergyReceptor next, int available) {
//Can only send to power conduits if we are in push mode or the conduit is in pull mode
//With default setting interaction between conduits and Cap Banks is handled by NetworkPowerManager
IPowerConduit con = next.getConduit();
if(con != null && next.getMode() == IoMode.NONE && con.getConnectionMode(next.getDir().getOpposite()) == ConnectionMode.IN_OUT) {
return 0;
}
IPowerInterface inf = next.getReceptor();
int result = inf.recieveEnergy(next.getDir().getOpposite(), available);
if(result < 0) {
result = 0;
}
return result;
}
public boolean chargeItems(ItemStack[] items) {
if(items == null) {
return false;
}
boolean chargedItem = false;
int available = getEnergyAvailableForTick(getMaxIO());
for (ItemStack item : items) {
if(item != null && available > 0 && item.stackSize == 1 && item.getItem() instanceof IEnergyContainerItem) {
IEnergyContainerItem chargable = (IEnergyContainerItem) item.getItem();
int max = chargable.getMaxEnergyStored(item);
int cur = chargable.getEnergyStored(item);
if(cur < max) {
int canUse = Math.min(available, max - cur);
int used = chargable.receiveEnergy(item, canUse, false);
if(used > 0) {
addEnergy(-used);
chargedItem = true;
available -= used;
}
}
}
}
return chargedItem;
}
private void distributeEnergyToBanks() {
if(capBanks.isEmpty()) {
return;
}
int energyPerCapBank = (int) (energyStored / capBanks.size());
int remaining = (int) (energyStored % capBanks.size());
for (TileCapBank cb : capBanks) {
cb.setEnergyStored(energyPerCapBank);
}
TileCapBank cb = capBanks.get(0);
cb.setEnergyStored(cb.getEnergyStored() + remaining);
}
//------ Power
@Override
public float getAverageChangePerTick() {
return powerTrackerIn.getAverage() - powerTrackerOut.getAverage();
}
@Override
public float getAverageInputPerTick() {
return powerTrackerIn.getAverage();
}
@Override
public float getAverageOutputPerTick() {
return powerTrackerOut.getAverage();
}
@Override
public int receiveEnergy(int maxReceive, boolean simulate) {
if(maxReceive <= 0 || !inputRedstoneConditionMet) {
return 0;
}
long spaceAvailable = maxEnergyStored - energyStored;
if(spaceAvailable > Integer.MAX_VALUE) {
spaceAvailable = Integer.MAX_VALUE;
}
int res = Math.min(maxReceive, (int) spaceAvailable);
res = Math.min(res, getMaxInput());
if(!simulate) {
addEnergy(res);
}
return res;
}
@Override
public void addEnergy(int energy) {
if(energy > 0) {
energyReceived += energy;
} else {
energySend -= energy;
}
if(!type.isCreative()) {
energyStored += energy;
if(energyStored > maxEnergyStored) {
energyStored = maxEnergyStored;
} else if(energyStored < 0) {
energyStored = 0;
}
}
}
public void addEnergyReceptor(EnergyReceptor rec) {
receptors.add(rec);
receptorIterator = null;
}
@Override
public void addReceptors(Collection<EnergyReceptor> rec) {
if(rec.isEmpty()) {
return;
}
receptors.addAll(rec);
receptorIterator = null;
}
@Override
public void removeReceptors(Collection<EnergyReceptor> rec) {
if(rec.isEmpty()) {
return;
}
receptors.removeAll(rec);
receptorIterator = null;
}
public void removeReceptor(EnergyReceptor rec) {
receptors.remove(rec);
receptorIterator = null;
}
@Override
public long getEnergyStoredL() {
return energyStored;
}
@Override
public long getMaxEnergyStoredL() {
return maxEnergyStored;
}
@Override
public int getMaxIO() {
return maxIO;
}
//----- IO overrides
@Override
public int getMaxInput() {
if(maxInput == -1) {
return maxIO;
}
return Math.min(maxInput, maxIO);
}
@Override
public int getMaxOutput() {
if(maxOutput == -1) {
return maxIO;
}
return Math.min(maxOutput, maxIO);
}
@Override
public void setMaxInput(int max) {
if(max >= maxIO) {
maxInput = -1;
} else if(max < 0) {
maxInput = 0;
} else {
maxInput = max;
}
for (TileCapBank cb : capBanks) {
cb.setMaxInput(maxInput);
}
}
@Override
public void setMaxOutput(int max) {
if(max >= maxIO) {
maxOutput = -1;
} else if(max < 0) {
maxOutput = 0;
} else {
maxOutput = max;
}
for (TileCapBank cb : capBanks) {
cb.setMaxOutput(maxOutput);
}
}
//----------- Redstone
@Override
public RedstoneControlMode getInputControlMode() {
return inputControlMode;
}
@Override
public void setInputControlMode(RedstoneControlMode inputControlMode) {
if(this.inputControlMode == inputControlMode) {
return;
}
this.inputControlMode = inputControlMode;
for (TileCapBank capBank : capBanks) {
capBank.setInputControlMode(inputControlMode);
}
updateRedstoneConditions();
}
@Override
public RedstoneControlMode getOutputControlMode() {
return outputControlMode;
}
@Override
public void setOutputControlMode(RedstoneControlMode outputControlMode) {
if(this.outputControlMode == outputControlMode) {
return;
}
this.outputControlMode = outputControlMode;
for (TileCapBank capBank : capBanks) {
capBank.setOutputControlMode(outputControlMode);
}
updateRedstoneConditions();
}
@Override
public void updateRedstoneSignal(TileCapBank tileCapBank, boolean recievingSignal) {
if(recievingSignal) {
redstoneRecievers.add(tileCapBank.getLocation());
} else {
redstoneRecievers.remove(tileCapBank.getLocation());
}
updateRedstoneConditions();
}
@Override
public boolean isInputEnabled() {
return inputRedstoneConditionMet;
}
@Override
public boolean isOutputEnabled() {
return outputRedstoneConditionMet;
}
private void updateRedstoneConditions() {
int powerLevel = redstoneRecievers.isEmpty() ? 0 : 15;
inputRedstoneConditionMet = RedstoneControlMode.isConditionMet(inputControlMode, powerLevel);
outputRedstoneConditionMet = RedstoneControlMode.isConditionMet(outputControlMode, powerLevel);
}
private class TickReciever implements TickListener {
@Override
public void tickStart(ServerTickEvent evt) {
}
@Override
public void tickEnd(ServerTickEvent evt) {
doNetworkTick();
}
}
@Override
public IPowerStorage getController() {
return this;
}
@Override
public boolean isOutputEnabled(ForgeDirection direction) {
return isOutputEnabled();
}
@Override
public boolean isInputEnabled(ForgeDirection direction) {
return isInputEnabled();
}
@Override
public boolean isCreative() {
return type.isCreative();
}
@Override
public boolean isNetworkControlledIo(ForgeDirection direction) {
//This is handled at the block level based on the IO mode
return true;
}
@Override
public void invalidateDisplayInfoCache() {
}
}