gt: 			final int targetMaxLimit = allowedExtent + farmWidth;
context: /*******************************************************************************
   * Copyright (c) 2011-2014 SirSengir.
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the GNU Lesser Public License v3
   * which accompanies this distribution, and is available at
   * http://www.gnu.org/licenses/lgpl-3.0.txt
   *
   * Various Contributors including, but not limited to:
   * SirSengir (original work), CovertJaguar, Player, Binnie, MysteriousAges
   ******************************************************************************/
  package forestry.farming.multiblock;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.EnumMap;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.Stack;
  
  import net.minecraft.block.Block;
  import net.minecraft.entity.player.EntityPlayer;
  import net.minecraft.item.ItemStack;
  import net.minecraft.nbt.NBTTagCompound;
  import net.minecraft.util.ChunkCoordinates;
  import net.minecraft.util.StatCollector;
  import net.minecraft.world.World;
  import net.minecraft.world.biome.BiomeGenBase;
  
  import net.minecraftforge.fluids.FluidRegistry;
  import net.minecraftforge.fluids.FluidStack;
  
  import forestry.api.circuits.ChipsetManager;
  import forestry.api.circuits.CircuitSocketType;
  import forestry.api.circuits.ICircuitBoard;
  import forestry.api.circuits.ICircuitSocketType;
  import forestry.api.core.EnumHumidity;
  import forestry.api.core.EnumTemperature;
  import forestry.api.core.IErrorLogic;
  import forestry.api.farming.FarmDirection;
  import forestry.api.farming.ICrop;
  import forestry.api.farming.IFarmComponent;
  import forestry.api.farming.IFarmInventory;
  import forestry.api.farming.IFarmListener;
  import forestry.api.farming.IFarmLogic;
  import forestry.api.farming.IFarmable;
  import forestry.core.access.EnumAccess;
  import forestry.core.config.Config;
  import forestry.core.config.Constants;
  import forestry.core.errors.EnumErrorCode;
  import forestry.core.fluids.Fluids;
  import forestry.core.fluids.TankManager;
  import forestry.core.fluids.tanks.FilteredTank;
  import forestry.core.fluids.tanks.StandardTank;
  import forestry.core.inventory.FakeInventoryAdapter;
  import forestry.core.inventory.IInventoryAdapter;
  import forestry.core.inventory.InventoryAdapter;
  import forestry.core.multiblock.CoordTriplet;
  import forestry.core.multiblock.IMultiblockPart;
  import forestry.core.multiblock.MultiblockControllerBase;
  import forestry.core.multiblock.MultiblockTileEntityBase;
  import forestry.core.multiblock.MultiblockValidationException;
  import forestry.core.multiblock.rectangular.RectangularMultiblockControllerBase;
  import forestry.core.network.DataInputStreamForestry;
  import forestry.core.network.DataOutputStreamForestry;
  import forestry.core.tiles.ILiquidTankTile;
  import forestry.core.utils.Log;
  import forestry.core.utils.PlayerUtil;
  import forestry.core.utils.vect.Vect;
  import forestry.core.utils.vect.VectUtil;
  import forestry.farming.FarmHelper;
  import forestry.farming.FarmTarget;
  import forestry.farming.gui.IFarmLedgerDelegate;
  import forestry.farming.logic.FarmLogicArboreal;
  import forestry.farming.tiles.TileGearbox;
  
  public class FarmController extends RectangularMultiblockControllerBase implements IFarmController, ILiquidTankTile {
  
  	private enum Stage {
  		CULTIVATE, HARVEST;
  
  		public Stage next() {
  			if (this == CULTIVATE) {
  				return HARVEST;
  			} else {
  				return CULTIVATE;
  			}
  		}
  	}
  
  	private static FarmDirection getLayoutDirection(FarmDirection farmSide) {
  		switch (farmSide) {
  			case NORTH:
  				return FarmDirection.WEST;
  			case WEST:
  				return FarmDirection.SOUTH;
  			case SOUTH:
  				return FarmDirection.EAST;
  			case EAST:
  				return FarmDirection.NORTH;
  		}
  		return null;
  	}
  
  	private final Map<FarmDirection, List<FarmTarget>> targets = new EnumMap<>(FarmDirection.class);
  	private int allowedExtent = 0;
  
  	private IFarmLogic harvestProvider; // The farm logic which supplied the pending crops.
  	private final Stack<ICrop> pendingCrops = new Stack<>();
  	private final Stack<ItemStack> pendingProduce = new Stack<>();
  
  	private Stage stage = Stage.CULTIVATE;
  
  	// active components are stored with a tick offset so they do not all tick together
  	private final Map<IFarmComponent.Active, Integer> farmActiveComponents = new HashMap<>();
  	private final Set<IFarmListener> farmListeners = new HashSet<>();
  
  	private final Map<FarmDirection, IFarmLogic> farmLogics = new EnumMap<>(FarmDirection.class);
  
  	private final InventoryAdapter sockets;
  	private final InventoryFarm inventory;
  	private final TankManager tankManager;
  	private final StandardTank resourceTank;
  	private final FarmHydrationManager hydrationManager;
  	private final FarmFertilizerManager fertilizerManager;
  
  	// the number of work ticks that this farm has had no power
  	private int noPowerTime = 0;
  
  	private BiomeGenBase cachedBiome;
  
  	public FarmController(World world) {
  		super(world);
  
  		this.resourceTank = new FilteredTank(Constants.PROCESSOR_TANK_CAPACITY, FluidRegistry.WATER);
  		this.tankManager = new TankManager(this, resourceTank);
  
  		this.inventory = new InventoryFarm(this);
  		this.sockets = new InventoryAdapter(1, "sockets");
  		this.hydrationManager = new FarmHydrationManager(this);
  		this.fertilizerManager = new FarmFertilizerManager();
  
  		refreshFarmLogics();
  	}
  
  	@Override
  	public IFarmLedgerDelegate getFarmLedgerDelegate() {
  		return hydrationManager;
  	}
  
  	@Override
  	public IInventoryAdapter getInternalInventory() {
  		if (isAssembled()) {
  			return inventory;
  		} else {
  			return FakeInventoryAdapter.instance();
  		}
  	}
  
  	@Override
  	public TankManager getTankManager() {
  		return tankManager;
  	}
  
  	private BiomeGenBase getBiome() {
  		if (cachedBiome == null) {
  			CoordTriplet coords = getReferenceCoord();
  			cachedBiome = worldObj.getBiomeGenForCoords(coords.x, coords.z);
  		}
  		return cachedBiome;
  	}
  
  	@Override
  	public void onAttachedPartWithMultiblockData(IMultiblockPart part, NBTTagCompound data) {
  		this.readFromNBT(data);
  	}
  
  	@Override
  	protected void onBlockAdded(IMultiblockPart newPart) {
  		if (newPart instanceof IFarmComponent.Listener) {
  			IFarmComponent.Listener listenerPart = (IFarmComponent.Listener) newPart;
  			farmListeners.add(listenerPart.getFarmListener());
  		}
  
  		if (newPart instanceof IFarmComponent.Active) {
  			farmActiveComponents.put((IFarmComponent.Active) newPart, worldObj.rand.nextInt(256));
  		}
  	}
  
  	@Override
  	protected void onBlockRemoved(IMultiblockPart oldPart) {
  		if (oldPart instanceof IFarmComponent.Listener) {
  			IFarmComponent.Listener listenerPart = (IFarmComponent.Listener) oldPart;
  			farmListeners.remove(listenerPart.getFarmListener());
  		}
  
  		if (oldPart instanceof IFarmComponent.Active) {
  			farmActiveComponents.remove(oldPart);
  		}
  	}
  
  	@Override
  	protected void isMachineWhole() throws MultiblockValidationException {
  		super.isMachineWhole();
  
  		boolean hasGearbox = false;
  		for (IMultiblockPart part : connectedParts) {
  			if (part instanceof TileGearbox) {
  				hasGearbox = true;
  				break;
  			}
  		}
  
  		if (!hasGearbox) {
  			throw new MultiblockValidationException(StatCollector.translateToLocal("for.multiblock.farm.error.needGearbox"));
  		}
  	}
  
  	@Override
  	protected void onMachineDisassembled() {
  		super.onMachineDisassembled();
  		targets.clear();
  	}
  
  	@Override
  	protected int getMinimumNumberOfBlocksForAssembledMachine() {
  		return 3 * 3 * 4;
  	}
  
  	@Override
  	protected int getMaximumXSize() {
  		return 5;
  	}
  
  	@Override
  	protected int getMaximumZSize() {
  		return 5;
  	}
  
  	@Override
  	protected int getMaximumYSize() {
  		return 4;
  	}
  
  	@Override
  	protected int getMinimumXSize() {
  		return 3;
  	}
  
  	@Override
  	protected int getMinimumZSize() {
  		return 3;
  	}
  
  	@Override
  	protected int getMinimumYSize() {
  		return 4;
  	}
  
  	@Override
  	protected void onAssimilate(MultiblockControllerBase assimilated) {
  
  	}
  
  	@Override
  	protected void onAssimilated(MultiblockControllerBase assimilator) {
  
  	}
  
  	@Override
  	protected boolean updateServer(int tickCount) {
  		hydrationManager.updateServer(worldObj, getBiome());
  
  		if (updateOnInterval(20)) {
  			inventory.drainCan(tankManager);
  		}
  
  		boolean hasPower = false;
  		for (Map.Entry<IFarmComponent.Active, Integer> entry : farmActiveComponents.entrySet()) {
  			IFarmComponent.Active farmComponent = entry.getKey();
  			if (farmComponent instanceof TileGearbox) {
  				hasPower |= ((TileGearbox) farmComponent).getEnergyManager().getTotalEnergyStored() > 0;
  			}
  
  			int tickOffset = entry.getValue();
  			farmComponent.updateServer(tickCount + tickOffset);
  		}
  
  		if (hasPower) {
  			noPowerTime = 0;
  			getErrorLogic().setCondition(false, EnumErrorCode.NOPOWER);
  		} else {
  			if (noPowerTime <= 4) {
  				noPowerTime++;
  			} else {
  				getErrorLogic().setCondition(true, EnumErrorCode.NOPOWER);
  			}
  		}
  
  		//FIXME: be smarter about the farm needing to save
  		return true;
  	}
  
  	@Override
  	protected void updateClient(int tickCount) {
  		for (Map.Entry<IFarmComponent.Active, Integer> entry : farmActiveComponents.entrySet()) {
  			IFarmComponent.Active farmComponent = entry.getKey();
  			int tickOffset = entry.getValue();
  			farmComponent.updateClient(tickCount + tickOffset);
  		}
  	}
  
  	@Override
  	public void writeToNBT(NBTTagCompound data) {
  		super.writeToNBT(data);
  		sockets.writeToNBT(data);
  		hydrationManager.writeToNBT(data);
  		tankManager.writeToNBT(data);
  		fertilizerManager.writeToNBT(data);
  		inventory.writeToNBT(data);
  	}
  
  	@Override
  	public void readFromNBT(NBTTagCompound data) {
  		super.readFromNBT(data);
  		sockets.readFromNBT(data);
  		hydrationManager.readFromNBT(data);
  		tankManager.readFromNBT(data);
  		fertilizerManager.readFromNBT(data);
  		inventory.readFromNBT(data);
  
  		refreshFarmLogics();
  	}
  
  	@Override
  	public void formatDescriptionPacket(NBTTagCompound data) {
  		sockets.writeToNBT(data);
  		hydrationManager.writeToNBT(data);
  		tankManager.writeToNBT(data);
  		fertilizerManager.writeToNBT(data);
  	}
  
  	@Override
  	public void decodeDescriptionPacket(NBTTagCompound data) {
  		sockets.readFromNBT(data);
  		hydrationManager.readFromNBT(data);
  		tankManager.readFromNBT(data);
  		fertilizerManager.readFromNBT(data);
  
  		refreshFarmLogics();
  	}
  
  	@Override
  	public ChunkCoordinates getCoordinates() {
  		CoordTriplet coord = getReferenceCoord();
  		return new ChunkCoordinates(coord.x, coord.y, coord.z);
  	}
  
  	@Override
  	public void onSwitchAccess(EnumAccess oldAccess, EnumAccess newAccess) {
  		if (oldAccess == EnumAccess.SHARED || newAccess == EnumAccess.SHARED) {
  			// pipes connected to this need to update
  			for (IMultiblockPart part : connectedParts) {
  				if (part instanceof MultiblockTileEntityBase) {
  					MultiblockTileEntityBase tile = (MultiblockTileEntityBase) part;
  					tile.notifyNeighborsOfBlockChange();
  				}
  			}
  			markDirty();
  		}
  	}
  
  	@Override
  	public void writeGuiData(DataOutputStreamForestry data) throws IOException {
  		tankManager.writeData(data);
  		hydrationManager.writeData(data);
  		fertilizerManager.writeData(data);
  	}
  
  	@Override
  	public void readGuiData(DataInputStreamForestry data) throws IOException {
  		tankManager.readData(data);
  		hydrationManager.readData(data);
  		fertilizerManager.readData(data);
  	}
  
  	private void refreshFarmLogics() {
  		for (FarmDirection direction : FarmDirection.values()) {
  			resetFarmLogic(direction);
  		}
  
  		// See whether we have socketed stuff.
  		ItemStack chip = sockets.getStackInSlot(0);
  		if (chip != null) {
  			ICircuitBoard chipset = ChipsetManager.circuitRegistry.getCircuitboard(chip);
  			if (chipset != null) {
  				chipset.onLoad(this);
  			}
  		}
  	}
  
  	@Override
  	public EnumTemperature getTemperature() {
  		CoordTriplet coords = getReferenceCoord();
  		return EnumTemperature.getFromBiome(getBiome(), coords.x, coords.y, coords.z);
  	}
  
  	@Override
  	public EnumHumidity getHumidity() {
  		return EnumHumidity.getFromValue(getExactHumidity());
  	}
  
  	@Override
  	public float getExactTemperature() {
  		CoordTriplet coords = getReferenceCoord();
  		return getBiome().getFloatTemperature(coords.x, coords.y, coords.z);
  	}
  
  	@Override
  	public float getExactHumidity() {
  		return getBiome().rainfall;
  	}
  
  	private int[] coords;
  	private int[] offset;
  	private int[] area;
  
  	@Override
  	public int[] getCoords() {
  		if (coords == null) {
  			CoordTriplet centerCoord = getCenterCoord();
  			coords = new int[]{centerCoord.x, centerCoord.y, centerCoord.z};
  		}
  		return coords;
  	}
  
  	@Override
  	public int[] getOffset() {
  		if (offset == null) {
  			offset = new int[]{-getArea()[0] / 2, -2, -getArea()[2] / 2};
  		}
  		return offset;
  	}
  
  	@Override
  	public int[] getArea() {
  		if (area == null) {
  			area = new int[]{7 + (allowedExtent * 2), 13, 7 + (allowedExtent * 2)};
  		}
  		return area;
  	}
  
  	@Override
  	public World getWorld() {
  		return worldObj;
  	}
  
  	@Override
  	public boolean doWork() {
  		if (targets.isEmpty() || updateOnInterval(400)) {
  			setUpFarmlandTargets();
  		}
  
  		IErrorLogic errorLogic = getErrorLogic();
  
  		if (!pendingProduce.isEmpty()) {
  			boolean added = inventory.tryAddPendingProduce(pendingProduce);
  			errorLogic.setCondition(!added, EnumErrorCode.NOSPACE);
  			return added;
  		}
  
  		boolean hasFertilizer = fertilizerManager.maintainFertilizer(inventory);
  		if (errorLogic.setCondition(!hasFertilizer, EnumErrorCode.NOFERTILIZER)) {
  			return false;
  		}
  
  		// Cull queued crops.
  		if (!pendingCrops.isEmpty()) {
  			if (cullCrop(pendingCrops.peek(), harvestProvider)) {
  				pendingCrops.pop();
  				return true;
  			} else {
  				return false;
  			}
  		}
  
  		// Cultivation and collection
  		FarmWorkStatus farmWorkStatus = new FarmWorkStatus();
  
  		for (FarmDirection farmSide : FarmDirection.values()) {
  
  			IFarmLogic logic = getFarmLogic(farmSide);
  			if (logic == null) {
  				continue;
  			}
  
  			// Allow listeners to cancel this cycle.
  			if (isCycleCanceledByListeners(logic, farmSide, farmListeners)) {
  				continue;
  			}
  
  			// Always try to collect windfall.
  			if (collectWindfall(logic)) {
  				farmWorkStatus.didWork = true;
  			}
  
  			List<FarmTarget> farmTargets = targets.get(farmSide);
  
  			if (stage == Stage.HARVEST) {
  				Collection<ICrop> harvested = harvestTargets(farmTargets, logic, farmListeners);
  				farmWorkStatus.didWork = harvested.size() > 0;
  				if (harvested.size() > 0) {
  					pendingCrops.addAll(harvested);
  					harvestProvider = logic;
  				}
  			} else if (stage == Stage.CULTIVATE) {
  				farmWorkStatus = cultivateTargets(farmWorkStatus, farmTargets, logic);
  			}
  
  			if (farmWorkStatus.didWork) {
  				break;
  			}
  		}
  
  		if (stage == Stage.CULTIVATE) {
  			errorLogic.setCondition(!farmWorkStatus.hasFarmland, EnumErrorCode.NOFARMLAND);
  			errorLogic.setCondition(!farmWorkStatus.hasFertilizer, EnumErrorCode.NOFERTILIZER);
  			errorLogic.setCondition(!farmWorkStatus.hasLiquid, EnumErrorCode.NOLIQUID);
  		}
  
  		// alternate between cultivation and harvest.
  		stage = stage.next();
  
  		return farmWorkStatus.didWork;
  	}
  
  	private void setUpFarmlandTargets() {
  		Vect targetStart = new Vect(getCoords());
  
  		CoordTriplet max = getMaximumCoord();
  		CoordTriplet min = getMinimumCoord();
  
  		int sizeNorthSouth = Math.abs(max.z - min.z) + 1;
  		int sizeEastWest = Math.abs(max.x - min.x) + 1;
  
  		// Set the maximum allowed extent.
  		allowedExtent = Math.max(sizeNorthSouth, sizeEastWest) * Config.farmSize + 1;
  
  		createTargets(worldObj, targets, targetStart, allowedExtent, sizeNorthSouth, sizeEastWest);
  		setExtents(worldObj, targets);
  	}
  
  	private static void createTargets(World world, Map<FarmDirection, List<FarmTarget>> targets, Vect targetStart, final int allowedExtent, final int farmSizeNorthSouth, final int farmSizeEastWest) {
  
  		for (FarmDirection farmSide : FarmDirection.values()) {
  
  			final int farmWidth;
  			if (farmSide == FarmDirection.NORTH || farmSide == FarmDirection.SOUTH) {
  				farmWidth = farmSizeEastWest;
  			} else {
  				farmWidth = farmSizeNorthSouth;
  			}
  
  			// targets extend sideways in a pinwheel pattern around the farm, so they need to go a little extra distance
  