@Mod(modid = "PLC", name = "modPLC", version = "0.0.1")
@NetworkMod(clientSideRequired = true, serverSideRequired = true, packetHandler = PacketHandler.class, channels = { "modPLCChannel12" })
public class PLC {

	public static Block controller = null;
	public static Block extenderBasic = null;

	public static Item multiItem = null;
	public static Item itemWaferRaw = null;
	public static ItemStack itemWafer = null;
	public static ItemStack itemWaferRed = null;
	public static ItemStack itemWaferGreen = null;
	public static ItemStack itemWaferBlue = null;
	public static ItemStack itemChipRed = null;
	public static ItemStack itemChipGreen = null;
	public static ItemStack itemChipBlue = null;
	public static ItemStack itemInterface = null;
	public static ItemStack itemInterfaceRedstone = null;

	private NetworkBroker networkBroker = new NetworkBroker();

	// The instance of your mod that Forge uses.
	@Instance("PLC")
	public static PLC instance;

	// Says where the client and server 'proxy' code is loaded.
	@SidedProxy(clientSide = "de.squig.plc.client.ClientProxy", serverSide = "de.squig.plc.CommonProxy")
	public static CommonProxy proxy;

	@PreInit
	public void preInit(FMLPreInitializationEvent event) {
		LogHelper.init();
		Configuration config = new Configuration(
				event.getSuggestedConfigurationFile());
		config.load();

		StaticData.BlockController = config.getBlock("controller", 500)
				.getInt();
		StaticData.BlockExtender = config.getBlock("extender", 501).getInt();
		StaticData.range = config.get("extenders", "range", 32,
				"Range of Extenders to connect controllers (-1 no limit)")
				.getInt();

		StaticData.ItemsBase = config.getItem("itemsBase", 1900).getInt();
		StaticData.ItemWaferRaw = config.getItem("waferRaw", 1901).getInt();

		config.save();

		CircuitElement.addCircuitElementType(0, Deleted.class);
		CircuitElement.addCircuitElementType(1, Line.class);
		CircuitElement.addCircuitElementType(2, Input.class);
		CircuitElement.addCircuitElementType(3, Output.class);
		CircuitElement.addCircuitElementType(50, Not.class);
		CircuitElement.addCircuitElementType(51, High.class);
		CircuitElement.addCircuitElementType(100, Counter.class);
		CircuitElement.addCircuitElementType(101, Pulse.class);
		CircuitElement.addCircuitElementType(102, Timer.class);
		CircuitElement.addCircuitElementType(103, Delay.class);

		CircuitObject.addCircuitObjectType(1, LogicInput.class, null, null);
		CircuitObject.addCircuitObjectType(2, LogicOutput.class, null, null);
		CircuitObject.addCircuitObjectType(3, LogicMemory.class, null, null);
		CircuitObject.addCircuitObjectType(10, LogicDelay.class,
				LogicDelay.dataTypes, LogicDelay.dataStatics);
		CircuitObject.addCircuitObjectType(11, LogicTimer.class,
				LogicTimer.dataTypes, LogicTimer.dataStatics);
		CircuitObject.addCircuitObjectType(12, LogicCounter.class,
				LogicCounter.dataTypes, LogicCounter.dataStatics);

	}

	@Init
	public void load(FMLInitializationEvent event) {
		proxy.registerRenderers();
		proxy.initTileEntities();

		// TickRegistry.registerTickHandler(new TickHandler(), Side.SERVER);

		NetworkRegistry.instance().registerGuiHandler(instance, proxy);

		controller = new Controller(StaticData.BlockController);
		extenderBasic = new ExtenderBasic(StaticData.BlockExtender);

		multiItem = new ItemMulti(StaticData.ItemsBase);
		itemWaferRaw = new ItemRawWafer(StaticData.ItemWaferRaw);

		LogHelper.info("multiItem @" + StaticData.ItemsBase);
		LogHelper.info("WafterRaw @" + StaticData.ItemWaferRaw);

		itemWafer = new ItemStack(multiItem, 1, StaticData.ItemsShiftWafer);
		itemWaferRed = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftRedWafer);
		itemWaferGreen = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftGreenWafer);
		itemWaferBlue = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftBlueWafer);
		itemChipRed = new ItemStack(multiItem, 1, StaticData.ItemsShiftRedChip);
		itemChipGreen = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftGreenChip);
		itemChipBlue = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftBlueChip);
		itemInterface = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftInterface);
		itemInterfaceRedstone = new ItemStack(multiItem, 1,
				StaticData.ItemsShiftInterfaceRedstone);

		LanguageRegistry.addName(controller, "Controller");
		MinecraftForge.setBlockHarvestLevel(controller, "pickaxe", 3);
		GameRegistry.registerBlock(controller, "plc.controller");

		LanguageRegistry.addName(extenderBasic, "Basic Extender");
		MinecraftForge.setBlockHarvestLevel(extenderBasic, "pickaxe", 3);
		GameRegistry.registerBlock(extenderBasic, "plc.extender");

		GameRegistry.registerItem(itemWaferRaw, "plc.rawwafer");
		GameRegistry.registerItem(multiItem, "plc.multiitem");

		LanguageRegistry.addName(itemWaferRaw, "Raw Wafer");
		LanguageRegistry.addName(itemWafer, "Sliced Wafer");
		LanguageRegistry.addName(itemWaferRed, "Red Wafer");
		LanguageRegistry.addName(itemWaferGreen, "Green Wafer");
		LanguageRegistry.addName(itemWaferBlue, "Blue Wafer");
		LanguageRegistry.addName(itemChipRed, "Power Chip");
		LanguageRegistry.addName(itemChipGreen, "Controller Chip");
		LanguageRegistry.addName(itemChipBlue, "Memory Chip");
		LanguageRegistry.addName(itemInterface, "Interface Card");
		LanguageRegistry.addName(itemInterfaceRedstone, "Redstone Interface");

		GameRegistry.addRecipe(new ItemStack(itemWaferRaw, 1), new Object[] {
				"SCS", "CSC", "SCS", 'C', Item.coal, 'S', Block.sand });

		GameRegistry.addSmelting(itemWaferRaw.shiftedIndex, new ItemStack(
				multiItem, 8, StaticData.ItemsShiftWafer), 0.1f);

		GameRegistry.addRecipe(new ItemStack(multiItem, 3,
				StaticData.ItemsShiftRedWafer), new Object[] { "   ", " R ",
				"WWW", 'W', itemWafer, 'R', Item.redstone });
		GameRegistry.addRecipe(new ItemStack(multiItem, 3,
				StaticData.ItemsShiftGreenWafer), new Object[] { "   ", "GRG",
				"WWW", 'W', itemWafer, 'R', Item.redstone, 'G',
				Item.lightStoneDust });
		GameRegistry.addRecipe(new ItemStack(multiItem, 3,
				StaticData.ItemsShiftBlueWafer), new Object[] { "   ", "LRL",
				"WWW", 'W', itemWafer, 'R', Item.redstone, 'L',
				new ItemStack(Item.dyePowder, 1, 4) });

		GameRegistry.addRecipe(new ItemStack(multiItem, 4,
				StaticData.ItemsShiftRedChip), new Object[] { "WIW", "ICI",
				"WIW", 'C', itemWaferRed, 'I', Item.ingotIron, 'W',
				Block.cloth});
		GameRegistry.addRecipe(new ItemStack(multiItem, 4,
				StaticData.ItemsShiftBlueChip), new Object[] { "WWW", "WCG",
				"WWW", 'C', itemWaferBlue, 'G', Item.goldNugget, 'W',
				Block.cloth});
		GameRegistry.addRecipe(new ItemStack(multiItem, 4,
				StaticData.ItemsShiftGreenChip), new Object[] { "WGW", "ICG",
				"WGW", 'C', itemWaferGreen, 'G', Item.goldNugget, 'I', Item.ingotIron,'W',
				Block.cloth});
		
		GameRegistry.addRecipe(new ItemStack(controller,1), new Object[] { "MMW", "PCG",
				"MMW", 'M', itemChipBlue, 'C', itemChipGreen, 
				'P',itemChipRed,'G', Block.glass,'W',
				Block.planks});
		
		GameRegistry.addRecipe(new ItemStack(extenderBasic,1), new Object[] { "WWI", "PCI",
			"WWI",  'C', itemChipGreen, 
			'P',itemChipRed,
			'I', itemInterface,
			'W', Block.planks});
	
		GameRegistry.addRecipe(new ItemStack(multiItem,8,StaticData.ItemsShiftInterface), new Object[] {
			"   ", "III","PWW", 
			'I', Item.ingotIron,
			'W', Block.woodSingleSlab,
			'P', Item.paper});
	
		GameRegistry.addRecipe(new ItemStack(multiItem,6,StaticData.ItemsShiftInterfaceRedstone), new Object[] {
			" R ", " C "," I ", 
			'C',itemChipRed,
			'I', itemInterface,
			'R', Item.redstone});
		
		// trying to load bc3 int

		try {
			Class plcmod = PLC.class.getClassLoader().loadClass(
					"de.squig.plc.bc3.BC3Integration");
			plcmod.getMethod("init", new Class[0]).invoke(null, new Object[0]);
		} catch (Throwable t) {
			LogHelper.warn("could not load BC3 Integration! " + t.getMessage());
		}

	}

	@PostInit
	public void postInit(FMLPostInitializationEvent event) {

	}

	public NetworkBroker getNetworkBroker() {
		return networkBroker;
	}

} public class SignalEvent extends PLCEvent {
	private Signal signal;
	private int channel;
	private UUID dest;
	public SignalEvent(TilePLC source, UUID dest, Signal signal, int channel) {
		super(source, dest);
		this.signal = signal;
		this.channel = channel;
	}
	
	
	public Signal getSignal() {
		return signal;
	}
	public int getChannel() {
		return channel;
	}

	
	public static void fireDirected (TilePLC src, UUID dest, Signal signal, int chn) {
		SignalEvent event = new SignalEvent(src,dest, signal,chn);
		PLC.instance.getNetworkBroker().fireEvent(event);
	}
	
} public interface ISignalListener {
	public void onSignal(Signal signal);
} public enum Signal {
	ON, OFF, PULSE, NEGATIVEPULSE;

	public Signal getHigherSignal(Signal signal) {
		if (signal == null)
			return OFF;
		if (this.equals(signal))
			return this;
		if (signal.equals(ON) || this.equals(ON))
			return ON;
		if (this.equals(OFF))
			return signal;
		if (signal.equals(OFF))
			return this;
		if (this.equals(PULSE) && signal.equals(NEGATIVEPULSE))
			return ON;
		if (this.equals(NEGATIVEPULSE) && signal.equals(PULSE))
			return ON;
		return OFF;
	}

	public Signal getLowerSignal(Signal signal) {
		if (signal == null)
			return OFF;
		if (this.equals(OFF) || signal.equals(OFF))
			return OFF;
		if (this.equals(ON) && signal.equals(ON))
			return Signal.ON;
		if (this.equals(ON))
			return signal;
		if (signal.equals(ON))
			return this;
		if (this.equals(PULSE) && signal.equals(NEGATIVEPULSE))
			return OFF;
		if (this.equals(NEGATIVEPULSE) && signal.equals(PULSE))
			return OFF;
		return OFF;
	}

	public Signal invert() {
		if (this.equals(ON))
			return OFF;
		if (this.equals(OFF))
			return ON;
		if (this.equals(PULSE))
			return NEGATIVEPULSE;
		if (this.equals(NEGATIVEPULSE))
			return PULSE;
		return OFF;
	}

	public static Signal fromOrdinal(int ordinal) {
		switch (ordinal) {
		case 0:
			return ON;
		case 1:
			return OFF;
		case 2:
			return PULSE;
		case 3:
			return NEGATIVEPULSE;
		}
		return null;
	}

} public class ExtenderChannel {
	public static enum TYPES {
		INPUT, OUTPUT
	}

	private TYPES type;
	private int number;
	private TileExtender extender;

	
	private String functionData = "";
	private String triggerData = "";
	private Object functionLocalData = "";
	private Object triggerLocalData = "";
	
	private long sheduledOn = -1;
	

	private ExtenderFunction function = ExtenderFunction.disabledFunction;
	private int side = 6;

	private Signal signal = Signal.OFF;
	private ExtenderTrigger trigger;

	
	
	
	public ExtenderChannel(TileExtender extender, TYPES type, int number) {
		this.number = number;
		this.type = type;
		this.extender = extender;
		this.function = ExtenderFunction.disabledFunction;
	}
	
	public void setSignalWithReset(Signal signal, int timer) {
		
	}



	public TYPES getType() {
		return type;
	}

	public void setType(TYPES type) {
		this.type = type;
	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}

	public String getName() {
		if (type == TYPES.INPUT)
			return ("INPUT " + number);
		else
			return ("OUTPUT " + number);
	}

	public ExtenderFunction getFunction() {
		return function;
	}

	public void setFunction(int functionid) {
		if (function != null && function.getId() == functionid)
			return;
		if (function != null) {
			function.onDestroy(this);
		}
		function = ExtenderFunction.getFunction(this,functionid);
		if (function != null)
			function.onCreate(this);
		
	}

	public TileExtender getExtender() {
		return extender;
	}

	public Signal getSignal() {
		return signal;
	}

	public void setSignal(Signal signal) {
		this.signal = signal;
	}

	public void saveTo(DataOutputStream data) throws IOException {
		data.writeChar(number);
	
		data.writeChar(type.ordinal());
		data.writeChar(side);
		
		data.writeChar(function.getId());
		if (trigger != null)
			data.writeChar(trigger.getTriggerId());
		else data.writeChar(255);
		data.writeUTF(functionData);
		data.writeUTF(triggerData);
		
	}

	public int getSide() {
		return side;
	}

	public void setSide(int side) {
		boolean update = this.side != side;
		this.side = side;
		if (update)
			extender.sheduleNeighbourUpdate();
	}

	public static ExtenderChannelNetworkData readFrom(DataInputStream data)
			throws IOException {

		char number = data.readChar();
		char type = data.readChar();
		char side = data.readChar();
		char functiontype = data.readChar();
		char triggerid = data.readChar();
		String functionData = data.readUTF();
		String triggervalue = data.readUTF();
		return new ExtenderChannelNetworkData(number, type, side, functiontype, triggerid, functionData,triggervalue);

	}

	public void inject(ExtenderChannelNetworkData dt) {
		if (this.type.ordinal() != dt.getType()) {
			LogHelper.error("Trying to Update Channel of wrong type! "
					+ this.getType().ordinal() + " != " + dt.getType());
			return;
		}
		
		
		setSide(dt.getSide());
		
		setFunction(dt.getFunctionType());
		
		if (dt.getTriggerType() < 255 && function != null && function.getTriggers() != null) {
			for (ExtenderTrigger trg : function.getTriggers()) 
				if (trg.getTriggerId() == dt.getTriggerType()) 
					setTrigger(trg);
				
		} else trigger = null;
			
		functionData = dt.getFunctionData();
		triggerData = dt.getTriggerData();

	}

	public void onSignal(Signal signal) {
		
		if (type.equals(TYPES.INPUT)) {
			if (function != null)
				function.onSignal(this,signal);
			this.signal = signal;

		} else if (type.equals(TYPES.OUTPUT)) {
			if (function != null && !(function instanceof DisabledFunction))
				function.onSignal(this,signal);
		}
	}
	
	public void onRedstoneChanged(boolean isRedstonePowered, boolean b) {
		if (function instanceof RedstoneFunction)
			((RedstoneFunction) function).onRedstoneChanged(this,isRedstonePowered,b);
	}

	public void enableShedule(long inticks) {
		setSheduledOn(extender.getWorldObj().getTotalWorldTime()+inticks);
		extender.addChannelToShedule(this);
	}
	
	
	public ExtenderTrigger getTrigger() {
		return trigger;
	}
	public void setTrigger(ExtenderTrigger trigger) {
		this.trigger = trigger;
	}


	public String getFunctionData() {
		return functionData;
	}


	public void setFunctionData(String functionData) {
		this.functionData = functionData;
	}


	public String getTriggerData() {
		return triggerData;
	}


	public void setTriggerData(String triggerData) {
		this.triggerData = triggerData;
	}


	public Object getFunctionLocalData() {
		return functionLocalData;
	}


	public void setFunctionLocalData(Object functionLocalData) {
		this.functionLocalData = functionLocalData;
	}


	public Object getTriggerLocalData() {
		return triggerLocalData;
	}


	public void setTriggerLocalData(Object triggerLocalData) {
		this.triggerLocalData = triggerLocalData;
	}

	public long getSheduledOn() {
		return sheduledOn;
	}

	public void setSheduledOn(long sheduledOn) {
		this.sheduledOn = sheduledOn;
	}
	

} public static enum TYPES {
	INPUT, OUTPUT
} public class LogHelper {

	private static Logger plcLoggerServer = null;
	private static Logger plcLoggerClient = null;

	public static void init() {
		plcLoggerClient = Logger.getLogger("plcClient");
		plcLoggerClient.setParent(FMLLog.getLogger());
		plcLoggerServer = Logger.getLogger("plcServer");
		plcLoggerServer.setParent(FMLLog.getLogger());

	}

	public static void log(Level logLevel, String message) {
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side == Side.CLIENT)
			plcLoggerClient.log(logLevel, message);
		else
			plcLoggerServer.log(logLevel, message);

	}

	public static void info(String message) {
		log(Level.INFO, message);
	}

	public static void warn(String message) {
		log(Level.WARNING, message);
	}

	public static void error(String message) {
		log(Level.SEVERE, message);
	}

} public class LogicInput extends CircuitObject {
	protected CircuitObjectOutputPin out;
	
	public LogicInput(Circuit circuit, short linkNumber) {
		super(circuit,null,null);
		out = new CircuitObjectOutputPin(this,"Input");
		addOutputPin(out);
		setLinkNumber(linkNumber);
		name = "Internal Input";
		
	}
	
	@Override
	public CircuitObjectOutputPin getOutputPin(ElementFunction funct) {
		return out;
	}
	
	
	
	
	
	public void onSignal(Signal signal) {
		if (!signal.equals(out.getSignal())) {
			out.onSignal(signal);
			circuit.setNeedsSimulation(true);
		}
	}
	
	@Override
	public void commit() {
		if (out.getSignal().equals(Signal.PULSE))
			out.onSignal(Signal.OFF);
		else if (out.getSignal().equals(Signal.NEGATIVEPULSE))
			out.onSignal(Signal.ON);
	}
	
	
} public class LogicOutput extends CircuitObject implements ICircuitObjectInputPinListener {
	protected CircuitObjectOutputPin out = new CircuitObjectOutputPin(this, "Output");
	protected CircuitObjectInputPin in = new CircuitObjectInputPin(this, "Output");
	protected CircuitObjectInputPin inSet = new CircuitObjectInputPin(this, "Output Set");
	protected CircuitObjectInputPin inReset = new CircuitObjectInputPin(this, "Output Reset");
	 
	protected boolean changed = false;
	
	protected Signal signal = Signal.OFF;
	protected Signal lastSignal = Signal.OFF;
	
	public LogicOutput(Circuit circuit, short linkNumber) {
		super(circuit,null,null);
		setLinkNumber(linkNumber);
		addInputPin(in);
		
		addInputPin(inSet);
		addInputPin(inReset);
		addOutputPin(out);
		in.setListener(this);
		inSet.setListener(this);
		inReset.setListener(this);
		name = "Internal Output";
	}
	

	public CircuitObjectInputPin getInputPin(ElementFunction funct) {
		if (ElementFunction.OUTPUTSET == funct)
			return inSet;
		if (ElementFunction.OUTPUTRESET == funct)
			return inReset;
		if (ElementFunction.OUTPUT == funct)
			return in;
		return null;
	}
	
	public CircuitObjectOutputPin getOutputPin(ElementFunction funct) {
		if (ElementFunction.OUTPUTREAD == funct)
			return out;
		return null;
	}
	

	@Override
	public void onSignal(CircuitObjectInputPin pin, Signal signal) {
		//LogHelper.info("LOP onSignal with "+lastSignal+" "+signal);
		if (pin == in) {
			this.signal = signal;
		} else if (pin == inSet && (signal.equals(Signal.ON) || signal.equals(Signal.PULSE))) {
			this.signal = Signal.ON;
		} else if (pin == inReset && (signal.equals(Signal.ON) || signal.equals(Signal.PULSE))) {
			this.signal = Signal.OFF;
		}
		out.onSignal(this.signal);
	}
	
	@Override
	public void commit() {
		//LogHelper.info("LOP commiting with "+lastSignal+" "+signal);
		if (!signal.equals(lastSignal)  ) {
			changed = true;
			lastSignal = signal;
		}
		if (signal.equals(Signal.PULSE))
			lastSignal = Signal.OFF;
		if (signal.equals(Signal.NEGATIVEPULSE))
			lastSignal = Signal.ON;
	}	


	public boolean isChanged() {
		return changed;
	}

	public void setChanged(boolean changed) {
		this.changed = changed;
	}


	public Signal getSignal() {
		return signal;
	}
	
} public class TileExtender extends TilePLC implements IInventory {
	public enum TYPE {
		BASIC
	};

	private ItemStack[] extenderCards = new ItemStack[2];

	private TYPE type = null;

	private int range = StaticData.ExtenderBaseRange;

	// SOFT link
	private UUID connectedController = null;
	private String connectedControllerName = null;

	private List<SearchResponseEvent> controllersInRange = null;

	private List<ExtenderChannel> channelsIn = null;
	private List<ExtenderChannel> channelsOut = null;

	private List<ExtenderChannel> sheduledChannelUpdates;

	private List<ExtenderChannel> tickListener = new ArrayList<ExtenderChannel>();

	private boolean isRedstonePowered;

	private boolean remoteUpdate = false;
	private boolean neighbourUpdate = false;

	private boolean channelsConfigChangedForGui = false;
	
	// load shit
	private boolean needsLoad = false;
	private List<ExtenderChannelNetworkData> loadChannelDataIn = null;
	private List<ExtenderChannelNetworkData> loadChannelDataOut = null;

	// status
	private char[] instatus = new char[0];
	private char[] outstatus = new char[0];

	public TileExtender() {
		super(PLCEvent.TARGETTYPE.EXTENDER);
		channelsIn = new ArrayList<ExtenderChannel>();
		channelsOut = new ArrayList<ExtenderChannel>();
		sheduledChannelUpdates = new LinkedList<ExtenderChannel>();

		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT))
			needsLoad = true;

	}

	public void updateStatus(char[] ins, char[] outs) {
		instatus = ins;
		outstatus = outs;
	}

	public char[] getInputs() {
		return instatus;
	}

	public char[] getOutputs() {
		return outstatus;
	}

	/**
	 * bc3 support integration
	 * 
	 * @return
	 */
	public static TileExtender createInstance() {
		try {
			Class bc3Extender = PLC.class.getClassLoader().loadClass(
					"de.squig.plc.bc3.compat.TileExtenderBC3");
			Constructor constructor = bc3Extender.getConstructor(null);
			TileExtender res = (TileExtender) constructor.newInstance(null);
			return res;
		} catch (Exception ex) {
			return new TileExtender();
		}
	}

	public boolean isSidePowered(ForgeDirection side) {
		return getSideSignal(side).equals(Signal.ON);
	}

	public Signal getSideSignal(ForgeDirection side) {
		Signal ret = Signal.OFF;
		for (int i = 0; i < channelsOut.size(); i++) {
			ExtenderChannel chn = channelsOut.get(i);
			if (chn.getFunction() instanceof RedstoneFunction
					&& (chn.getSide() == 6 || chn.getSide() == side.ordinal()))
				ret = ret.getHigherSignal(chn.getSignal());

		}
		return ret;
	}

	public void sheduleNeighbourUpdate() {
		neighbourUpdate = true;
	}

	public void sheduleRemoteUpdate() {
		remoteUpdate = true;
	}

	@Override
	public void onDestroy() {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		super.onDestroy();
	}

	@Override
	public void onChunkUnload() {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		super.onChunkUnload();
	}

	@Override
	protected void initialize() {
		super.initialize();

	}

	@Override
	public void validate() {
		super.validate();
	};

	@Override
	public boolean canUpdate() {
		return true;
	};

	public void addChannelToShedule(ExtenderChannel chn) {
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT))
			return;
		if (!sheduledChannelUpdates.contains(chn))
			sheduledChannelUpdates.add(chn);

	}

	@Override
	public void updateEntity() {
		super.updateEntity();
		if (needsLoad) {
			loadData();
		}
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT))
			return;
		long now = worldObj.getTotalWorldTime();
		if (sheduledChannelUpdates.size() > 0) {
			List<ExtenderChannel> tr = new LinkedList<ExtenderChannel>();
			for (ExtenderChannel chn : sheduledChannelUpdates) {
				if (now >= chn.getSheduledOn() && chn.getFunction() != null) {
					chn.getFunction().onUpdate(chn, now);
					chn.setSheduledOn(-1);
					tr.add(chn);
				}
			}
			sheduledChannelUpdates.removeAll(tr);
		}
		if (remoteUpdate) {
			PacketExtenderLiteData.sendUpdateToClients(this);
			remoteUpdate = false;
		}
		if (neighbourUpdate) {
			worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord,
					StaticData.BlockExtender);
			neighbourUpdate = false;
		}
	};

	private void loadData() {
		needsLoad = false;
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if (side.equals(Side.CLIENT)) {
			PacketExtenderLiteData.requestUpdateFromServer(this);
			return;
		}

		for (ExtenderChannelNetworkData dt : loadChannelDataIn) {
			injectChannel(ExtenderChannel.TYPES.INPUT, dt);
		}
		for (ExtenderChannelNetworkData dt : loadChannelDataOut) {
			injectChannel(ExtenderChannel.TYPES.OUTPUT, dt);
		}

		if (connectedController != null) {
			PLC.instance.getNetworkBroker().addMulticastListener(
					connectedController, this);
			requestUpdateFromController();
		}

		loadChannelDataIn = null;
		loadChannelDataOut = null;

	}

	public void readFromNBT(NBTTagCompound nbtTagCompound) {
		super.readFromNBT(nbtTagCompound);
		needsLoad = true;

		if (nbtTagCompound.hasKey("cuuid")) {
			connectedController = UUID.fromString(nbtTagCompound
					.getString("cuuid"));
			connectedControllerName = nbtTagCompound.getString("cname");
		}

		NBTTagList tagList = nbtTagCompound.getTagList("Items");
		this.extenderCards = new ItemStack[this.getSizeInventory()];
		for (int i = 0; i < tagList.tagCount(); ++i) {
			NBTTagCompound tagCompound = (NBTTagCompound) tagList.tagAt(i);
			byte slot = tagCompound.getByte("Slot");
			if (slot >= 0 && slot < this.extenderCards.length) {
				this.extenderCards[slot] = ItemStack
						.loadItemStackFromNBT(tagCompound);
			}
		}

		loadChannelDataIn = new ArrayList<ExtenderChannelNetworkData>();
		for (int i = 0; i < 64; i++) {
			if (nbtTagCompound.hasKey("I-" + i)) {
				try {
					InputStream is = new ByteArrayInputStream(
							nbtTagCompound.getByteArray("I-" + i));
					DataInputStream dis = new DataInputStream(is);
					loadChannelDataIn.add(ExtenderChannel.readFrom(dis));
				} catch (IOException ex) {
					LogHelper.error("exception durring reading data!");
				}
			}
		}
		loadChannelDataOut = new ArrayList<ExtenderChannelNetworkData>();
		for (int i = 0; i < 32; i++) {
			if (nbtTagCompound.hasKey("O-" + i)) {
				try {
					InputStream is = new ByteArrayInputStream(
							nbtTagCompound.getByteArray("O-" + i));
					DataInputStream dis = new DataInputStream(is);
					loadChannelDataOut.add(ExtenderChannel.readFrom(dis));
				} catch (IOException ex) {
					LogHelper.error("exception durring reading data!");
				}
			}
		}

	}

	public void writeToNBT(NBTTagCompound nbtTagCompound) {
		super.writeToNBT(nbtTagCompound);

		if (connectedController != null) {
			nbtTagCompound.setString("cuuid", connectedController.toString());
			nbtTagCompound.setString("cname", connectedControllerName);
		} else {
			nbtTagCompound.removeTag("cuuid");
			nbtTagCompound.removeTag("cname");
		}

		NBTTagList tagList = new NBTTagList();
		for (int currentIndex = 0; currentIndex < this.extenderCards.length; ++currentIndex) {
			if (this.extenderCards[currentIndex] != null) {
				NBTTagCompound tagCompound = new NBTTagCompound();
				tagCompound.setByte("Slot", (byte) currentIndex);
				this.extenderCards[currentIndex].writeToNBT(tagCompound);
				tagList.appendTag(tagCompound);
			}
		}
		nbtTagCompound.setTag("Items", tagList);

		for (int i = 0; i < 64; i++) {
			if (i < channelsIn.size()) {
				try {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					DataOutputStream w = new DataOutputStream(baos);

					ExtenderChannel chan = channelsIn.get(i);
					chan.saveTo(w);
					w.flush();

					byte[] result = baos.toByteArray();
					nbtTagCompound.setByteArray("I-" + i, result);

				} catch (IOException ex) {
					LogHelper.error("exception durring writing data!");
				}
			} else {
				if (nbtTagCompound.hasKey("I-" + i))
					nbtTagCompound.removeTag("I-" + i);
			}
		}
		for (int i = 0; i < 32; i++) {
			if (i < channelsOut.size()) {
				try {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					DataOutputStream w = new DataOutputStream(baos);

					ExtenderChannel chan = channelsOut.get(i);
					chan.saveTo(w);
					w.flush();

					byte[] result = baos.toByteArray();
					nbtTagCompound.setByteArray("O-" + i, result);

				} catch (IOException ex) {
					LogHelper.error("exception durring writing data!");
				}
			} else {
				if (nbtTagCompound.hasKey("O-" + i))
					nbtTagCompound.removeTag("O-" + i);
			}
		}

	}

	public void sendBroadcastSearch() {
		controllersInRange = new ArrayList<SearchResponseEvent>();
		SearchEvent event = new SearchEvent(this,
				PLCEvent.TARGETTYPE.CONTROLLER, null);
		PLC.instance.getNetworkBroker().fireEvent(event);
	}

	public List<SearchResponseEvent> getControllerInRange() {
		return controllersInRange;
	}

	private void createChannels(SearchResponseEvent linkto) {
		channelsIn.clear();
		channelsOut.clear();
		tickListener.clear();
		char[] ins = new char[linkto.getInChannels()];
		char[] outs = new char[linkto.getOutChannels()];

		for (int i = 0; i < linkto.getInChannels(); i++) {
			ExtenderChannel channel = new ExtenderChannel(this,
					ExtenderChannel.TYPES.INPUT, i);

			channelsIn.add(channel);
			ins[i] = 0;

		}
		for (int i = 0; i < linkto.getOutChannels(); i++) {
			ExtenderChannel channel = new ExtenderChannel(this,
					ExtenderChannel.TYPES.OUTPUT, i);

			channelsOut.add(channel);
			outs[i] = 0;
		}
		updateStatus(ins, outs);
	}

	public void checkRedstonePower() {
		boolean isRedstonePowered = worldObj.isBlockIndirectlyGettingPowered(
				xCoord, yCoord, zCoord);

		for (ExtenderChannel channel : channelsIn) {
			if (channel.getFunction() instanceof RedstoneFunction)
				channel.onRedstoneChanged(isRedstonePowered, false);
		}

	}

	public void injectChannel(TYPES input, ExtenderChannelNetworkData dt) {
		ExtenderChannel chnn = null;
		List<ExtenderChannel> chns = channelsIn;
		if (input.equals(ExtenderChannel.TYPES.OUTPUT))
			chns = channelsOut;

		for (ExtenderChannel chn : chns)
			if (chn.getType().ordinal() == dt.getType())
				if (chn.getNumber() == dt.getNumber()) {
					chnn = chn;
					break;
				}
		if (chnn == null) {
			chnn = new ExtenderChannel(this,
					ExtenderChannel.TYPES.values()[dt.getType()],
					dt.getNumber());
			chns.add(chnn);
		}
		chnn.inject(dt);
	}

	public void onEvent(PLCEvent event) {
		if (event instanceof ControllerDataEvent) {
			ControllerDataEvent eventd = (ControllerDataEvent) event;
			for (ControllerDataPayload load : eventd.getPayload()) {
				if (load.channel < channelsOut.size()) {
					ExtenderChannel chn = channelsOut.get(load.channel);
					chn.onSignal(load.signal);
				}
			}

		} else if (event instanceof SignalEvent) {
			SignalEvent events = (SignalEvent) event;
			int chn = events.getChannel();

			ExtenderChannel channel;
			if (chn < channelsOut.size()
					&& (channel = channelsOut.get(chn)) != null)
				channel.onSignal(events.getSignal());
			else
				LogHelper.warn("cound not route event to channel "
						+ events.getChannel());
		} else if (event instanceof SearchResponseEvent) {
			if (controllersInRange == null)
				controllersInRange = new ArrayList<SearchResponseEvent>();
			controllersInRange.add((SearchResponseEvent) event);
			return;
		}

	}

	public void link(SearchResponseEvent linkto) {
		connectedController = linkto.getUuid();
		connectedControllerName = linkto.getName();
		createChannels(linkto);

	}

	public void linkFromPackage(UUID controller, String controllerName) {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		boolean requestUpdate = (this.connectedController == null || !this.connectedController
				.equals(controller));
		connectedController = controller;
		connectedControllerName = controllerName;
		if (connectedController != null)
			PLC.instance.getNetworkBroker().addMulticastListener(
					connectedController, this);

		if (requestUpdate)
			requestUpdateFromController();

	}

	public void unlink() {
		if (connectedController != null)
			PLC.instance.getNetworkBroker().removeMulticastListener(
					connectedController, this);
		connectedController = null;
		connectedControllerName = null;
		channelsIn.clear();
		channelsOut.clear();
		tickListener.clear();
		sheduleNeighbourUpdate();
		sheduleRemoteUpdate();
	}

	public void requestUpdateFromController() {
		PLC.instance.getNetworkBroker().fireEvent(
				new ControllerDataEvent(this, connectedController, null));
	}

	public UUID getConnectedController() {
		return connectedController;
	}

	public String getConnectedControllerName() {
		return connectedControllerName;
	}

	public List<ExtenderChannel> getChannelsIn() {
		return channelsIn;
	}

	public List<ExtenderChannel> getChannelsOut() {
		return channelsOut;
	}

	/**
	 * Returns the number of slots in the inventory.
	 */
	public int getSizeInventory() {
		return this.extenderCards.length;
	}

	@Override
	public void setInventorySlotContents(int slot, ItemStack stack) {
		extenderCards[slot] = stack;
		if (stack != null && stack.stackSize > getInventoryStackLimit()) {
			stack.stackSize = getInventoryStackLimit();
		}
		checkInterfaces();
		onSlotChange();
	}

	@Override
	public ItemStack getStackInSlot(int slot) {
		return extenderCards[slot];
	}

	@Override
	public ItemStack getStackInSlotOnClosing(int slot) {
		ItemStack stack = getStackInSlot(slot);
		if (stack != null) {
			setInventorySlotContents(slot, null);
		}
		return stack;
	}

	public String getInvName() {
		return "container.extender";
	}

	public int getInvCheckSum() {
		int sum = 0;
		if (extenderCards[0] != null)
			sum += extenderCards[0].stackSize;
		return sum;
	}
	
	
	public int getInventoryStackLimit() {
		return 64;
	}

	private void checkInterfaces() {
		
		// redstone...
		Class chk = RedstoneFunction.class;
		boolean update = false;
		int space = 0;
		if (extenderCards[0] != null)
			space = extenderCards[0].stackSize;

		for (ExtenderChannel chn : channelsIn) {
			if (chn.getFunction() instanceof RedstoneFunction) {
				if (space > 0)
					space--;
				else {
					chn.setFunction(0);
					update = true;
				}
			}
		}
		for (ExtenderChannel chn : channelsOut) {
			if (chn.getFunction() instanceof RedstoneFunction) {
				if (space > 0)
					space--;
				else {
					chn.setFunction(0);
					update = true;
				}
			}
		}
		if (update) {
			channelsConfigChangedForGui = true;
		}
	}
	
	public void onSlotChange () {
		channelsConfigChangedForGui = true;
	}

	private int countFunction(Class funct) {
		int count = 0;
		for (ExtenderChannel chn : channelsIn) {
			if (chn.getFunction() instanceof RedstoneFunction)
				count++;
		}
		for (ExtenderChannel chn : channelsOut) {
			if (chn.getFunction() instanceof RedstoneFunction)
				count++;
		}
		return count;
	}

	public boolean hasInterfaceFor(Class funct) {
		if (funct.equals(RedstoneFunction.class)) {
			if (extenderCards[0] == null)
				return false;
			int count = extenderCards[0].stackSize;
			int fnct = countFunction(RedstoneFunction.class);
			LogHelper.info("space: "+count+" has:"+fnct);
			if (count <= fnct)
				return false;
		}
		return true;
	}

	public void openChest() {

	}

	public void closeChest() {

	}

	@Override
	public ItemStack decrStackSize(int slot, int amt) {
		ItemStack stack = getStackInSlot(slot);
		if (stack != null) {
			if (stack.stackSize <= amt) {
				setInventorySlotContents(slot, null);
			} else {
				stack = stack.splitStack(amt);
				if (stack.stackSize == 0) {
					setInventorySlotContents(slot, null);
				}
			}
		}
		checkInterfaces();
		onSlotChange();
		return stack;
	}

	public boolean isChannelsConfigChangedForGui() {
		if (channelsConfigChangedForGui) {
			channelsConfigChangedForGui = false;
			return true;
		} else return false;
	}

}
import java.util.ArrayList;
import java.util.List;
import cpw.mods.fml.common.FMLCommonHandler;
import de.squig.plc.PLC;
import de.squig.plc.event.SignalEvent;
import de.squig.plc.logic.ISignalListener;
import de.squig.plc.logic.Signal;
import de.squig.plc.logic.extender.ExtenderChannel;
import de.squig.plc.logic.extender.ExtenderChannel.TYPES;
import de.squig.plc.logic.helper.LogHelper;
import de.squig.plc.logic.objects.LogicInput;
import de.squig.plc.logic.objects.LogicOutput;
import de.squig.plc.tile.TileExtender;
package de.squig.plc.logic.extender.function;




public class RedstoneFunction extends ExtenderFunction {
	private static ExtenderTrigger defaultTrigger = new ExtenderTrigger("direct signal", false, 0, null);
	private static List<ExtenderTrigger> myTriggers = new ArrayList<ExtenderTrigger>() {
		{
			add(defaultTrigger);
			add(new ExtenderTrigger("inverted input", false, 1, ExtenderChannel.TYPES.INPUT));