package crazypants.enderio.conduit.item;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import com.enderio.core.common.util.BlockCoord;
import com.enderio.core.common.util.InventoryWrapper;
import com.enderio.core.common.util.ItemUtil;
import com.enderio.core.common.util.RoundRobinIterator;
import crazypants.enderio.conduit.ConnectionMode;
import crazypants.enderio.conduit.item.filter.IItemFilter;
import crazypants.enderio.config.Config;
import crazypants.enderio.machine.invpanel.TileInventoryPanel;
public class NetworkedInventory {
private ISidedInventory inv;
IItemConduit con;
ForgeDirection conDir;
BlockCoord location;
int inventorySide;
List<Target> sendPriority = new ArrayList<Target>();
RoundRobinIterator<Target> rrIter = new RoundRobinIterator<Target>(sendPriority);
private int extractFromSlot = -1;
int tickDeficit;
//work around for a vanilla chest changing into a double chest without doing unneeded checks all the time
boolean recheckInv = false;
//Hack for TiC crafting station not working correctly when setting output slot to null
boolean ticHack = false;
boolean inventoryPanel = false;
World world;
ItemConduitNetwork network;
NetworkedInventory(ItemConduitNetwork network, IInventory inv, IItemConduit con, ForgeDirection conDir, BlockCoord location) {
this.network = network;
inventorySide = conDir.getOpposite().ordinal();
this.con = con;
this.conDir = conDir;
this.location = location;
world = con.getBundle().getWorld();
TileEntity te = world.getTileEntity(location.x, location.y, location.z);
if(te.getClass().getName().equals("tconstruct.tools.logic.CraftingStationLogic")) {
ticHack = true;
} else if(te.getClass().getName().contains("cpw.mods.ironchest")) {
recheckInv = true;
} else if(te instanceof TileEntityChest) {
recheckInv = true;
} else if(te instanceof TileInventoryPanel) {
inventoryPanel = true;
}
updateInventory();
}
public boolean hasTarget(IItemConduit conduit, ForgeDirection dir) {
for (Target t : sendPriority) {
if(t.inv.con == conduit && t.inv.conDir == dir) {
return true;
}
}
return false;
}
boolean canExtract() {
ConnectionMode mode = con.getConnectionMode(conDir);
return mode == ConnectionMode.INPUT || mode == ConnectionMode.IN_OUT;
}
boolean canInsert() {
if(inventoryPanel) {
return false;
}
ConnectionMode mode = con.getConnectionMode(conDir);
return mode == ConnectionMode.OUTPUT || mode == ConnectionMode.IN_OUT;
}
boolean isInventoryPanel() {
return inventoryPanel;
}
boolean isSticky() {
return con.getOutputFilter(conDir) != null && con.getOutputFilter(conDir).isValid() && con.getOutputFilter(conDir).isSticky();
}
int getPriority() {
return con.getOutputPriority(conDir);
}
public void onTick() {
if(tickDeficit > 0 || !canExtract() || !con.isExtractionRedstoneConditionMet(conDir)) {
//do nothing
} else {
transferItems();
}
tickDeficit--;
if(tickDeficit < -1) {
//Sleep for a second before checking again.
tickDeficit = 20;
}
}
private boolean canExtractThisTick(long tick) {
if(!con.isExtractionRedstoneConditionMet(conDir)) {
return false;
}
return true;
}
private int nextSlot(int numSlots) {
++extractFromSlot;
if(extractFromSlot >= numSlots || extractFromSlot < 0) {
extractFromSlot = 0;
}
return extractFromSlot;
}
private void setNextStartingSlot(int slot) {
extractFromSlot = slot;
extractFromSlot--;
}
private boolean transferItems() {
if(recheckInv) {
updateInventory();
}
int[] slotIndices = getInventory().getAccessibleSlotsFromSide(inventorySide);
if(slotIndices == null) {
return false;
}
int numSlots = slotIndices.length;
ItemStack extractItem = null;
int maxExtracted = con.getMaximumExtracted(conDir);
int slot = -1;
int slotChecksPerTick = Math.min(numSlots, ItemConduitNetwork.MAX_SLOT_CHECK_PER_TICK);
for (int i = 0; i < slotChecksPerTick; i++) {
int index = nextSlot(numSlots);
slot = slotIndices[index];
ItemStack item = getInventory().getStackInSlot(slot);
if(canExtractItem(item)) {
extractItem = item.copy();
if(getInventory().canExtractItem(slot, extractItem, inventorySide)) {
if(doTransfer(extractItem, slot, maxExtracted)) {
setNextStartingSlot(slot);
return true;
}
}
}
}
return false;
}
private boolean canExtractItem(ItemStack itemStack) {
if(itemStack == null) {
return false;
}
IItemFilter filter = con.getInputFilter(conDir);
if(filter == null) {
return true;
}
return filter.doesItemPassFilter(this, itemStack);
}
private boolean doTransfer(ItemStack extractedItem, int slot, int maxExtract) {
if(extractedItem == null || extractedItem.getItem() == null) {
return false;
}
ItemStack toExtract = extractedItem.copy();
toExtract.stackSize = Math.min(maxExtract, toExtract.stackSize);
int numInserted = insertIntoTargets(toExtract);
if(numInserted <= 0) {
return false;
}
itemExtracted(slot, numInserted);
return true;
}
public void itemExtracted(int slot, int numInserted) {
ItemStack curStack = getInventory().getStackInSlot(slot);
if(curStack != null) {
if(ticHack) {
getInventory().decrStackSize(slot, numInserted);
getInventory().markDirty();
} else {
curStack = curStack.copy();
curStack.stackSize -= numInserted;
if(curStack.stackSize > 0) {
getInventory().setInventorySlotContents(slot, curStack);
getInventory().markDirty();
} else {
getInventory().setInventorySlotContents(slot, null);
getInventory().markDirty();
}
}
}
con.itemsExtracted(numInserted, slot);
tickDeficit = Math.round(numInserted * con.getTickTimePerItem(conDir));
}
int insertIntoTargets(ItemStack toExtract) {
if(toExtract == null) {
return 0;
}
int totalToInsert = toExtract.stackSize;
int leftToInsert = totalToInsert;
boolean matchedStickyInput = false;
Iterable<Target> targets = getTargetIterator();
//for (Target target : sendPriority) {
for (Target target : targets) {
if(target.stickyInput && !matchedStickyInput) {
IItemFilter of = target.inv.con.getOutputFilter(target.inv.conDir);
matchedStickyInput = of != null && of.isValid() && of.doesItemPassFilter(this, toExtract);
}
if(target.stickyInput || !matchedStickyInput) {
if(target.inv.recheckInv) {
target.inv.updateInventory();
}
int inserted = target.inv.insertItem(toExtract);
if(inserted > 0) {
toExtract.stackSize -= inserted;
leftToInsert -= inserted;
}
if(leftToInsert <= 0) {
return totalToInsert;
}
}
}
return totalToInsert - leftToInsert;
}
private Iterable<Target> getTargetIterator() {
if(con.isRoundRobinEnabled(conDir)) {
return rrIter;
}
return sendPriority;
}
public final void updateInventory() {
TileEntity te = world.getTileEntity(location.x, location.y, location.z);
if(te instanceof ISidedInventory) {
inv = (ISidedInventory) te;
} else if(te instanceof IInventory) {
inv = new InventoryWrapper((IInventory) te);
}
}
private int insertItem(ItemStack item) {
if(!canInsert() || item == null) {
return 0;
}
IItemFilter filter = con.getOutputFilter(conDir);
if(filter != null) {
if(!filter.doesItemPassFilter(this, item)) {
return 0;
}
}
return ItemUtil.doInsertItem(getInventory(), item, ForgeDirection.values()[inventorySide]);
}
void updateInsertOrder() {
sendPriority.clear();
if(!canExtract()) {
return;
}
List<Target> result = new ArrayList<NetworkedInventory.Target>();
for (NetworkedInventory other : network.inventories) {
if((con.isSelfFeedEnabled(conDir) || (other != this))
&& other.canInsert()
&& con.getInputColor(conDir) == other.con.getOutputColor(other.conDir)) {
if(Config.itemConduitUsePhyscialDistance) {
sendPriority.add(new Target(other, distanceTo(other), other.isSticky(), other.getPriority()));
} else {
result.add(new Target(other, 9999999, other.isSticky(), other.getPriority()));
}
}
}
if(Config.itemConduitUsePhyscialDistance) {
Collections.sort(sendPriority);
} else {
if(!result.isEmpty()) {
Map<BlockCoord, Integer> visited = new HashMap<BlockCoord, Integer>();
List<BlockCoord> steps = new ArrayList<BlockCoord>();
steps.add(con.getLocation());
calculateDistances(result, visited, steps, 0);
sendPriority.addAll(result);
Collections.sort(sendPriority);
}
}
}
private void calculateDistances(List<Target> targets, Map<BlockCoord, Integer> visited, List<BlockCoord> steps, int distance) {
if(steps == null || steps.isEmpty()) {
return;
}
ArrayList<BlockCoord> nextSteps = new ArrayList<BlockCoord>();
for (BlockCoord bc : steps) {
IItemConduit con = network.conMap.get(bc);
if(con != null) {
for (ForgeDirection dir : con.getExternalConnections()) {
Target target = getTarget(targets, con, dir);
if(target != null && target.distance > distance) {
target.distance = distance;
}
}
if(!visited.containsKey(bc)) {
visited.put(bc, distance);
} else {
int prevDist = visited.get(bc);
if(prevDist <= distance) {
continue;
}
visited.put(bc, distance);
}
for (ForgeDirection dir : con.getConduitConnections()) {
nextSteps.add(bc.getLocation(dir));
}
}
}
calculateDistances(targets, visited, nextSteps, distance + 1);
}
private Target getTarget(List<Target> targets, IItemConduit con, ForgeDirection dir) {
if(targets == null || con == null || con.getLocation() == null) {
return null;
}
for (Target target : targets) {
BlockCoord targetConLoc = null;
if(target != null && target.inv != null && target.inv.con != null) {
targetConLoc = target.inv.con.getLocation();
}
if(targetConLoc != null && target.inv.conDir == dir && targetConLoc.equals(con.getLocation())) {
return target;
}
}
return null;
}
private int distanceTo(NetworkedInventory other) {
return con.getLocation().getDistSq(other.con.getLocation());
}
public ISidedInventory getInventory() {
return inv;
}
public ISidedInventory getInventoryRecheck() {
if(recheckInv) {
updateInventory();
}
return inv;
}
public int getInventorySide() {
return inventorySide;
}
public void setInventorySide(int inventorySide) {
this.inventorySide = inventorySide;
}
public String getLocalizedInventoryName() {
String inventoryName = getInventory().getInventoryName();
if(inventoryName == null) {
return "null";
} else {
// don't use Lang.localize as that passes the localized string to
// String.format which might crash when it contains formatting specifiers
return StatCollector.translateToLocal(inventoryName);
}
}
static class Target implements Comparable<Target> {
NetworkedInventory inv;
int distance;
boolean stickyInput;
int priority;
Target(NetworkedInventory inv, int distance, boolean stickyInput, int priority) {
this.inv = inv;
this.distance = distance;
this.stickyInput = stickyInput;
this.priority = priority;
}
@Override
public int compareTo(Target o) {
if(stickyInput && !o.stickyInput) {
return -1;
}
if(!stickyInput && o.stickyInput) {
return 1;
}
if(priority != o.priority) {
return ItemConduitNetwork.compare(o.priority, priority);
}
return ItemConduitNetwork.compare(distance, o.distance);
}
}
}