package pneumaticCraft.common.tileentity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.ChunkPosition;
import net.minecraftforge.common.util.ForgeDirection;
import pneumaticCraft.api.item.IProgrammable;
import pneumaticCraft.client.AreaShowManager;
import pneumaticCraft.common.NBTUtil;
import pneumaticCraft.common.block.Blockss;
import pneumaticCraft.common.item.ItemProgrammingPuzzle;
import pneumaticCraft.common.network.GuiSynced;
import pneumaticCraft.common.progwidgets.IAreaProvider;
import pneumaticCraft.common.progwidgets.IProgWidget;
import pneumaticCraft.common.progwidgets.IVariableWidget;
import pneumaticCraft.common.progwidgets.WidgetRegistrator;
import pneumaticCraft.common.util.IOHelper;
import pneumaticCraft.common.util.PneumaticCraftUtils;
public class TileEntityProgrammer extends TileEntityBase implements IInventory, IGUITextFieldSensitive{
public final List<IProgWidget> progWidgets = new ArrayList<IProgWidget>();
@GuiSynced
public int redstoneMode;
private ItemStack[] inventory = new ItemStack[1];
public static final int PROGRAM_SLOT = 0;
//Client side variables that are used to prevent resetting.
public int translatedX, translatedY, zoomState;
public boolean showInfo = true, showFlow = true;
@GuiSynced
public boolean canUndo, canRedo;
private NBTTagList history = new NBTTagList();//Used to undo/redo.
private int historyIndex;
public TileEntityProgrammer(){
saveToHistory();
}
@Override
public void readFromNBT(NBTTagCompound tag){
super.readFromNBT(tag);
redstoneMode = tag.getInteger("redstoneMode");
// Read in the ItemStacks in the inventory from NBT
NBTTagList tagList = tag.getTagList("Items", 10);
inventory = new ItemStack[getSizeInventory()];
for(int i = 0; i < tagList.tagCount(); ++i) {
NBTTagCompound tagCompound = tagList.getCompoundTagAt(i);
byte slot = tagCompound.getByte("Slot");
if(slot >= 0 && slot < inventory.length) {
inventory[slot] = ItemStack.loadItemStackFromNBT(tagCompound);
}
}
history = tag.getTagList("history", 10);
if(history.tagCount() == 0) saveToHistory();
}
@Override
public void readFromPacket(NBTTagCompound tag){
super.readFromPacket(tag);
readProgWidgetsFromNBT(tag);
}
@Override
public void writeToNBT(NBTTagCompound tag){
super.writeToNBT(tag);
tag.setInteger("redstoneMode", redstoneMode);
// Write the ItemStacks in the inventory to NBT
NBTTagList tagList = new NBTTagList();
for(int currentIndex = 0; currentIndex < inventory.length; ++currentIndex) {
if(inventory[currentIndex] != null) {
NBTTagCompound tagCompound = new NBTTagCompound();
tagCompound.setByte("Slot", (byte)currentIndex);
inventory[currentIndex].writeToNBT(tagCompound);
tagList.appendTag(tagCompound);
}
}
tag.setTag("Items", tagList);
tag.setTag("history", history);
}
@Override
public void writeToPacket(NBTTagCompound tag){
super.writeToPacket(tag);
writeProgWidgetsToNBT(tag);
}
public void readProgWidgetsFromNBT(NBTTagCompound tag){
progWidgets.clear();
getWidgetsFromNBT(tag, progWidgets);
}
public void writeProgWidgetsToNBT(NBTTagCompound tag){
setWidgetsToNBT(progWidgets, tag);
}
public static List<IProgWidget> getWidgetsFromNBT(NBTTagCompound tag){
List<IProgWidget> progWidgets = new ArrayList<IProgWidget>();
getWidgetsFromNBT(tag, progWidgets);
return progWidgets;
}
public static void getWidgetsFromNBT(NBTTagCompound tag, List<IProgWidget> progWidgets){
NBTTagList widgetTags = tag.getTagList("widgets", 10);
for(int i = 0; i < widgetTags.tagCount(); i++) {
NBTTagCompound widgetTag = widgetTags.getCompoundTagAt(i);
String widgetName = widgetTag.getString("name");
for(IProgWidget widget : WidgetRegistrator.registeredWidgets) {
if(widgetName.equals(widget.getWidgetString())) {//create the right progWidget for the given id tag.
IProgWidget addedWidget = widget.copy();
addedWidget.readFromNBT(widgetTag);
progWidgets.add(addedWidget);
break;
}
}
}
updatePuzzleConnections(progWidgets);
}
public static void setWidgetsToNBT(List<IProgWidget> widgets, NBTTagCompound tag){
NBTTagList widgetTags = new NBTTagList();
for(IProgWidget widget : widgets) {
NBTTagCompound widgetTag = new NBTTagCompound();
widget.writeToNBT(widgetTag);
widgetTags.appendTag(widgetTag);
}
tag.setTag("widgets", widgetTags);
}
public static void updatePuzzleConnections(List<IProgWidget> progWidgets){
for(IProgWidget widget : progWidgets) {
widget.setParent(null);
Class<? extends IProgWidget>[] parameters = widget.getParameters();
if(parameters != null) {
for(int i = 0; i < parameters.length * 2; i++) {
widget.setParameter(i, null);
}
}
if(widget.hasStepOutput()) widget.setOutputWidget(null);
}
for(IProgWidget checkedWidget : progWidgets) {
//check for connection to the right of the checked widget.
Class<? extends IProgWidget>[] parameters = checkedWidget.getParameters();
if(parameters != null) {
for(IProgWidget widget : progWidgets) {
if(widget != checkedWidget && checkedWidget.getX() + checkedWidget.getWidth() / 2 == widget.getX()) {
for(int i = 0; i < parameters.length; i++) {
if(checkedWidget.canSetParameter(i) && parameters[i] == widget.returnType() && checkedWidget.getY() + i * 11 == widget.getY()) {
checkedWidget.setParameter(i, widget);
widget.setParent(checkedWidget);
}
}
}
}
}
//check for connection to the bottom of the checked widget.
if(checkedWidget.hasStepOutput()) {
for(IProgWidget widget : progWidgets) {
if(widget.hasStepInput() && widget.getX() == checkedWidget.getX() && widget.getY() == checkedWidget.getY() + checkedWidget.getHeight() / 2) {
checkedWidget.setOutputWidget(widget);
}
}
}
}
//go again for the blacklist (as those are mirrored)
for(IProgWidget checkedWidget : progWidgets) {
if(checkedWidget.returnType() == null) { //if it's a program (import/export inventory, attack entity) rather than a parameter (area, item filter).
Class<? extends IProgWidget>[] parameters = checkedWidget.getParameters();
if(parameters != null) {
for(int i = 0; i < parameters.length; i++) {
if(checkedWidget.canSetParameter(i)) {
for(IProgWidget widget : progWidgets) {
if(parameters[i] == widget.returnType()) {
if(widget != checkedWidget && widget.getX() + widget.getWidth() / 2 == checkedWidget.getX() && widget.getY() == checkedWidget.getY() + i * 11) {
IProgWidget root = widget;
while(root.getParent() != null) {
root = root.getParent();
}
checkedWidget.setParameter(i + parameters.length, root);
}
}
}
}
}
}
}
}
}
@Override
public void handleGUIButtonPress(int buttonID, EntityPlayer player){
switch(buttonID){
case 0:
if(++redstoneMode > 1) redstoneMode = 0;
break;
case 1:
NBTTagCompound tag = inventory[PROGRAM_SLOT] != null ? inventory[PROGRAM_SLOT].getTagCompound() : null;
if(tag != null) readProgWidgetsFromNBT(tag);
else progWidgets.clear();
break;
case 2:
tryProgramDrone(player);
break;
case 9:
undo();
break;
case 10:
redo();
break;
}
sendDescriptionPacket();
}
@Override
public void setText(int textFieldID, String text){
if(textFieldID == 0 && inventory[PROGRAM_SLOT] != null) {
inventory[PROGRAM_SLOT].setStackDisplayName(text);
}
}
@Override
public String getText(int textFieldID){
return inventory[PROGRAM_SLOT] != null ? inventory[PROGRAM_SLOT].getDisplayName() : "";
}
private void tryProgramDrone(EntityPlayer player){
if(inventory[PROGRAM_SLOT] != null) {
if(player == null || !player.capabilities.isCreativeMode) {
List<ItemStack> requiredStacks = getRequiredPuzzleStacks();
for(ItemStack stack : requiredStacks) {
if(!hasEnoughPuzzleStacks(player, stack)) return;
}
for(ItemStack stack : requiredStacks) {
int left = stack.stackSize;
if(player != null) {
for(int i = 0; i < player.inventory.getSizeInventory(); i++) {
if(PneumaticCraftUtils.areStacksEqual(stack, player.inventory.getStackInSlot(i), true, true, false, false)) {
left -= player.inventory.decrStackSize(i, left).stackSize;
if(left <= 0) break;
}
}
}
if(left > 0) {
for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
IInventory neighbor = IOHelper.getInventoryForTE(getWorldObj().getTileEntity(xCoord + d.offsetX, yCoord + d.offsetY, zCoord + d.offsetZ));
for(int slot : IOHelper.getAccessibleSlotsForInventory(neighbor, d.getOpposite())) {
if(IOHelper.canExtractItemFromInventory(neighbor, stack, slot, d.getOpposite().ordinal())) {
ItemStack neighborStack = neighbor.getStackInSlot(slot);
if(PneumaticCraftUtils.areStacksEqual(neighborStack, stack, true, true, false, false)) {
left -= neighbor.decrStackSize(slot, left).stackSize;
if(left <= 0) break;
}
}
}
}
}
}
List<ItemStack> returnedStacks = getReturnedPuzzleStacks();
for(ItemStack stack : returnedStacks) {
for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
IInventory neighbor = IOHelper.getInventoryForTE(getWorldObj().getTileEntity(xCoord + d.offsetX, yCoord + d.offsetY, zCoord + d.offsetZ));
stack = IOHelper.insert(neighbor, stack, d.getOpposite().ordinal(), false);
if(stack == null) break;
}
if(player != null && stack != null) {
if(!player.inventory.addItemStackToInventory(stack)) {
player.dropPlayerItemWithRandomChoice(stack.copy(), false);
stack = null;
}
}
if(stack != null) {
worldObj.spawnEntityInWorld(new EntityItem(worldObj, xCoord + 0.5, yCoord + 1.5, zCoord + 0.5, stack));
}
}
}
NBTTagCompound tag = inventory[PROGRAM_SLOT].getTagCompound();
if(tag == null) {
tag = new NBTTagCompound();
inventory[PROGRAM_SLOT].setTagCompound(tag);
}
writeProgWidgetsToNBT(tag);
}
}
public List<ItemStack> getRequiredPuzzleStacks(){
List<ItemStack> stacks = new ArrayList<ItemStack>();
if(((IProgrammable)inventory[PROGRAM_SLOT].getItem()).usesPieces(inventory[PROGRAM_SLOT])) {
Map<Integer, Integer> tePieces = getPuzzleSummary(progWidgets);
Map<Integer, Integer> dronePieces = getPuzzleSummary(getProgWidgets(inventory[PROGRAM_SLOT]));
for(Integer includedWidget : tePieces.keySet()) {
Integer existingWidgets = dronePieces.get(includedWidget);
if(existingWidgets != null) {
Integer neededWidgets = tePieces.get(includedWidget);
if(neededWidgets > existingWidgets) {
ItemStack stack = ItemProgrammingPuzzle.getStackForColor(includedWidget);
stack.stackSize = (neededWidgets - existingWidgets) * inventory[PROGRAM_SLOT].stackSize;
stacks.add(stack);
}
} else {
ItemStack stack = ItemProgrammingPuzzle.getStackForColor(includedWidget);
stack.stackSize = tePieces.get(includedWidget) * inventory[PROGRAM_SLOT].stackSize;
stacks.add(stack);
}
}
}
return stacks;
}
public List<ItemStack> getReturnedPuzzleStacks(){
List<ItemStack> stacks = new ArrayList<ItemStack>();
if(((IProgrammable)inventory[PROGRAM_SLOT].getItem()).usesPieces(inventory[PROGRAM_SLOT])) {
Map<Integer, Integer> tePieces = getPuzzleSummary(progWidgets);
Map<Integer, Integer> dronePieces = getPuzzleSummary(getProgWidgets(inventory[PROGRAM_SLOT]));
for(Integer availableWidget : dronePieces.keySet()) {
Integer requiredWidget = tePieces.get(availableWidget);
if(requiredWidget != null) {
Integer availableWidgets = dronePieces.get(availableWidget);
if(availableWidgets > requiredWidget) {
ItemStack stack = ItemProgrammingPuzzle.getStackForColor(availableWidget);
stack.stackSize = (availableWidgets - requiredWidget) * inventory[PROGRAM_SLOT].stackSize;
while(stack.stackSize > stack.getMaxStackSize()) {
stacks.add(stack.splitStack(stack.getMaxStackSize()));
}
stacks.add(stack);
}
} else {
ItemStack stack = ItemProgrammingPuzzle.getStackForColor(availableWidget);
stack.stackSize = dronePieces.get(availableWidget) * inventory[PROGRAM_SLOT].stackSize;
while(stack.stackSize > stack.getMaxStackSize()) {
stacks.add(stack.splitStack(stack.getMaxStackSize()));
}
stacks.add(stack);
}
}
}
return stacks;
}
public static List<IProgWidget> getProgWidgets(ItemStack iStack){
if(NBTUtil.hasTag(iStack, "widgets")) {
return TileEntityProgrammer.getWidgetsFromNBT(iStack.getTagCompound());
} else {
return new ArrayList<IProgWidget>();
}
}
public boolean hasEnoughPuzzleStacks(EntityPlayer player, ItemStack stack){
int amountLeft = stack.stackSize;
if(player != null) {
for(int i = 0; i < player.inventory.getSizeInventory(); i++) {
ItemStack playerStack = player.inventory.getStackInSlot(i);
if(PneumaticCraftUtils.areStacksEqual(playerStack, stack, true, true, false, false)) {
amountLeft -= playerStack.stackSize;
if(amountLeft <= 0) return true;
}
}
}
for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
IInventory neighbor = IOHelper.getInventoryForTE(getWorldObj().getTileEntity(xCoord + d.offsetX, yCoord + d.offsetY, zCoord + d.offsetZ));
for(int slot : IOHelper.getAccessibleSlotsForInventory(neighbor, d.getOpposite())) {
if(IOHelper.canExtractItemFromInventory(neighbor, stack, slot, d.getOpposite().ordinal())) {
ItemStack neighborStack = neighbor.getStackInSlot(slot);
if(PneumaticCraftUtils.areStacksEqual(neighborStack, stack, true, true, false, false)) {
amountLeft -= neighborStack.stackSize;
if(amountLeft <= 0) return true;
}
}
}
}
return false;
}
public static Map<Integer, Integer> getPuzzleSummary(List<IProgWidget> widgets){
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(IProgWidget widget : widgets) {
if(widget.getCraftingColorIndex() != -1) {
if(!map.containsKey(widget.getCraftingColorIndex())) {
map.put(widget.getCraftingColorIndex(), 1);
} else {
map.put(widget.getCraftingColorIndex(), map.get(widget.getCraftingColorIndex()) + 1);
}
}
}
return map;
}
/**
* Returns a set with all variables that are used in the program.
* @return
*/
public Set<String> getAllVariables(){
Set<String> variables = new HashSet<String>();
for(IProgWidget widget : progWidgets) {
if(widget instanceof IVariableWidget) ((IVariableWidget)widget).addVariables(variables);
}
variables.remove("");
return variables;
}
// INVENTORY METHODS
/**
* Returns the number of slots in the inventory.
*/
@Override
public int getSizeInventory(){
return inventory.length;
}
/**
* Returns the stack in slot i
*/
@Override
public ItemStack getStackInSlot(int slot){
return inventory[slot];
}
@Override
public ItemStack decrStackSize(int slot, int amount){
ItemStack itemStack = getStackInSlot(slot);
if(itemStack != null) {
if(itemStack.stackSize <= amount) {
setInventorySlotContents(slot, null);
} else {
itemStack = itemStack.splitStack(amount);
if(itemStack.stackSize == 0) {
setInventorySlotContents(slot, null);
}
}
}
return itemStack;
}
@Override
public ItemStack getStackInSlotOnClosing(int slot){
ItemStack itemStack = getStackInSlot(slot);
if(itemStack != null) {
setInventorySlotContents(slot, null);
}
return itemStack;
}
@Override
public void setInventorySlotContents(int slot, ItemStack itemStack){
inventory[slot] = itemStack;
if(itemStack != null && itemStack.stackSize > getInventoryStackLimit()) {
itemStack.stackSize = getInventoryStackLimit();
}
if(redstoneMode == 1 && slot == 0 && itemStack != null) {
tryProgramDrone(null);
}
}
@Override
public String getInventoryName(){
return Blockss.programmer.getUnlocalizedName();
}
@Override
public int getInventoryStackLimit(){
return 64;
}
@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack){
if(i == PROGRAM_SLOT && itemstack != null && (!(itemstack.getItem() instanceof IProgrammable) || !((IProgrammable)itemstack.getItem()).canProgram(itemstack))) return false;
return true;
}
@Override
public void updateEntity(){
super.updateEntity();
}
public boolean previewArea(int widgetX, int widgetY){
for(IProgWidget w : progWidgets) {
if(w.getX() == widgetX && w.getY() == widgetY && w instanceof IAreaProvider) {
Set<ChunkPosition> area = new HashSet<ChunkPosition>();
((IAreaProvider)w).getArea(area);
AreaShowManager.getInstance().showArea(area, 0x00FF00, this);
}
}
return true;
}
@Override
public boolean hasCustomInventoryName(){
return false;
}
@Override
public boolean isUseableByPlayer(EntityPlayer var1){
return isGuiUseableByPlayer(var1);
}
@Override
public void openInventory(){}
@Override
public void closeInventory(){}
public void saveToHistory(){
NBTTagCompound tag = new NBTTagCompound();
writeProgWidgetsToNBT(tag);
if(history.tagCount() == 0 || !history.getCompoundTagAt(historyIndex).equals(tag)) {
while(history.tagCount() > historyIndex + 1) {
history.removeTag(historyIndex + 1);
}
history.appendTag(tag);
if(history.tagCount() > 20) history.removeTag(0);//Only save up to 20 steps back.
historyIndex = history.tagCount() - 1;
updateUndoRedoState();
}
}
public void undo(){
if(canUndo) {
historyIndex--;
readProgWidgetsFromNBT(history.getCompoundTagAt(historyIndex));
updateUndoRedoState();
}
}
public void redo(){
if(canRedo) {
historyIndex++;
readProgWidgetsFromNBT(history.getCompoundTagAt(historyIndex));
updateUndoRedoState();
}
}
private void updateUndoRedoState(){
canUndo = historyIndex > 0;
canRedo = historyIndex < history.tagCount() - 1;
}
}